)]}' {"version": 3, "sources": ["/base/static/tests/test_ir_model_fields_translation.js", "/web/static/tests/legacy/helpers/test_utils_file.js", "/web/static/tests/helpers/cleanup.js", "/web/static/tests/helpers/utils.js", "/web/static/tests/utils.js", "/mail/static/tests/tours/discuss_public_tour.js", "/mail/static/tests/tours/mail_channel_as_guest_tour.js", "/mail/static/tests/tours/mail_full_composer_test_tour.js", "/mail/static/tests/tours/user_modify_own_profile_tour.js", "/portal/static/tests/tours/portal.js", "/stock/static/tests/tours/stock_report_tests.js", "/account/static/tests/tours/account_dashboard_setup_bar_tests.js", "/account/static/tests/tours/tax_group_tests.js", "/point_of_sale/static/tests/tours/BarcodeScanning.tour.js", "/point_of_sale/static/tests/tours/Chrome.tour.js", "/point_of_sale/static/tests/tours/PaymentScreen.tour.js", "/point_of_sale/static/tests/tours/ProductConfigurator.tour.js", "/point_of_sale/static/tests/tours/ProductScreen.tour.js", "/point_of_sale/static/tests/tours/ReceiptScreen.tour.js", "/point_of_sale/static/tests/tours/TicketScreen.tour.js", "/point_of_sale/static/tests/tours/helpers/ChromeTourMethods.js", "/point_of_sale/static/tests/tours/helpers/ErrorPopupTourMethods.js", "/point_of_sale/static/tests/tours/helpers/NumberPopupTourMethods.js", "/point_of_sale/static/tests/tours/helpers/PartnerListScreenTourMethods.js", "/point_of_sale/static/tests/tours/helpers/PaymentScreenTourMethods.js", "/point_of_sale/static/tests/tours/helpers/ProductConfiguratorTourMethods.js", "/point_of_sale/static/tests/tours/helpers/ProductScreenTourMethods.js", "/point_of_sale/static/tests/tours/helpers/ReceiptScreenTourMethods.js", "/point_of_sale/static/tests/tours/helpers/SelectionPopupTourMethods.js", "/point_of_sale/static/tests/tours/helpers/TextAreaPopupTourMethods.js", "/point_of_sale/static/tests/tours/helpers/TextInputPopupTourMethods.js", "/point_of_sale/static/tests/tours/helpers/TicketScreenTourMethods.js", "/point_of_sale/static/tests/tours/helpers/utils.js", "/point_of_sale/static/tests/tours/point_of_sale.js", "/website/static/tests/tour_utils/widget_lifecycle_dep_widget.js", "/website/static/tests/tours/automatic_editor.js", "/website/static/tests/tours/carousel_content_removal.js", "/website/static/tests/tours/client_action_iframe_fallback.js", "/website/static/tests/tours/client_action_redirect.js", "/website/static/tests/tours/conditional_visibility.js", "/website/static/tests/tours/conditional_visibility_frontend.js", "/website/static/tests/tours/configurator_translation.js", "/website/static/tests/tours/custom_popup_snippet.js", "/website/static/tests/tours/default_shape_gets_palette_colors.js", "/website/static/tests/tours/drag_and_drop_on_non_editable.js", "/website/static/tests/tours/edit_link_popover.js", "/website/static/tests/tours/edit_megamenu.js", "/website/static/tests/tours/edit_menus.js", "/website/static/tests/tours/edit_translated_page.js", "/website/static/tests/tours/editable_root_as_custom_snippet.js", "/website/static/tests/tours/focus_blur_snippets.js", "/website/static/tests/tours/gray_color_palette.js", "/website/static/tests/tours/grid_layout.js", "/website/static/tests/tours/homepage_edit_discard.js", "/website/static/tests/tours/html_editor.js", "/website/static/tests/tours/link_tools.js", "/website/static/tests/tours/media_dialog.js", "/website/static/tests/tours/media_iframe_video.js", "/website/static/tests/tours/multi_edition.js", "/website/static/tests/tours/page_manager.js", "/website/static/tests/tours/parallax.js", "/website/static/tests/tours/popup_visibility_option.js", "/website/static/tests/tours/public_user_editor.js", "/website/static/tests/tours/reset_password.js", "/website/static/tests/tours/restricted_editor.js", "/website/static/tests/tours/rte.js", "/website/static/tests/tours/snippet_background_edition.js", "/website/static/tests/tours/snippet_cache_across_websites.js", "/website/static/tests/tours/snippet_countdown.js", "/website/static/tests/tours/snippet_editor_panel_options.js", "/website/static/tests/tours/snippet_empty_parent_autoremove.js", "/website/static/tests/tours/snippet_image_gallery.js", "/website/static/tests/tours/snippet_images_wall.js", "/website/static/tests/tours/snippet_popup_add_remove.js", "/website/static/tests/tours/snippet_popup_and_animations.js", "/website/static/tests/tours/snippet_popup_and_scrollbar.js", "/website/static/tests/tours/snippet_popup_open_on_top.js", "/website/static/tests/tours/snippet_social_media.js", "/website/static/tests/tours/snippet_table_of_content.js", "/website/static/tests/tours/snippet_translation.js", "/website/static/tests/tours/snippet_version.js", "/website/static/tests/tours/snippets_all_drag_and_drop.js", "/website/static/tests/tours/specific_website_editor.js", "/website/static/tests/tours/start_cloned_snippet.js", "/website/static/tests/tours/text_animations.js", "/website/static/tests/tours/translate_menu_name.js", "/website/static/tests/tours/unsplash_beacon.js", "/website/static/tests/tours/website_backend_menus_redirect.js", "/website/static/tests/tours/website_click_tests.js", "/website/static/tests/tours/website_form_editor.js", "/website/static/tests/tours/website_form_editor_frontend.js", "/website/static/tests/tours/website_navbar_menu.js", "/website/static/tests/tours/website_no_dirty_page.js", "/website/static/tests/tours/website_page_options.js", "/website/static/tests/tours/website_snippets_menu_tabs.js", "/website/static/tests/tours/website_style_edition.js", "/website/static/tests/tours/website_text_edition.js", "/website/static/tests/tours/widget_lifecycle.js", "/website/static/src/client_actions/website_preview/website_preview_test_mode.js", "/sale/static/tests/tours/sale_signature.js", "/calendar/static/tests/tours/calendar_tour.js", "/contacts/static/tests/tours/debug_menu_set_defaults.js", "/crm/static/tests/tours/create_crm_team_tour.js", "/crm/static/tests/tours/crm_email_and_phone_propagation.js", "/crm/static/tests/tours/crm_forecast_tour.js", "/crm/static/tests/tours/crm_rainbowman.js", "/project/static/tests/tours/personal_stage_tour.js", "/project/static/tests/tours/project_burndown_chart_tour.js", "/project/static/tests/tours/project_sharing_tour.js", "/project/static/tests/tours/project_tags_filter_tour_tests.js", "/project/static/tests/tours/project_update_tour_tests.js", "/website_payment/static/tests/tours/donation.js", "/website_sale/static/tests/tours/website_sale_add_to_cart_snippet_tour.js", "/website_sale/static/tests/tours/website_sale_buy.js", "/website_sale/static/tests/tours/website_sale_cart_popover.js", "/website_sale/static/tests/tours/website_sale_category_page_and_products_snippet.js", "/website_sale/static/tests/tours/website_sale_complete_flow.js", "/website_sale/static/tests/tours/website_sale_complete_flow_backend.js", "/website_sale/static/tests/tours/website_sale_fiscal_position_tour.js", "/website_sale/static/tests/tours/website_sale_google_analytics.js", "/website_sale/static/tests/tours/website_sale_remove_product_image.js", "/website_sale/static/tests/tours/website_sale_reorder_from_portal.js", "/website_sale/static/tests/tours/website_sale_restricted_editor_ui.js", "/website_sale/static/tests/tours/website_sale_shop_archived_variant_multi.js", "/website_sale/static/tests/tours/website_sale_shop_cart_recovery.js", "/website_sale/static/tests/tours/website_sale_shop_compare_list_price_pricelist.js", "/website_sale/static/tests/tours/website_sale_shop_custom_attribute_value.js", "/website_sale/static/tests/tours/website_sale_shop_customize.js", "/website_sale/static/tests/tours/website_sale_shop_deleted_archived_variants.js", "/website_sale/static/tests/tours/website_sale_shop_dynamic_variants.js", "/website_sale/static/tests/tours/website_sale_shop_editor_tour.js", "/website_sale/static/tests/tours/website_sale_shop_list_view_b2c.js", "/website_sale/static/tests/tours/website_sale_shop_mail.js", "/website_sale/static/tests/tours/website_sale_shop_no_variant_attribute.js", "/website_sale/static/tests/tours/website_sale_shop_pricelist_tour.js", "/website_sale/static/tests/tours/website_sale_shop_zoom.js", "/website_sale/static/tests/tours/website_sale_snippet_products.js", "/mrp/static/tests/tours/mrp_manual_consumption.js", "/hr/static/tests/tours/hr_employee_flow.js", "/hr/static/tests/tours/user_modify_own_profile_tour.js", "/hr_expense/static/tests/tours/expense_upload_tours.js", "/hr_holidays/static/tests/tours/time_off_request_calendar_view.js", "/event_sale/static/tests/tours/event_configurator_ui.js", "/website_event/static/tests/tours/website_event.js", "/website_event/static/tests/tours/website_event_pages_seo.js", "/pos_loyalty/static/src/tours/EWalletProgramTours.js", "/pos_loyalty/static/src/tours/GiftCardProgramTours.js", "/pos_loyalty/static/src/tours/MultipleGiftWalletProgramsTour.js", "/pos_loyalty/static/src/tours/PosLoyaltyLoyaltyProgramTour.js", "/pos_loyalty/static/src/tours/PosLoyaltyRewardButtonTour.js", "/pos_loyalty/static/src/tours/PosLoyaltyTour.js", "/pos_loyalty/static/src/tours/PosLoyaltyTourMethods.js", "/pos_loyalty/static/src/tours/PosLoyaltyValidityTour.js", "/website_profile/static/tests/tours/tour_website_profile_description.js", "/website_slides/static/tests/tours/slide_course_publisher_standard.js", "/website_slides/static/tests/tours/slides_course_member.js", "/website_slides/static/tests/tours/slides_course_member_yt.js", "/website_slides/static/tests/tours/slides_course_publisher.js", "/website_slides/static/tests/tours/slides_course_reviews.js", "/website_slides/static/tests/tours/slides_full_screen_web_editor.js", "/website_slides/static/tests/tours/slides_tour_tools.js", "/im_livechat/static/tests/tours/im_livechat_channel_creation_tour.js", "/im_livechat/static/tests/tours/im_livechat_chatbot_steps_sequence_tour.js", "/survey/static/tests/tours/certification_failure.js", "/survey/static/tests/tours/certification_success.js", "/survey/static/tests/tours/survey.js", "/survey/static/tests/tours/survey_chained_conditional_questions.js", "/survey/static/tests/tours/survey_prefill.js", "/hr_skills/static/tests/tours/skills_tour.js", "/website_hr_recruitment/static/tests/tours/website_hr_recruitment.js", "/pos_restaurant/static/tests/tours/ControlButtons.tour.js", "/pos_restaurant/static/tests/tours/FloorScreen.tour.js", "/pos_restaurant/static/tests/tours/SplitBillScreen.tour.js", "/pos_restaurant/static/tests/tours/TicketScreen.tour.js", "/pos_restaurant/static/tests/tours/TipScreen.tour.js", "/pos_restaurant/static/tests/tours/helpers/BillScreenTourMethods.js", "/pos_restaurant/static/tests/tours/helpers/ChromeTourMethods.js", "/pos_restaurant/static/tests/tours/helpers/FloorScreenTourMethods.js", "/pos_restaurant/static/tests/tours/helpers/ProductScreenTourMethods.js", "/pos_restaurant/static/tests/tours/helpers/SplitBillScreenTourMethods.js", "/pos_restaurant/static/tests/tours/helpers/TipScreenTourMethods.js", "/pos_restaurant/static/tests/tours/pos_restaurant.js", "/pos_restaurant/static/tests/tours/refund.tour.js", "/pos_sale/static/tests/helpers/ProductScreenTourMethods.js", "/pos_sale/static/tests/helpers/ReceiptScreenTourMethods.js", "/pos_sale/static/tests/tours/pos_sale_tours.js", "/pos_sale_loyalty/static/src/tours/PosSaleLoyaltyTour.js", "/website_crm/static/tests/tours/website_crm.js", "/auth_totp/static/tests/totp_flow.js", "/auth_totp_mail/static/tests/totp_flow.js", "/auth_totp_portal/static/tests/totp_portal.js", "/sale_timesheet/static/tests/tours/sale_timesheet_tour.js", "/pos_hr/static/tests/tours/PosHrTour.js", "/pos_hr/static/tests/tours/PosHrTourMethods.js", "/sign_oca/static/src/tests/sign_tour.esm.js", "/website_blog/static/tests/tours/blog_search_with_date.js", "/website_blog/static/tests/tours/blog_tags_tour.js", "/website_event_sale/static/tests/tours/helpers/WebsiteEventSaleTourMethods.js", "/website_event_sale/static/tests/tours/website_event_sale.js", "/website_event_sale/static/tests/tours/website_event_sale_last_ticket.js", "/website_event_sale/static/tests/tours/website_event_sale_pricelists.js", "/website_event_questions/static/tests/tours/tickets_questions.js", "/website_links/static/tests/tours/website_links.js", "/website_mass_mailing/static/tests/tours/newsletter_block.js", "/website_mass_mailing/static/tests/tours/newsletter_popup.js", "/website_sale_stock/static/tests/tours/website_sale_stock_multilang.js", "/website_sale_stock/static/tests/tours/website_sale_stock_stock_notification.js", "/website_sale_delivery/static/tests/tours/website_free_delivery.js", "/website_sale_loyalty/static/tests/tours/test_gift_card_tour.js", "/website_sale_loyalty/static/tests/tours/test_promo_main_tour.js", "/website_sale_loyalty_delivery/static/tests/tours/website_sale_delivery_gift_card.js", "/website_sale_product_configurator/static/tests/tours/website_sale_buy.js", "/website_sale_product_configurator/static/tests/tours/website_sale_preconfigured_variant_price.js", "/website_sale_product_configurator/static/tests/tours/website_sale_shop_custom_attributes_value.js", "/website_sale_product_configurator/static/tests/tours/website_sale_variants_modal_window.js", "/website_sale_stock_product_configurator/static/tests/tours/website_sale_stock_message_after_close_configurator_modal.js", "/website_sale_wishlist/static/tests/tours/website_sale_wishlist.js", "/website_sale_wishlist/static/tests/tours/website_sale_wishlist_admin.js", "/website_sale_stock_wishlist/static/tests/tours/website_sale_stock_wishlist_stock_notification.js", "/website_forum/static/tests/tours/website_forum_question.js"], "mappings": "AAAA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9CA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC9JA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3HA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC74BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChpBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1BA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACtBA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC1FA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC1CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;AChIA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACvDA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACzGA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnSA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC9EA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AClQA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACxGA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC5MA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC7BA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACvCA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACxEA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC/CA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC3PA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC3FA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC7aA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC/HA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACvCA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACvCA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACvCA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnMA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACzJA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACxZA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtCA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5PA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClCA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC/BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/BA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnNA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnLA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3RA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrCA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtBA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACxQA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtRA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpJA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AClEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1BA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC/BA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC9BA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACZA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC7PA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/WA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5MA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACrEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1FA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7HA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxOA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClDA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC7DA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AClIA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC7BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzBA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACdA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrCA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACn+BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChOA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACpBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjCA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACZA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3JA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC1EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClCA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACxDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpLA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACpFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrFA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AChGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjEA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACtYA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrBA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACrCA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC1EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClGA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACvFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/CA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC/BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtJA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACtDA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACrCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5EA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC5DA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACvCA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnCA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACtDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnBA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnFA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC/CA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC7EA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACjEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzIA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7MA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5PA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnbA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC1NA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpCA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC5BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3GA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChKA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClIA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC7GA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACpGA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACtEA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnEA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC3JA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnJA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC/JA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC9DA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACtHA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AChFA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC/DA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC/HA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACxDA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACjCA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC7JA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACvFA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACjEA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC5DA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC9HA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC7CA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACpFA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnBA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACzMA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrBA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACrFA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnVA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC3EA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC5HA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AChTA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACpFA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnEA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC7CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvDA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC5EA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACzFA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC5FA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACzDA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACxDA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACpFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjEA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACjFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChCA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACrDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1FA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;AC5GA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnBA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC/DA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnEA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC3DA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACvZA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACrFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5BA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA", "sourcesContent": ["/** @odoo-module **/\n\n\"use strict\";\n\nimport tour from \"web_tour.tour\";\n\nfunction checkLoginColumn(translation) {\n return [\n tour.stepUtils.showAppsMenuItem(), {\n content: \"Settings\",\n trigger: 'a[data-menu-xmlid=\"base.menu_administration\"]',\n run: 'click',\n }, {\n content: \"Open Users & Companies\",\n trigger: '[data-menu-xmlid=\"base.menu_users\"]',\n run: \"click\",\n }, {\n content: \"Open Users\",\n trigger: '[data-menu-xmlid=\"base.menu_action_res_users\"]',\n run: \"click\",\n }, {\n content: `Login column should be ${translation}`,\n trigger: `[data-name=\"login\"] span:contains(\"${translation}\")`,\n }\n ]\n}\n\ntour.register('ir_model_fields_translation_en_tour', {\n test: true,\n url: '/web',\n}, checkLoginColumn('Login'));\n\ntour.register('ir_model_fields_translation_en_tour2', {\n test: true,\n url: '/web',\n}, checkLoginColumn('Login2'));\n\ntour.register('ir_model_fields_translation_fr_tour', {\n test: true,\n url: '/web',\n}, checkLoginColumn('Identifiant'));\n\ntour.register('ir_model_fields_translation_fr_tour2', {\n test: true,\n url: '/web',\n}, checkLoginColumn('Identifiant2'));\n", "odoo.define('web.test_utils_file', function () {\n\"use strict\";\n\n/**\n * FILE Test Utils\n *\n * This module defines various utility functions to help simulate events with\n * files, such as drag-and-drop.\n *\n * Note that all methods defined in this module are exported in the main\n * testUtils file.\n */\n\n\n//------------------------------------------------------------------------------\n// Private functions\n//------------------------------------------------------------------------------\n\n/**\n * Create a fake object 'dataTransfer', linked to some files, which is passed to\n * drag and drop events.\n *\n * @param {Object[]} files\n * @returns {Object}\n */\nfunction _createFakeDataTransfer(files) {\n return {\n dropEffect: 'all',\n effectAllowed: 'all',\n files,\n getData: function () {\n return files;\n },\n items: [],\n types: ['Files'],\n };\n}\n\n//------------------------------------------------------------------------------\n// Public functions\n//------------------------------------------------------------------------------\n\n/**\n * Create a file object, which can be used for drag-and-drop.\n *\n * @param {Object} data\n * @param {string} data.name\n * @param {string} data.content\n * @param {string} data.contentType\n * @returns {Promise} resolved with file created\n */\nfunction createFile(data) {\n // Note: this is only supported by Chrome, and does not work in Incognito mode\n return new Promise(function (resolve, reject) {\n var requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;\n if (!requestFileSystem) {\n throw new Error('FileSystem API is not supported');\n }\n requestFileSystem(window.TEMPORARY, 1024 * 1024, function (fileSystem) {\n fileSystem.root.getFile(data.name, { create: true }, function (fileEntry) {\n fileEntry.createWriter(function (fileWriter) {\n fileWriter.onwriteend = function (e) {\n fileSystem.root.getFile(data.name, {}, function (fileEntry) {\n fileEntry.file(function (file) {\n resolve(file);\n });\n });\n };\n fileWriter.write(new Blob([ data.content ], { type: data.contentType }));\n });\n });\n });\n });\n}\n\n/**\n * Drag a file over a DOM element\n *\n * @param {$.Element} $el\n * @param {Object} file must have been created beforehand (@see createFile)\n */\nfunction dragoverFile($el, file) {\n var ev = new Event('dragover', { bubbles: true });\n Object.defineProperty(ev, 'dataTransfer', {\n value: _createFakeDataTransfer(file),\n });\n $el[0].dispatchEvent(ev);\n}\n\n/**\n * Drop a file on a DOM element.\n *\n * @param {$.Element} $el\n * @param {Object} file must have been created beforehand (@see createFile)\n */\nfunction dropFile($el, file) {\n var ev = new Event('drop', { bubbles: true, });\n Object.defineProperty(ev, 'dataTransfer', {\n value: _createFakeDataTransfer([file]),\n });\n $el[0].dispatchEvent(ev);\n}\n\n/**\n * Drop some files on a DOM element.\n *\n * @param {$.Element} $el\n * @param {Object[]} files must have been created beforehand (@see createFile)\n */\nfunction dropFiles($el, files) {\n var ev = new Event('drop', { bubbles: true, });\n Object.defineProperty(ev, 'dataTransfer', {\n value: _createFakeDataTransfer(files),\n });\n $el[0].dispatchEvent(ev);\n}\n\n/**\n * Set files in a file input\n *\n * @param {DOM.Element} el\n * @param {Object[]} files must have been created beforehand\n * @see testUtils.file.createFile\n */\nfunction inputFiles(el, files) {\n // could not use _createFakeDataTransfer as el.files assignation will only\n // work with a real FileList object.\n const dataTransfer = new window.DataTransfer();\n for (const file of files) {\n dataTransfer.items.add(file);\n }\n el.files = dataTransfer.files;\n /**\n * Changing files programatically is not supposed to trigger the event but\n * it does in Chrome versions before 73 (which is on runbot), so in that\n * case there is no need to make a manual dispatch, because it would lead to\n * the files being added twice.\n */\n const versionRaw = navigator.userAgent.match(/Chrom(e|ium)\\/([0-9]+)\\./);\n const chromeVersion = versionRaw ? parseInt(versionRaw[2], 10) : false;\n if (!chromeVersion || chromeVersion >= 73) {\n el.dispatchEvent(new Event('change'));\n }\n}\n\n//------------------------------------------------------------------------------\n// Exposed API\n//------------------------------------------------------------------------------\n\nreturn {\n createFile: createFile,\n dragoverFile: dragoverFile,\n dropFile: dropFile,\n dropFiles,\n inputFiles,\n};\n\n});\n", "/** @odoo-module **/\n\n// -----------------------------------------------------------------------------\n// Cleanup\n// -----------------------------------------------------------------------------\n\nconst cleanups = [];\n\n/**\n * Register a cleanup callback that will be executed whenever the current test\n * is done.\n *\n * - the cleanups will be executed in reverse order\n * - they will be executed even if the test fails/crashes\n *\n * @param {Function} callback\n */\nexport function registerCleanup(callback) {\n cleanups.push(callback);\n}\n\nif (window.QUnit) {\n QUnit.on(\"OdooAfterTestHook\", (info) => {\n if (QUnit.config.debug) {\n return;\n }\n let cleanup;\n // note that this calls the cleanup callbacks in reverse order!\n while ((cleanup = cleanups.pop())) {\n try {\n cleanup(info);\n } catch (error) {\n console.error(error);\n }\n }\n });\n\n // -----------------------------------------------------------------------------\n // Check leftovers\n // -----------------------------------------------------------------------------\n\n /**\n * List of elements tolerated in the body after a test. The property \"keep\"\n * prevents the element from being removed (typically: qunit suite elements).\n */\n const validElements = [\n // always in the body:\n { tagName: \"DIV\", attr: \"id\", value: \"qunit\", keep: true },\n { tagName: \"DIV\", attr: \"id\", value: \"qunit-fixture\", keep: true },\n // shouldn't be in the body after a test but are tolerated:\n { tagName: \"SCRIPT\", attr: \"id\", value: \"\" },\n { tagName: \"DIV\", attr: \"class\", value: \"o_notification_manager\" },\n { tagName: \"DIV\", attr: \"class\", value: \"tooltip fade bs-tooltip-auto\" },\n {\n tagName: \"DIV\",\n attr: \"class\",\n value: \"tooltip fade bs-tooltip-auto show\",\n },\n {\n tagName: \"DIV\",\n attr: \"class\",\n value: \"tooltip tooltip-field-info fade bs-tooltip-auto\",\n },\n {\n tagName: \"DIV\",\n attr: \"class\",\n value: \"tooltip tooltip-field-info fade bs-tooltip-auto show\",\n },\n { tagName: \"SPAN\", attr: \"class\", value: \"select2-hidden-accessible\" },\n\n // Due to a Document Kanban bug (already present in 12.0)\n { tagName: \"DIV\", attr: \"class\", value: \"ui-helper-hidden-accessible\" },\n {\n tagName: \"UL\",\n attr: \"class\",\n value: \"ui-menu ui-widget ui-widget-content ui-autocomplete ui-front\",\n },\n {\n tagName: \"UL\",\n attr: \"class\",\n value: \"ui-menu ui-widget ui-widget-content ui-autocomplete dropdown-menu ui-front\", // many2ones\n },\n ];\n\n /**\n * After each test, we check that there is no leftover in the DOM.\n *\n * Note: this event is not QUnit standard, we added it for this specific use case.\n * As a payload, an object with keys 'moduleName' and 'testName' is provided. It\n * is used to indicate the test that left elements in the DOM, when it happens.\n */\n QUnit.on(\"OdooAfterTestHook\", function (info) {\n if (QUnit.config.debug) {\n return;\n }\n const failed = info.testReport.getStatus() === \"failed\";\n const toRemove = [];\n // check for leftover elements in the body\n for (const bodyChild of document.body.children) {\n const tolerated = validElements.find(\n (e) => e.tagName === bodyChild.tagName && bodyChild.getAttribute(e.attr) === e.value\n );\n if (!failed && !tolerated) {\n QUnit.pushFailure(\n `Body still contains undesirable elements:\\n${bodyChild.outerHTML}`\n );\n }\n if (!tolerated || !tolerated.keep) {\n toRemove.push(bodyChild);\n }\n }\n // cleanup leftovers in #qunit-fixture\n const qunitFixture = document.getElementById(\"qunit-fixture\");\n if (qunitFixture.children.length) {\n toRemove.push(...qunitFixture.children);\n }\n // remove unwanted elements if not in debug\n for (const el of toRemove) {\n el.remove();\n }\n document.body.classList.remove(\"modal-open\");\n });\n}\n", "/** @odoo-module **/\n\nimport { browser } from \"@web/core/browser/browser\";\nimport { isMacOS } from \"@web/core/browser/feature_detection\";\nimport { download } from \"@web/core/network/download\";\nimport { Deferred } from \"@web/core/utils/concurrency\";\nimport { patch, unpatch } from \"@web/core/utils/patch\";\nimport { isVisible } from \"@web/core/utils/ui\";\nimport { registerCleanup } from \"./cleanup\";\nimport { templates } from \"@web/core/assets\";\n\nimport { App, onMounted, onPatched, useComponent } from \"@odoo/owl\";\n\n/**\n * Patch the native Date object\n *\n * Note that it will be automatically unpatched at the end of the test\n *\n * @param {number} [year]\n * @param {number} [month]\n * @param {number} [day]\n * @param {number} [hours]\n * @param {number} [minutes]\n * @param {number} [seconds]\n */\nexport function patchDate(year, month, day, hours, minutes, seconds) {\n var RealDate = window.Date;\n var actualDate = new RealDate();\n\n // By default, RealDate uses the browser offset, so we must replace it with the offset fixed in luxon.\n var fakeDate = new RealDate(year, month, day, hours, minutes, seconds);\n if (!(luxon.Settings.defaultZone instanceof luxon.FixedOffsetZone)) {\n throw new Error(\"luxon.Settings.defaultZone must be a FixedOffsetZone\");\n }\n const browserOffset = -fakeDate.getTimezoneOffset();\n const patchedOffset = luxon.Settings.defaultZone.offset();\n const offsetDiff = patchedOffset - browserOffset;\n const correctedMinutes = fakeDate.getMinutes() - offsetDiff;\n fakeDate.setMinutes(correctedMinutes);\n\n var timeInterval = actualDate.getTime() - fakeDate.getTime();\n\n // eslint-disable-next-line no-global-assign\n window.Date = (function (NativeDate) {\n function Date(Y, M, D, h, m, s, ms) {\n var length = arguments.length;\n let date;\n if (arguments.length > 0) {\n date =\n length == 1 && String(Y) === Y // isString(Y)\n ? // We explicitly pass it through parse:\n new NativeDate(Date.parse(Y))\n : // We have to manually make calls depending on argument\n // length here\n length >= 7\n ? new NativeDate(Y, M, D, h, m, s, ms)\n : length >= 6\n ? new NativeDate(Y, M, D, h, m, s)\n : length >= 5\n ? new NativeDate(Y, M, D, h, m)\n : length >= 4\n ? new NativeDate(Y, M, D, h)\n : length >= 3\n ? new NativeDate(Y, M, D)\n : length >= 2\n ? new NativeDate(Y, M)\n : length >= 1\n ? new NativeDate(Y)\n : new NativeDate();\n // Prevent mixups with unfixed Date object\n date.constructor = Date;\n return date;\n } else {\n date = new NativeDate();\n var time = date.getTime();\n time -= timeInterval;\n date.setTime(time);\n return date;\n }\n }\n\n // Copy any custom methods a 3rd party library may have added\n for (var key in NativeDate) {\n Date[key] = NativeDate[key];\n }\n\n // Copy \"native\" methods explicitly; they may be non-enumerable\n // exception: 'now' uses fake date as reference\n Date.now = function () {\n var date = new NativeDate();\n var time = date.getTime();\n time -= timeInterval;\n return time;\n };\n Date.UTC = NativeDate.UTC;\n Date.prototype = NativeDate.prototype;\n Date.prototype.constructor = Date;\n\n // Upgrade Date.parse to handle simplified ISO 8601 strings\n Date.parse = NativeDate.parse;\n return Date;\n })(Date);\n\n registerCleanup(() => {\n window.Date = RealDate;\n });\n}\n\n/**\n * Applies a fixed time zone to luxon based on an offset to the UTC time zone.\n *\n * @param {number} offset the number of minutes ahead or behind the UTC time zone\n * +120 => UTC+2\n * -120 => UTC-2\n */\nexport function patchTimeZone(offset) {\n const originalZone = luxon.Settings.defaultZone;\n luxon.Settings.defaultZone = new luxon.FixedOffsetZone.instance(offset);\n registerCleanup(() => {\n luxon.Settings.defaultZone = originalZone;\n });\n}\n\nlet nextId = 1;\n\n/**\n *\n * @param {Object} obj object to patch\n * @param {Object} patchValue the actual patch description\n * @param {{pure?: boolean}} [options]\n */\nexport function patchWithCleanup(obj, patchValue, options) {\n const patchName = `__test_patch_${nextId++}__`;\n patch(obj, patchName, patchValue, options);\n registerCleanup(() => {\n unpatch(obj, patchName);\n });\n}\n\n/**\n * @returns {HTMLElement}\n */\nexport function getFixture() {\n if (!window.QUnit) {\n return document;\n }\n if (QUnit.config.debug) {\n return document.body;\n } else {\n return document.querySelector(\"#qunit-fixture\");\n }\n}\n\nexport async function nextTick() {\n await new Promise((resolve) => window.requestAnimationFrame(resolve));\n await new Promise((resolve) => setTimeout(resolve));\n}\n\nexport function makeDeferred() {\n return new Deferred();\n}\n\nexport function findElement(el, selector) {\n let target = el;\n if (selector) {\n const els = el.querySelectorAll(selector);\n if (els.length === 0) {\n throw new Error(`No element found (selector: ${selector})`);\n }\n if (els.length > 1) {\n throw new Error(`Found ${els.length} elements, instead of 1 (selector: ${selector})`);\n }\n target = els[0];\n }\n return target;\n}\n\nfunction keyboardEventBubble(args) {\n return Object.assign({}, args, {\n bubbles: true,\n keyCode: args.which,\n cancelable: true,\n });\n}\n\nfunction mouseEventMapping(args) {\n return {\n clientX: args ? args.pageX : undefined,\n clientY: args ? args.pageY : undefined,\n ...args,\n bubbles: true,\n cancelable: true,\n view: window,\n };\n}\n\nfunction mouseEventNoBubble(args) {\n return {\n clientX: args ? args.pageX : undefined,\n clientY: args ? args.pageY : undefined,\n ...args,\n bubbles: false,\n cancelable: false,\n view: window,\n };\n}\n\nfunction touchEventMapping(args) {\n return {\n ...args,\n cancelable: true,\n bubbles: true,\n composed: true,\n view: window,\n rotation: 0.0,\n zoom: 1.0,\n touches: args.touches ? [...args.touches.map((e) => new Touch(e))] : undefined,\n };\n}\n\nfunction touchEventCancelMapping(args) {\n return {\n ...touchEventMapping(args),\n cancelable: false,\n };\n}\n\nfunction noBubble(args) {\n return Object.assign({}, args, { bubbles: false });\n}\n\nfunction onlyBubble(args) {\n return Object.assign({}, args, { bubbles: true });\n}\n\n// TriggerEvent constructor/args processor mapping\nconst EVENT_TYPES = {\n auxclick: { constructor: MouseEvent, processParameters: mouseEventMapping },\n click: { constructor: MouseEvent, processParameters: mouseEventMapping },\n contextmenu: {\n constructor: MouseEvent,\n processParameters: mouseEventMapping,\n },\n dblclick: { constructor: MouseEvent, processParameters: mouseEventMapping },\n mousedown: { constructor: MouseEvent, processParameters: mouseEventMapping },\n mouseup: { constructor: MouseEvent, processParameters: mouseEventMapping },\n mousemove: { constructor: MouseEvent, processParameters: mouseEventMapping },\n mouseenter: {\n constructor: MouseEvent,\n processParameters: mouseEventNoBubble,\n },\n mouseleave: {\n constructor: MouseEvent,\n processParameters: mouseEventNoBubble,\n },\n mouseover: { constructor: MouseEvent, processParameters: mouseEventMapping },\n mouseout: { constructor: MouseEvent, processParameters: mouseEventMapping },\n focus: { constructor: FocusEvent, processParameters: noBubble },\n focusin: { constructor: FocusEvent, processParameters: onlyBubble },\n blur: { constructor: FocusEvent, processParameters: noBubble },\n cut: { constructor: ClipboardEvent, processParameters: onlyBubble },\n copy: { constructor: ClipboardEvent, processParameters: onlyBubble },\n paste: { constructor: ClipboardEvent, processParameters: onlyBubble },\n keydown: {\n constructor: KeyboardEvent,\n processParameters: keyboardEventBubble,\n },\n keypress: {\n constructor: KeyboardEvent,\n processParameters: keyboardEventBubble,\n },\n keyup: { constructor: KeyboardEvent, processParameters: keyboardEventBubble },\n drag: { constructor: DragEvent, processParameters: onlyBubble },\n dragend: { constructor: DragEvent, processParameters: onlyBubble },\n dragenter: { constructor: DragEvent, processParameters: onlyBubble },\n dragstart: { constructor: DragEvent, processParameters: onlyBubble },\n dragleave: { constructor: DragEvent, processParameters: onlyBubble },\n dragover: { constructor: DragEvent, processParameters: onlyBubble },\n drop: { constructor: DragEvent, processParameters: onlyBubble },\n input: { constructor: InputEvent, processParameters: onlyBubble },\n compositionstart: {\n constructor: CompositionEvent,\n processParameters: onlyBubble,\n },\n compositionend: {\n constructor: CompositionEvent,\n processParameters: onlyBubble,\n },\n};\n\nif (typeof TouchEvent === \"function\") {\n Object.assign(EVENT_TYPES, {\n touchstart: {\n constructor: TouchEvent,\n processParameters: touchEventMapping,\n },\n touchend: { constructor: TouchEvent, processParameters: touchEventMapping },\n touchmove: {\n constructor: TouchEvent,\n processParameters: touchEventMapping,\n },\n touchcancel: {\n constructor: TouchEvent,\n processParameters: touchEventCancelMapping,\n },\n });\n}\n\nfunction _makeEvent(eventType, eventAttrs) {\n let event;\n if (eventType in EVENT_TYPES) {\n const { constructor, processParameters } = EVENT_TYPES[eventType];\n event = new constructor(eventType, processParameters(eventAttrs));\n } else {\n event = new Event(eventType, Object.assign({}, eventAttrs, { bubbles: true }));\n }\n return event;\n}\n\nexport function triggerEvent(el, selector, eventType, eventAttrs = {}, options = {}) {\n const event = _makeEvent(eventType, eventAttrs);\n const target = findElement(el, selector);\n if (!target) {\n throw new Error(`Can't find a target to trigger ${eventType} event`);\n }\n if (!options.skipVisibilityCheck) {\n if (!isVisible(target)) {\n throw new Error(`Called triggerEvent ${eventType} on invisible target`);\n }\n }\n target.dispatchEvent(event);\n if (!options.fast) {\n return nextTick().then(() => event);\n }\n return event;\n}\n\nexport async function triggerEvents(el, querySelector, events, options) {\n for (let e = 0; e < events.length; e++) {\n if (Array.isArray(events[e])) {\n triggerEvent(el, querySelector, events[e][0], events[e][1], options);\n } else {\n triggerEvent(el, querySelector, events[e], {}, options);\n }\n }\n await nextTick();\n}\n\n/**\n * Triggers a scroll event on the given target\n *\n * If the target cannot be scrolled or an axis has reached\n * the end of the scrollable area, the event can be transmitted\n * to its nearest parent until it can be triggered\n *\n * @param {HTMLElement} target target of the scroll event\n * @param {Object} coordinates\n * @param {Number} coordinates[left] coordinates to scroll horizontally\n * @param {Number} coordinates[top] coordinates to scroll vertically\n * @param {Boolean} canPropagate states if the scroll can propagate to a scrollable parent\n */\nexport async function triggerScroll(\n target,\n coordinates = { left: null, top: null },\n canPropagate = true\n) {\n const isScrollable =\n (target.scrollHeight > target.clientHeight && target.clientHeight > 0) ||\n (target.scrollWidth > target.clientWidth && target.clientWidth > 0);\n if (!isScrollable && !canPropagate) {\n return;\n }\n if (isScrollable) {\n const canScrollFrom = {\n left:\n coordinates.left > target.scrollLeft\n ? target.scrollLeft + target.clientWidth < target.scrollWidth\n : target.scrollLeft > 0,\n top:\n coordinates.top > target.scrollTop\n ? target.scrollTop + target.clientHeight < target.scrollHeight\n : target.scrollTop > 0,\n };\n const scrollCoordinates = {};\n Object.entries(coordinates).forEach(([key, value]) => {\n if (value !== null && canScrollFrom[key]) {\n scrollCoordinates[key] = value;\n delete coordinates[key];\n }\n });\n target.scrollTo(scrollCoordinates);\n target.dispatchEvent(new UIEvent(\"scroll\"));\n await nextTick();\n if (!canPropagate || !Object.entries(coordinates).length) {\n return;\n }\n }\n target.parentElement\n ? triggerScroll(target.parentElement, coordinates)\n : window.dispatchEvent(new UIEvent(\"scroll\"));\n await nextTick();\n}\n\nexport function click(el, selector, skipVisibilityCheck = false) {\n return triggerEvent(\n el,\n selector,\n \"click\",\n { bubbles: true, cancelable: true },\n { skipVisibilityCheck }\n );\n}\n\nexport function clickCreate(htmlElement) {\n if (htmlElement.querySelectorAll(\".o_form_button_create\").length) {\n return click(htmlElement, \".o_form_button_create\");\n } else if (htmlElement.querySelectorAll(\".o_list_button_create\").length) {\n return click(htmlElement, \".o_list_button_create\");\n } else {\n throw new Error(\"No edit button found to be clicked.\");\n }\n}\n\nexport function clickEdit(htmlElement) {\n if (htmlElement.querySelectorAll(\".o_list_button_edit\").length) {\n return click(htmlElement, \".o_list_button_edit\");\n } else {\n throw new Error(\"No edit button found to be clicked.\");\n }\n}\n\nexport async function clickSave(htmlElement) {\n if (htmlElement.querySelectorAll(\".o_form_status_indicator\").length) {\n await mouseEnter(htmlElement, \".o_form_status_indicator\");\n }\n if (htmlElement.querySelectorAll(\".o_form_button_save\").length) {\n return click(htmlElement, \".o_form_button_save\");\n } else if (htmlElement.querySelectorAll(\".o_list_button_save\").length) {\n return click(htmlElement, \".o_list_button_save\");\n } else {\n throw new Error(\"No save button found to be clicked.\");\n }\n}\n\nexport async function clickDiscard(htmlElement) {\n if (htmlElement.querySelectorAll(\".o_form_status_indicator\").length) {\n await mouseEnter(htmlElement, \".o_form_status_indicator\");\n }\n if (htmlElement.querySelectorAll(\".o_form_button_cancel\").length) {\n return click(htmlElement, \".o_form_button_cancel\");\n } else if (htmlElement.querySelectorAll(\".o_list_button_discard\").length) {\n return click(htmlElement, \".o_list_button_discard\");\n } else {\n throw new Error(\"No discard button found to be clicked.\");\n }\n}\n\n/**\n * Triggers a mouseenter event on the given target. If no\n * coordinates are given, the event is located by default\n * in the middle of the target to simplify the test process\n *\n * @param {HTMLElement} el\n * @param {string} selector\n * @param {Object} coordinates position of the mouseenter event\n */\nexport async function mouseEnter(el, selector, coordinates) {\n const target = el.querySelector(selector) || el;\n const atPos = coordinates || {\n clientX: target.getBoundingClientRect().left + target.getBoundingClientRect().width / 2,\n clientY: target.getBoundingClientRect().top + target.getBoundingClientRect().height / 2,\n };\n return triggerEvent(target, null, \"mouseenter\", atPos);\n}\n\nexport async function editInput(el, selector, value) {\n const input = findElement(el, selector);\n if (!(input instanceof HTMLInputElement || input instanceof HTMLTextAreaElement)) {\n throw new Error(\"Only 'input' and 'textarea' elements can be edited with 'editInput'.\");\n }\n if (\n ![\"text\", \"textarea\", \"email\", \"search\", \"color\", \"number\", \"file\", \"tel\"].includes(\n input.type\n )\n ) {\n throw new Error(`Type \"${input.type}\" not supported by 'editInput'.`);\n }\n\n const eventOpts = {};\n if (input.type === \"file\") {\n const files = Array.isArray(value) ? value : [value];\n const dataTransfer = new DataTransfer();\n for (const file of files) {\n if (!(file instanceof File)) {\n throw new Error(`File input value should be one or several File objects.`);\n }\n dataTransfer.items.add(file);\n }\n input.files = dataTransfer.files;\n eventOpts.skipVisibilityCheck = true;\n } else {\n input.value = value;\n }\n\n await triggerEvents(input, null, [\"input\", \"change\"], eventOpts);\n\n if (input.type === \"file\") {\n // Need to wait for the file to be loaded by the input\n await nextTick();\n await nextTick();\n }\n}\n\nexport function editSelect(el, selector, value) {\n const select = findElement(el, selector);\n if (select.tagName !== \"SELECT\") {\n throw new Error(\"Only select tag can be edited with selectInput.\");\n }\n select.value = value;\n return triggerEvent(select, null, \"change\");\n}\n\n/**\n * Triggers an hotkey properly disregarding the operating system.\n *\n * @param {string} hotkey\n * @param {boolean} addOverlayModParts\n * @param {KeyboardEventInit} eventAttrs\n * @returns {{ keydownEvent: KeyboardEvent, keyupEvent: KeyboardEvent }}\n */\nexport function triggerHotkey(hotkey, addOverlayModParts = false, eventAttrs = {}) {\n eventAttrs.key = hotkey.split(\"+\").pop();\n\n if (/shift/i.test(hotkey)) {\n eventAttrs.shiftKey = true;\n }\n\n if (/control/i.test(hotkey)) {\n if (isMacOS()) {\n eventAttrs.metaKey = true;\n } else {\n eventAttrs.ctrlKey = true;\n }\n }\n\n if (/alt/i.test(hotkey) || addOverlayModParts) {\n if (isMacOS()) {\n eventAttrs.ctrlKey = true;\n } else {\n eventAttrs.altKey = true;\n }\n }\n\n if (!(\"bubbles\" in eventAttrs)) {\n eventAttrs.bubbles = true;\n }\n\n const keydownEvent = new KeyboardEvent(\"keydown\", eventAttrs);\n const keyupEvent = new KeyboardEvent(\"keyup\", eventAttrs);\n document.activeElement.dispatchEvent(keydownEvent);\n document.activeElement.dispatchEvent(keyupEvent);\n return { keydownEvent, keyupEvent };\n}\n\nexport async function legacyExtraNextTick() {\n return nextTick();\n}\n\nexport function mockDownload(cb) {\n patchWithCleanup(download, { _download: cb });\n}\n\nexport const hushConsole = Object.create(null);\nfor (const propName of Object.keys(window.console)) {\n hushConsole[propName] = () => {};\n}\n\nexport function mockSendBeacon(mock) {\n patchWithCleanup(navigator, {\n sendBeacon: (url, blob) => {\n return mock(url, blob) !== false;\n },\n });\n}\n\nexport function mockTimeout() {\n const timeouts = new Map();\n let currentTime = 0;\n let id = 1;\n patchWithCleanup(browser, {\n setTimeout(fn, delay = 0) {\n timeouts.set(id, { fn, scheduledFor: delay + currentTime, id });\n return id++;\n },\n clearTimeout(id) {\n timeouts.delete(id);\n },\n });\n return {\n execRegisteredTimeouts() {\n for (const { fn } of timeouts.values()) {\n fn();\n }\n timeouts.clear();\n },\n async advanceTime(duration) {\n // wait here so all microtasktick scheduled in this frame can be\n // executed and possibly register their own timeout\n await nextTick();\n currentTime += duration;\n for (const { fn, scheduledFor, id } of timeouts.values()) {\n if (scheduledFor <= currentTime) {\n fn();\n timeouts.delete(id);\n }\n }\n // wait here to make sure owl can update the UI\n await nextTick();\n },\n };\n}\n\nexport function mockAnimationFrame() {\n const callbacks = new Map();\n let id = 1;\n patchWithCleanup(browser, {\n requestAnimationFrame(fn) {\n callbacks.set(id, fn);\n return id++;\n },\n cancelAnimationFrame(id) {\n callbacks.delete(id);\n },\n });\n return function execRegisteredCallbacks() {\n for (const fn of callbacks.values()) {\n fn();\n }\n callbacks.clear();\n };\n}\n\nexport async function mount(Comp, target, config = {}) {\n let { props, env } = config;\n env = env || {};\n const configuration = {\n env,\n templates,\n test: true,\n props,\n };\n if (env.services && \"localization\" in env.services) {\n configuration.translateFn = env._t;\n }\n const app = new App(Comp, configuration);\n registerCleanup(() => app.destroy());\n return app.mount(target);\n}\n\nexport function destroy(comp) {\n comp.__owl__.app.destroy();\n}\n\nexport function findChildren(comp, predicate = (e) => e) {\n const queue = [];\n [].unshift.apply(queue, Object.values(comp.__owl__.children));\n\n while (queue.length > 0) {\n const curNode = queue.pop();\n if (predicate(curNode)) {\n return curNode;\n }\n [].unshift.apply(queue, Object.values(curNode.component.__owl__.children));\n }\n}\n\n// partial replacement of t-ref on component\nexport function useChild() {\n const node = useComponent().__owl__;\n const setChild = () => {\n const componentNode = Object.values(node.children)[0];\n node.component.child = componentNode.component;\n };\n onMounted(setChild);\n onPatched(setChild);\n}\n\nconst lifeCycleHooks = [\n \"onError\",\n \"onMounted\",\n \"onPatched\",\n \"onRendered\",\n \"onWillDestroy\",\n \"onWillPatch\",\n \"onWillRender\",\n \"onWillStart\",\n \"onWillUnmount\",\n \"onWillUpdateProps\",\n];\nexport function useLogLifeCycle(logFn, name = \"\") {\n const component = owl.useComponent();\n let loggedName = `${component.constructor.name}`;\n if (name) {\n loggedName = `${component.constructor.name} ${name}`;\n }\n for (const hook of lifeCycleHooks) {\n owl[hook](() => {\n logFn(`${hook} ${loggedName}`);\n });\n }\n}\n\n/**\n * Returns the list of nodes containing n2 (included) that do not contain n1.\n *\n * @param {Node} n1\n * @param {Node} n2\n * @returns {Node[]}\n */\nfunction getDifferentParents(n1, n2) {\n const parents = [n2];\n while (parents[0].parentNode) {\n const parent = parents[0].parentNode;\n if (parent.contains(n1)) {\n break;\n }\n parents.unshift(parent);\n }\n return parents;\n}\n\n/**\n * Helper performing a drag and drop sequence.\n *\n * - the 'fromSelector' is used to determine the element on which the drag will\n * start;\n * - the 'toSelector' will determine the element on which the first one will be\n * dropped.\n *\n * The first element will be dragged by its center, and will be dropped on the\n * bottom-right inner pixel of the target element. This behavior covers both\n * cases of appending the first element to the end of a list (toSelector =\n * target list) or moving it at the position of another element, effectively\n * placing the first element before the second (toSelector = other element).\n *\n * A position can be given to drop the first element above, below, or on the\n * side of the second (default is inside, as specified above).\n *\n * Note that only the last event is awaited, since all the others are\n * considered to be synchronous.\n *\n * @param {Element|string} from\n * @param {Element|string} to\n * @param {string} [position] \"top\" | \"bottom\" | \"left\" | \"right\"\n * @returns {Promise}\n */\nexport async function dragAndDrop(from, to, position) {\n const dropFunction = drag(from, to, position);\n await dropFunction();\n}\n\n/**\n * Helper performing a drag.\n *\n * - the 'from' selector is used to determine the element on which the drag will\n * start;\n * - the 'to' selector will determine the element on which the dragged element will be\n * moved.\n *\n * Returns a drop function\n * @param {Element|string} from\n * @param {Element|string} to\n * @param {string} [position] \"top\" | \"bottom\" | \"left\" | \"right\"\n * @returns {function: Promise}\n */\nexport function drag(from, to, position) {\n const fixture = getFixture();\n from = from instanceof Element ? from : fixture.querySelector(from);\n to = to instanceof Element ? to : fixture.querySelector(to);\n\n // Mouse down on main target\n const fromRect = from.getBoundingClientRect();\n const toRect = to.getBoundingClientRect();\n triggerEvent(from, null, \"mousedown\", {\n clientX: fromRect.x + fromRect.width / 2,\n clientY: fromRect.y + fromRect.height / 2,\n });\n\n // Find target position\n const toPos = {\n clientX: toRect.x + toRect.width / 2,\n clientY: toRect.y + toRect.height / 2,\n };\n if (position && typeof position === \"object\") {\n // x and y coordinates start from the element's initial coordinates\n toPos.clientX += position.x || 0;\n toPos.clientY += position.y || 0;\n } else {\n switch (position) {\n case \"top\": {\n toPos.clientY = toRect.y - 1;\n break;\n }\n case \"bottom\": {\n toPos.clientY = toRect.y + toRect.height + 1;\n break;\n }\n case \"left\": {\n toPos.clientX = toRect.x - 1;\n break;\n }\n case \"right\": {\n toPos.clientX = toRect.x + toRect.width + 1;\n break;\n }\n }\n }\n\n // Move, enter and drop the element on the target\n triggerEvent(window, null, \"mousemove\", toPos);\n // \"mouseenter\" is fired on every parent of `to` that do not contain\n // `from` (typically: different parent lists).\n for (const target of getDifferentParents(from, to)) {\n triggerEvent(target, null, \"mouseenter\", toPos);\n }\n\n return function () {\n return drop(from, toPos);\n };\n}\n\nfunction drop(from, toPos) {\n return triggerEvent(from, null, \"mouseup\", toPos);\n}\n\nexport async function clickDropdown(target, fieldName) {\n const dropdownInput = target.querySelector(`[name='${fieldName}'] .dropdown input`);\n dropdownInput.focus();\n await nextTick();\n await click(dropdownInput);\n}\n\nexport async function clickOpenedDropdownItem(target, fieldName, itemContent) {\n const dropdowns = target.querySelectorAll(`[name='${fieldName}'] .dropdown .dropdown-menu`);\n if (dropdowns.length === 0) {\n throw new Error(`No dropdown found for field ${fieldName}`);\n } else if (dropdowns.length > 1) {\n throw new Error(`Found ${dropdowns.length} dropdowns for field ${fieldName}`);\n }\n const dropdownItems = dropdowns[0].querySelectorAll(\"li\");\n const indexToClick = Array.from(dropdownItems)\n .map((html) => html.textContent)\n .indexOf(itemContent);\n if (indexToClick === -1) {\n throw new Error(`The element '${itemContent}' does not exist in the dropdown`);\n }\n await click(dropdownItems[indexToClick], null, \"click\");\n}\n\nexport async function selectDropdownItem(target, fieldName, itemContent) {\n await clickDropdown(target, fieldName);\n await clickOpenedDropdownItem(target, fieldName, itemContent);\n}\n\nexport function getNodesTextContent(nodes) {\n return Array.from(nodes).map((n) => n.textContent);\n}\n\n/**\n * Click to open the dropdown on a many2one\n */\nexport async function clickOpenM2ODropdown(el, fieldName, selector) {\n const m2oSelector = `${selector || \"\"} .o_field_many2one[name=${fieldName}] input`;\n const matches = el.querySelectorAll(m2oSelector);\n if (matches.length !== 1) {\n throw new Error(\n `cannot open m2o: selector ${selector} has been found ${matches.length} instead of 1`\n );\n }\n\n await click(matches[0]);\n return matches[0];\n}\n\n/**\n * Click on the active (highlighted) selection in a m2o dropdown.\n */\n// TO FIX\nexport async function clickM2OHighlightedItem(el, fieldName, selector) {\n const m2oSelector = `${selector || \"\"} .o_field_many2one[name=${fieldName}] input`;\n // const $dropdown = $(m2oSelector).autocomplete('widget');\n const matches = el.querySelectorAll(m2oSelector);\n if (matches.length !== 1) {\n throw new Error(\n `cannot open m2o: selector ${selector} has been found ${matches.length} instead of 1`\n );\n }\n // clicking on an li (no matter which one), will select the focussed one\n return click(matches[0].parentElement.querySelector(\"li\"));\n}\n\n// X2Many\nexport async function addRow(target, selector) {\n await click(target.querySelector(`${selector ? selector : \"\"} .o_field_x2many_list_row_add a`));\n}\n\nexport async function removeRow(target, index) {\n await click(target.querySelectorAll(\".o_list_record_remove\")[index]);\n}\n", "/* @odoo-module */\n\nimport { isVisible } from \"@web/core/utils/ui\";\nimport { registerCleanup } from \"@web/../tests/helpers/cleanup\";\nimport {\n click as webClick,\n getFixture,\n makeDeferred,\n triggerEvents as webTriggerEvents,\n} from \"@web/../tests/helpers/utils\";\n\n/**\n * Create a file object, which can be used for drag-and-drop.\n *\n * @param {Object} data\n * @param {string} data.name\n * @param {string} data.content\n * @param {string} data.contentType\n * @returns {Promise} resolved with file created\n */\nexport function createFile(data) {\n // Note: this is only supported by Chrome, and does not work in Incognito mode\n return new Promise(function (resolve, reject) {\n var requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;\n if (!requestFileSystem) {\n throw new Error(\"FileSystem API is not supported\");\n }\n requestFileSystem(window.TEMPORARY, 1024 * 1024, function (fileSystem) {\n fileSystem.root.getFile(data.name, { create: true }, function (fileEntry) {\n fileEntry.createWriter(function (fileWriter) {\n fileWriter.onwriteend = function (e) {\n fileSystem.root.getFile(data.name, {}, function (fileEntry) {\n fileEntry.file(function (file) {\n resolve(file);\n });\n });\n };\n fileWriter.write(new Blob([data.content], { type: data.contentType }));\n });\n });\n });\n });\n}\n\n/**\n * Create a fake object 'dataTransfer', linked to some files,\n * which is passed to drag and drop events.\n *\n * @param {Object[]} files\n * @returns {Object}\n */\nfunction createFakeDataTransfer(files) {\n return {\n dropEffect: \"all\",\n effectAllowed: \"all\",\n files,\n items: [],\n types: [\"Files\"],\n };\n}\n\n/**\n * Waits until exactly one element matching the given `selector` is present in\n * `options.target` and then clicks on it.\n *\n * @param {string} selector\n * @param {ContainsOptions} [options] forwarded to `contains`\n * @param {boolean} [options.shiftKey]\n */\nexport async function click(selector, options = {}) {\n const { shiftKey } = options;\n delete options.shiftKey;\n await contains(selector, { click: { shiftKey }, ...options });\n}\n\n/**\n * Waits until exactly one element matching the given `selector` is present in\n * `options.target` and then dragenters `files` on it.\n *\n * @param {string} selector\n * @param {Object[]} files\n * @param {ContainsOptions} [options] forwarded to `contains`\n */\nexport async function dragenterFiles(selector, files, options) {\n await contains(selector, { dragenterFiles: files, ...options });\n}\n\n/**\n * Waits until exactly one element matching the given `selector` is present in\n * `options.target` and then dragovers `files` on it.\n *\n * @param {string} selector\n * @param {Object[]} files\n * @param {ContainsOptions} [options] forwarded to `contains`\n */\nexport async function dragoverFiles(selector, files, options) {\n await contains(selector, { dragoverFiles: files, ...options });\n}\n\n/**\n * Waits until exactly one element matching the given `selector` is present in\n * `options.target` and then drops `files` on it.\n *\n * @param {string} selector\n * @param {Object[]} files\n * @param {ContainsOptions} [options] forwarded to `contains`\n */\nexport async function dropFiles(selector, files, options) {\n await contains(selector, { dropFiles: files, ...options });\n}\n\n/**\n * Waits until exactly one element matching the given `selector` is present in\n * `options.target` and then inputs `files` on it.\n *\n * @param {string} selector\n * @param {Object[]} files\n * @param {ContainsOptions} [options] forwarded to `contains`\n */\nexport async function inputFiles(selector, files, options) {\n await contains(selector, { inputFiles: files, ...options });\n}\n\n/**\n * Waits until exactly one element matching the given `selector` is present in\n * `options.target` and then pastes `files` on it.\n *\n * @param {string} selector\n * @param {Object[]} files\n * @param {ContainsOptions} [options] forwarded to `contains`\n */\nexport async function pasteFiles(selector, files, options) {\n await contains(selector, { pasteFiles: files, ...options });\n}\n\n/**\n * Waits until exactly one element matching the given `selector` is present in\n * `options.target` and then focuses on it.\n *\n * @param {string} selector\n * @param {ContainsOptions} [options] forwarded to `contains`\n */\nexport async function focus(selector, options) {\n await contains(selector, { setFocus: true, ...options });\n}\n\n/**\n * Waits until exactly one element matching the given `selector` is present in\n * `options.target` and then inserts the given `content`.\n *\n * @param {string} selector\n * @param {string} content\n * @param {ContainsOptions} [options] forwarded to `contains`\n * @param {boolean} [options.replace=false]\n */\nexport async function insertText(selector, content, options = {}) {\n const { replace = false } = options;\n delete options.replace;\n await contains(selector, { ...options, insertText: { content, replace } });\n}\n\n/**\n * Waits until exactly one element matching the given `selector` is present in\n * `options.target` and then sets its `scrollTop` to the given value.\n *\n * @param {string} selector\n * @param {number|\"bottom\"} scrollTop\n * @param {ContainsOptions} [options] forwarded to `contains`\n */\nexport async function scroll(selector, scrollTop, options) {\n await contains(selector, { setScroll: scrollTop, ...options });\n}\n\n/**\n * Waits until exactly one element matching the given `selector` is present in\n * `options.target` and then triggers `event` on it.\n *\n * @param {string} selector\n * @param {(import(\"@web/../tests/helpers/utils\").EventType|[import(\"@web/../tests/helpers/utils\").EventType, EventInit])[]} events\n * @param {ContainsOptions} [options] forwarded to `contains`\n */\nexport async function triggerEvents(selector, events, options) {\n await contains(selector, { triggerEvents: events, ...options });\n}\n\nfunction log(ok, message) {\n if (window.QUnit) {\n QUnit.assert.ok(ok, message);\n } else {\n if (ok) {\n console.log(message);\n } else {\n console.error(message);\n }\n }\n}\n\nlet hasUsedContainsPositively = false;\nif (window.QUnit) {\n QUnit.testStart(() => (hasUsedContainsPositively = false));\n}\n/**\n * @typedef {[string, ContainsOptions]} ContainsTuple tuple representing params of the contains\n * function, where the first element is the selector, and the second element is the options param.\n * @typedef {Object} ContainsOptions\n * @property {ContainsTuple} [after] if provided, the found element(s) must be after the element\n * matched by this param.\n * @property {ContainsTuple} [before] if provided, the found element(s) must be before the element\n * matched by this param.\n * @property {Object} [click] if provided, clicks on the first found element\n * @property {ContainsTuple|ContainsTuple[]} [contains] if provided, the found element(s) must\n * contain the provided sub-elements.\n * @property {number} [count=1] numbers of elements to be found to declare the contains check\n * as successful. Elements are counted after applying all other filters.\n * @property {Object[]} [dragenterFiles] if provided, dragenters the given files on the found element\n * @property {Object[]} [dragoverFiles] if provided, dragovers the given files on the found element\n * @property {Object[]} [dropFiles] if provided, drops the given files on the found element\n * @property {Object[]} [inputFiles] if provided, inputs the given files on the found element\n * @property {{content:string, replace:boolean}} [insertText] if provided, adds to (or replace) the\n * value of the first found element by the given content.\n * @property {ContainsTuple} [parent] if provided, the found element(s) must have as\n * parent the node matching the parent parameter.\n * @property {Object[]} [pasteFiles] if provided, pastes the given files on the found element\n * @property {number|\"bottom\"} [scroll] if provided, the scrollTop of the found element(s)\n * must match.\n * Note: when using one of the scrollTop options, it is advised to ensure the height is not going\n * to change soon, by checking with a preceding contains that all the expected elements are in DOM.\n * @property {boolean} [setFocus] if provided, focuses the first found element.\n * @property {number|\"bottom\"} [setScroll] if provided, sets the scrollTop on the first found\n * element.\n * @property {HTMLElement} [target=getFixture()]\n * @property {string[]} [triggerEvents] if provided, triggers the given events on the found element\n * @property {string} [text] if provided, the textContent of the found element(s) or one of their\n * descendants must match. Use `textContent` option for a match on the found element(s) only.\n * @property {string} [textContent] if provided, the textContent of the found element(s) must match.\n * Prefer `text` option for a match on the found element(s) or any of their descendants, usually\n * allowing for a simpler and less specific selector.\n * @property {string} [value] if provided, the input value of the found element(s) must match.\n * Note: value changes are not observed directly, another mutation must happen to catch them.\n * @property {boolean} [visible] if provided, the found element(s) must be (in)visible\n */\nclass Contains {\n /**\n * @param {string} selector\n * @param {ContainsOptions} [options={}]\n */\n constructor(selector, options = {}) {\n this.selector = selector;\n this.options = options;\n this.options.count = this.options.count === undefined ? 1 : this.options.count;\n this.options.targetParam = this.options.target;\n this.options.target =\n this.options.target === undefined ? getFixture() : this.options.target;\n let selectorMessage = `${this.options.count} of \"${this.selector}\"`;\n if (this.options.visible !== undefined) {\n selectorMessage = `${selectorMessage} ${\n this.options.visible ? \"visible\" : \"invisible\"\n }`;\n }\n if (this.options.targetParam) {\n selectorMessage = `${selectorMessage} inside a specific target`;\n }\n if (this.options.parent) {\n selectorMessage = `${selectorMessage} inside a specific parent`;\n }\n if (this.options.contains) {\n selectorMessage = `${selectorMessage} with a specified sub-contains`;\n }\n if (this.options.text !== undefined) {\n selectorMessage = `${selectorMessage} with text \"${this.options.text}\"`;\n }\n if (this.options.textContent !== undefined) {\n selectorMessage = `${selectorMessage} with textContent \"${this.options.textContent}\"`;\n }\n if (this.options.value !== undefined) {\n selectorMessage = `${selectorMessage} with value \"${this.options.value}\"`;\n }\n if (this.options.scroll !== undefined) {\n selectorMessage = `${selectorMessage} with scroll \"${this.options.scroll}\"`;\n }\n if (this.options.after !== undefined) {\n selectorMessage = `${selectorMessage} after a specified element`;\n }\n if (this.options.before !== undefined) {\n selectorMessage = `${selectorMessage} before a specified element`;\n }\n this.selectorMessage = selectorMessage;\n if (this.options.contains && !Array.isArray(this.options.contains[0])) {\n this.options.contains = [this.options.contains];\n }\n if (this.options.count) {\n hasUsedContainsPositively = true;\n } else if (!hasUsedContainsPositively) {\n throw new Error(\n `Starting a test with \"contains\" of count 0 for selector \"${this.selector}\" is useless because it might immediately resolve. Start the test by checking that an expected element actually exists.`\n );\n }\n /** @type {string} */\n this.successMessage = undefined;\n /** @type {function} */\n this.executeError = undefined;\n }\n\n /**\n * Starts this contains check, either immediately resolving if there is a\n * match, or registering appropriate listeners and waiting until there is a\n * match or a timeout (resolving or rejecting respectively).\n *\n * Success or failure messages will be logged with QUnit as well.\n *\n * @returns {Promise}\n */\n run() {\n this.done = false;\n this.def = makeDeferred();\n this.scrollListeners = new Set();\n this.onScroll = () => this.runOnce(\"after scroll\");\n if (!this.runOnce(\"immediately\")) {\n this.timer = setTimeout(\n () => this.runOnce(\"Timeout of 5 seconds\", { crashOnFail: true }),\n 5000\n );\n this.observer = new MutationObserver((mutations) => {\n try {\n this.runOnce(\"after mutations\");\n } catch (e) {\n this.def.reject(e); // prevents infinite loop in case of programming error\n }\n });\n this.observer.observe(document.body, {\n attributes: true,\n childList: true,\n subtree: true,\n });\n registerCleanup(() => {\n if (!this.done) {\n this.runOnce(\"Test ended\", { crashOnFail: true });\n }\n });\n }\n return this.def;\n }\n\n /**\n * Runs this contains check once, immediately returning the result (or\n * undefined), and possibly resolving or rejecting the main promise\n * (and printing QUnit log) depending on options.\n * If undefined is returned it means the check was not successful.\n *\n * @param {string} whenMessage\n * @param {Object} [options={}]\n * @param {boolean} [options.crashOnFail=false]\n * @param {boolean} [options.executeOnSuccess=true]\n * @returns {HTMLElement[]|undefined}\n */\n runOnce(whenMessage, { crashOnFail = false, executeOnSuccess = true } = {}) {\n const res = this.select();\n if ((res && res.length === this.options.count) || crashOnFail) {\n // clean before doing anything else to avoid infinite loop due to side effects\n if (this.observer) {\n this.observer.disconnect();\n }\n clearTimeout(this.timer);\n for (const el of this.scrollListeners || []) {\n el.removeEventListener(\"scroll\", this.onScroll);\n }\n this.done = true;\n }\n if (res && res.length === this.options.count) {\n this.successMessage = `Found ${this.selectorMessage} (${whenMessage})`;\n if (executeOnSuccess) {\n this.executeAction(res[0]);\n }\n return res;\n } else {\n this.executeError = () => {\n let message = `Failed to find ${this.selectorMessage} (${whenMessage}).`;\n message = res\n ? `${message} Found ${res.length} instead.`\n : `${message} Parent not found.`;\n if (this.parentContains) {\n if (this.parentContains.successMessage) {\n log(true, this.parentContains.successMessage);\n } else {\n this.parentContains.executeError();\n }\n }\n log(false, message);\n if (this.def) {\n this.def.reject(new Error(message));\n }\n for (const childContains of this.childrenContains || []) {\n if (childContains.successMessage) {\n log(true, childContains.successMessage);\n } else {\n childContains.executeError();\n }\n }\n };\n if (crashOnFail) {\n this.executeError();\n }\n }\n }\n\n /**\n * Executes the action(s) given to this constructor on the found element,\n * prints the success messages, and resolves the main deferred.\n \n * @param {HTMLElement} el\n */\n executeAction(el) {\n let message = this.successMessage;\n if (this.options.click) {\n message = `${message} and clicked it`;\n webClick(el, undefined, {\n mouseEventInit: this.options.click,\n skipDisabledCheck: true,\n skipVisibilityCheck: true,\n });\n }\n if (this.options.dragenterFiles) {\n message = `${message} and dragentered ${this.options.dragenterFiles.length} file(s)`;\n const ev = new Event(\"dragenter\", { bubbles: true });\n Object.defineProperty(ev, \"dataTransfer\", {\n value: createFakeDataTransfer(this.options.dragenterFiles),\n });\n el.dispatchEvent(ev);\n }\n if (this.options.dragoverFiles) {\n message = `${message} and dragovered ${this.options.dragoverFiles.length} file(s)`;\n const ev = new Event(\"dragover\", { bubbles: true });\n Object.defineProperty(ev, \"dataTransfer\", {\n value: createFakeDataTransfer(this.options.dragoverFiles),\n });\n el.dispatchEvent(ev);\n }\n if (this.options.dropFiles) {\n message = `${message} and dropped ${this.options.dropFiles.length} file(s)`;\n const ev = new Event(\"drop\", { bubbles: true });\n Object.defineProperty(ev, \"dataTransfer\", {\n value: createFakeDataTransfer(this.options.dropFiles),\n });\n el.dispatchEvent(ev);\n }\n if (this.options.inputFiles) {\n message = `${message} and inputted ${this.options.inputFiles.length} file(s)`;\n // could not use _createFakeDataTransfer as el.files assignation will only\n // work with a real FileList object.\n const dataTransfer = new window.DataTransfer();\n for (const file of this.options.inputFiles) {\n dataTransfer.items.add(file);\n }\n el.files = dataTransfer.files;\n /**\n * Changing files programatically is not supposed to trigger the event but\n * it does in Chrome versions before 73 (which is on runbot), so in that\n * case there is no need to make a manual dispatch, because it would lead to\n * the files being added twice.\n */\n const versionRaw = navigator.userAgent.match(/Chrom(e|ium)\\/([0-9]+)\\./);\n const chromeVersion = versionRaw ? parseInt(versionRaw[2], 10) : false;\n if (!chromeVersion || chromeVersion >= 73) {\n el.dispatchEvent(new Event(\"change\"));\n }\n }\n if (this.options.insertText !== undefined) {\n message = `${message} and inserted text \"${this.options.insertText.content}\" (replace: ${this.options.insertText.replace})`;\n el.focus();\n if (this.options.insertText.replace) {\n el.value = \"\";\n el.dispatchEvent(new window.KeyboardEvent(\"keydown\", { key: \"Backspace\" }));\n el.dispatchEvent(new window.KeyboardEvent(\"keyup\", { key: \"Backspace\" }));\n el.dispatchEvent(new window.InputEvent(\"input\"));\n }\n for (const char of this.options.insertText.content) {\n el.value += char;\n el.dispatchEvent(new window.KeyboardEvent(\"keydown\", { key: char }));\n el.dispatchEvent(new window.KeyboardEvent(\"keyup\", { key: char }));\n el.dispatchEvent(new window.InputEvent(\"input\"));\n }\n }\n if (this.options.pasteFiles) {\n message = `${message} and pasted ${this.options.pasteFiles.length} file(s)`;\n const ev = new Event(\"paste\", { bubbles: true });\n Object.defineProperty(ev, \"clipboardData\", {\n value: createFakeDataTransfer(this.options.pasteFiles),\n });\n el.dispatchEvent(ev);\n }\n if (this.options.setFocus) {\n message = `${message} and focused it`;\n el.focus();\n }\n if (this.options.setScroll !== undefined) {\n message = `${message} and set scroll to \"${this.options.setScroll}\"`;\n el.scrollTop =\n this.options.setScroll === \"bottom\" ? el.scrollHeight : this.options.setScroll;\n }\n if (this.options.triggerEvents) {\n message = `${message} and triggered \"${this.options.triggerEvents.join(\", \")}\" events`;\n webTriggerEvents(el, null, this.options.triggerEvents, {\n skipVisibilityCheck: true,\n });\n }\n if (this.parentContains) {\n log(true, this.parentContains.successMessage);\n }\n log(true, message);\n for (const childContains of this.childrenContains) {\n log(true, childContains.successMessage);\n }\n if (this.def) {\n this.def.resolve();\n }\n }\n\n /**\n * Returns the found element(s) according to this constructor setup.\n * If undefined is returned it means the parent cannot be found\n *\n * @returns {HTMLElement[]|undefined}\n */\n select() {\n const target = this.selectParent();\n if (!target) {\n return;\n }\n const baseRes = [...target.querySelectorAll(this.selector)];\n /** @type {Contains[]} */\n this.childrenContains = [];\n const res = baseRes.filter((el, currentIndex) => {\n let condition =\n (this.options.textContent === undefined ||\n el.textContent.trim() === this.options.textContent) &&\n (this.options.value === undefined || el.value === this.options.value) &&\n (this.options.scroll === undefined ||\n (this.options.scroll === \"bottom\"\n ? Math.abs(el.scrollHeight - el.clientHeight - el.scrollTop) <= 1\n : Math.abs(el.scrollTop - this.options.scroll) <= 1));\n if (condition && this.options.text !== undefined) {\n if (\n el.textContent.trim() !== this.options.text &&\n [...el.querySelectorAll(\"*\")].every(\n (el) => el.textContent.trim() !== this.options.text\n )\n ) {\n condition = false;\n }\n }\n if (condition && this.options.contains) {\n for (const param of this.options.contains) {\n const childContains = new Contains(param[0], {\n ...param[1],\n target: el,\n });\n if (\n !childContains.runOnce(`as child of el ${currentIndex + 1})`, {\n executeOnSuccess: false,\n })\n ) {\n condition = false;\n }\n this.childrenContains.push(childContains);\n }\n }\n if (condition && this.options.visible !== undefined) {\n if (isVisible(el) !== this.options.visible) {\n condition = false;\n }\n }\n if (condition && this.options.after) {\n const afterContains = new Contains(this.options.after[0], {\n ...this.options.after[1],\n target,\n });\n const afterEl = afterContains.runOnce(`as \"after\"`, {\n executeOnSuccess: false,\n });\n if (\n !afterEl ||\n !afterEl[0] ||\n !(el.compareDocumentPosition(afterEl[0]) & Node.DOCUMENT_POSITION_PRECEDING)\n ) {\n condition = false;\n }\n this.childrenContains.push(afterContains);\n }\n if (condition && this.options.before) {\n const beforeContains = new Contains(this.options.before[0], {\n ...this.options.before[1],\n target,\n });\n const beforeEl = beforeContains.runOnce(`as \"before\"`, {\n executeOnSuccess: false,\n });\n if (\n !beforeEl ||\n !beforeEl[0] ||\n !(el.compareDocumentPosition(beforeEl[0]) & Node.DOCUMENT_POSITION_FOLLOWING)\n ) {\n condition = false;\n }\n this.childrenContains.push(beforeContains);\n }\n return condition;\n });\n if (\n this.options.scroll !== undefined &&\n this.scrollListeners &&\n baseRes.length === this.options.count &&\n res.length !== this.options.count\n ) {\n for (const el of baseRes) {\n if (!this.scrollListeners.has(el)) {\n this.scrollListeners.add(el);\n el.addEventListener(\"scroll\", this.onScroll);\n }\n }\n }\n return res;\n }\n\n /**\n * Returns the found element that should act as the target (parent) for the\n * main selector.\n * If undefined is returned it means the parent cannot be found.\n *\n * @returns {HTMLElement|undefined}\n */\n selectParent() {\n if (this.options.parent) {\n this.parentContains = new Contains(this.options.parent[0], {\n ...this.options.parent[1],\n target: this.options.target,\n });\n const res = this.parentContains.runOnce(`as parent`, {\n executeOnSuccess: false,\n });\n return res && res[0];\n }\n return this.options.target;\n }\n}\n\n/**\n * Waits until `count` elements matching the given `selector` are present in\n * `options.target`.\n *\n * @param {string} selector\n * @param {ContainsOptions} [options]\n * @returns {Promise}\n */\nexport async function contains(selector, options) {\n await new Contains(selector, options).run();\n}\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\n\ntour.register('mail/static/tests/tours/discuss_public_tour.js', {\n test: true,\n}, [{\n trigger: '.o_DiscussPublicView',\n extraTrigger: '.o_ThreadView',\n}, {\n content: \"Check that we are on channel page\",\n trigger: '.o_ThreadView',\n run() {\n if (!window.location.pathname.startsWith('/discuss/channel')) {\n console.error('Did not automatically redirect to channel page');\n }\n // Wait for modules to be loaded or failed for the next step\n odoo.__DEBUG__.didLogInfo.then(() => {\n const { missing, failed, unloaded } = odoo.__DEBUG__.jsModules;\n if ([missing, failed, unloaded].some(arr => arr.length)) {\n console.error(\"Couldn't load all JS modules.\", JSON.stringify({ missing, failed, unloaded }));\n }\n document.body.classList.add('o_mail_channel_public_modules_loaded');\n });\n },\n extraTrigger: '.o_mail_channel_public_modules_loaded',\n}, {\n content: \"Wait for all modules loaded check in previous step\",\n trigger: '.o_mail_channel_public_modules_loaded',\n}]);\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\n\ntour.register('mail/static/tests/tours/mail_channel_as_guest_tour.js', {\n test: true,\n}, [{\n content: \"Click join\",\n trigger: '.o_WelcomeView_joinButton',\n extraTrigger: '.o_ThreadView',\n}, {\n content: \"Check that we are on channel page\",\n trigger: '.o_ThreadView',\n run() {\n if (!window.location.pathname.startsWith('/discuss/channel')) {\n console.error('Clicking on join button did not redirect to channel page');\n }\n // Wait for modules to be loaded or failed for the next step\n odoo.__DEBUG__.didLogInfo.then(() => {\n const { missing, failed, unloaded } = odoo.__DEBUG__.jsModules;\n if ([missing, failed, unloaded].some(arr => arr.length)) {\n console.error(\"Couldn't load all JS modules.\", JSON.stringify({ missing, failed, unloaded }));\n }\n document.body.classList.add('o_mail_channel_as_guest_tour_modules_loaded');\n });\n },\n extraTrigger: '.o_mail_channel_as_guest_tour_modules_loaded',\n}, {\n content: \"Wait for all modules loaded check in previous step\",\n trigger: '.o_mail_channel_as_guest_tour_modules_loaded',\n}]);\n", "/** @odoo-module **/\n\nimport {\n createFile,\n inputFiles,\n} from 'web.test_utils_file';\nimport { contains } from '@web/../tests/utils';\n\nimport tour from 'web_tour.tour';\n\n/**\n * This tour depends on data created by python test in charge of launching it.\n * It is not intended to work when launched from interface. It is needed to test\n * an action (action manager) which is not possible to test with QUnit.\n * @see mail/tests/test_mail_full_composer.py\n */\ntour.register('mail/static/tests/tours/mail_full_composer_test_tour.js', {\n test: true,\n}, [{\n content: \"Wait for the chatter to be fully loaded\",\n trigger: \".o_Chatter\",\n async run() {\n await contains(\".o_Message\", { count: 1 });\n document.body.setAttribute(\"data-found-message\", 1);\n },\n}, {\n content: \"Click on Send Message\",\n trigger: '.o_ChatterTopbar_buttonSendMessage',\n extra_trigger: 'body[data-found-message=1]',\n}, {\n content: \"Write something in composer\",\n trigger: '.o_ComposerTextInput_textarea',\n run: 'text blahblah',\n}, {\n content: \"Add one file in composer\",\n trigger: '.o_Composer_buttonAttachment',\n async run() {\n const file = await createFile({\n content: 'hello, world',\n contentType: 'text/plain',\n name: 'text.txt',\n });\n const messaging = await odoo.__DEBUG__.messaging;\n const uploader = messaging.models['ComposerView'].all()[0].fileUploader;\n inputFiles(\n uploader.fileInput,\n [file]\n );\n },\n}, {\n content: \"Open full composer\",\n trigger: '.o_Composer_buttonFullComposer',\n extra_trigger: '.o_AttachmentCard:not(.o-isUploading)' // waiting the attachment to be uploaded\n}, {\n content: \"Check the earlier provided attachment is listed\",\n trigger: '.o_AttachmentCard[title=\"text.txt\"]',\n run() {},\n}, {\n content: \"Check subject is autofilled\",\n trigger: '[name=\"subject\"] input',\n run() {\n const subjectValue = document.querySelector('[name=\"subject\"] input').value;\n if (subjectValue !== \"Re: Jane\") {\n console.error(\n `Full composer should have \"Re: Jane\" in subject input (actual: ${subjectValue})`\n );\n }\n },\n}, {\n content: \"Check composer content is kept\",\n trigger: '.o_field_html[name=\"body\"]',\n run() {\n const bodyContent = document.querySelector('.o_field_html[name=\"body\"]').textContent;\n if (!bodyContent.includes(\"blahblah\")) {\n console.error(\n `Full composer should contain text from small composer (\"blahblah\") in body input (actual: ${bodyContent})`\n );\n }\n },\n}, {\n content: \"Open templates\",\n trigger: '.o_field_widget[name=\"template_id\"] input',\n}, {\n content: \"Check a template is listed\",\n in_modal: false,\n trigger: '.ui-autocomplete .ui-menu-item a:contains(\"Test template\")',\n run() {},\n}, {\n content: \"Send message\",\n trigger: '.o_mail_send',\n}, {\n content: \"Check message is shown\",\n trigger: '.o_Message:contains(\"blahblah\")',\n}, {\n content: \"Check message contains the attachment\",\n trigger: '.o_Message .o_AttachmentCard_filename:contains(\"text.txt\")',\n}]);\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\n\n/**\n * Verify that a user can modify their own profile information.\n */\ntour.register('mail/static/tests/tours/user_modify_own_profile_tour.js', {\n test: true,\n}, [{\n content: 'Open user account menu',\n trigger: '.o_user_menu button',\n}, {\n content: \"Open preferences / profile screen\",\n trigger: '[data-menu=settings]',\n}, {\n content: \"Update the email address\",\n trigger: 'div[name=\"email\"] input',\n run: 'text updatedemail@example.com',\n}, {\n content: \"Save the form\",\n trigger: 'button[name=\"preference_save\"]',\n}, {\n content: \"Wait until the modal is closed\",\n trigger: 'body:not(.modal-open)',\n}]);\n", "odoo.define('portal.tour', function (require) {\n'use strict';\n\nvar tour = require(\"web_tour.tour\");\n\ntour.register('portal_load_homepage', {\n test: true,\n url: '/my',\n},\n [\n {\n content: \"Check portal is loaded\",\n trigger: 'a[href*=\"/my/account\"]:contains(\"Edit\"):first',\n },\n {\n content: \"Load my account details\",\n trigger: 'input[value=\"Joel Willis\"]'\n }\n ]\n);\n\n});\n", "odoo.define('stock.reports.setup.tour', function (require) {\n \"use strict\";\n\n const tour = require('web_tour.tour');\n\n tour.register('test_stock_route_diagram_report', {\n test: true,\n }, [\n {\n trigger: '.o_kanban_record',\n extra_trigger:'.breadcrumb',\n },\n {\n trigger: '.nav-item > a:contains(\"Inventory\")',\n },\n {\n trigger: '.btn[id=\"stock.view_diagram_button\"]',\n },\n {\n trigger: 'iframe .o_report_stock_rule',\n },\n ]);\n\n tour.register(\"test_context_from_warehouse_filter\", { test: true }, [\n // Add \"foo\" to the warehouse context key\n {\n trigger: \".o_searchview_input\",\n run: \"click\",\n },\n {\n trigger: \".o_searchview_input\",\n run: \"text foo\",\n },\n {\n trigger: \".o_menu_item.dropdown-item:contains(Warehouse):contains(foo)\",\n run: \"click\",\n },\n // Add warehouse A's id to the warehouse context key\n {\n trigger: \".o_searchview_input\",\n run: \"click\",\n },\n {\n trigger: \".o_searchview_input\",\n run: \"text warehouse\",\n },\n {\n trigger: \".o_menu_item.dropdown-item:contains(Warehouse) a.o_expand > i\",\n run: \"click\",\n },\n {\n trigger: \".o_menu_item.dropdown-item.o_indent:contains(Warehouse A)\",\n run: () => {}, // wait until Warehouse A is rendered\n }, \n {\n trigger: \".o_menu_item.dropdown-item.o_indent:contains(Warehouse A) a\",\n run: \"click\",\n },\n // Add warehouse B's id to the warehouse context key\n {\n trigger: \".o_searchview_input\",\n run: \"text warehouse\",\n },\n {\n trigger: \".o_menu_item.dropdown-item:contains(Warehouse) a.o_expand > i\",\n run: \"click\",\n },\n {\n trigger: \".o_menu_item.dropdown-item.o_indent:contains(Warehouse B)\",\n run: () => {}, // wait until Warehouse B is rendered\n },\n {\n trigger: \".o_menu_item.dropdown-item.o_indent:contains(Warehouse B) a\",\n run: \"click\",\n },\n {\n content: \"Go to product page\",\n trigger: \".oe_kanban_card:has(.o_kanban_record_title span:contains(Lovely Product))\",\n run: \"click\",\n },\n {\n trigger: \"button[name=action_product_tmpl_forecast_report]\",\n run: \"click\",\n },\n {\n trigger: \".o_graph_view\",\n isCheck: true,\n },\n ]);\n});\n", "odoo.define('account.dashboard.setup.tour', function (require) {\n \"use strict\";\n\n var core = require('web.core');\n var tour = require('web_tour.tour');\n\n var _t = core._t;\n\n tour.register('account_render_report', {\n test: true,\n url: '/web',\n }, [tour.stepUtils.showAppsMenuItem(),\n {\n id: 'account_menu_click',\n trigger: '.o_app[data-menu-xmlid=\"account.menu_finance\"]',\n position: 'bottom',\n }, {\n trigger: '.o_data_row:first .o_data_cell',\n extra_trigger: '.breadcrumb',\n }, {\n trigger: '.o_control_panel button:contains(\"' + _t('Print') + '\")',\n }, {\n trigger: '.o_control_panel .o-dropdown--menu span:contains(\"' + _t('Invoices without Payment') + '\")',\n }, {\n trigger: 'iframe .o_report_layout_standard h2',\n content: 'Primary color is correct',\n run: function () {\n if (this.$anchor.css('color') !== \"rgb(18, 52, 86)\") {\n console.error('The primary color should be the one set on the company.');\n }\n },\n }, {\n trigger: 'iframe .o_report_layout_standard #informations div strong',\n content: 'Secondary color is correct',\n run: function () {\n if (this.$anchor.css('color') !== \"rgb(120, 145, 1)\") {\n console.error('The secondary color should be the one set on the company.');\n }\n },\n }\n ]);\n});\n", "/** @odoo-module alias=account.tax.group.tour.tests */\n\"use strict\";\n\nimport tour from 'web_tour.tour';\n\ntour.register('account_tax_group', {\n test: true,\n url: \"/web\",\n}, [tour.stepUtils.showAppsMenuItem(),\n {\n id: 'account_menu_click',\n content: \"Go to Invoicing\",\n trigger: '.o_app[data-menu-xmlid=\"account.menu_finance\"]',\n },\n {\n content: \"Go to Vendors\",\n trigger: 'span:contains(\"Vendors\")',\n },\n {\n content: \"Go to Bills\",\n trigger: 'a:contains(\"Bills\")',\n },\n {\n extra_trigger: '.breadcrumb:contains(\"Bills\")',\n content: \"Create new bill\",\n trigger: '.o_list_button_add',\n },\n // Set a vendor\n {\n content: \"Add vendor\",\n trigger: 'div.o_field_widget.o_field_res_partner_many2one[name=\"partner_id\"] div input',\n run: 'text Account Tax Group Partner',\n },\n {\n content: \"Valid vendor\",\n trigger: '.ui-menu-item a:contains(\"Account Tax Group Partner\")',\n },\n // Add First product\n {\n content: \"Add items\",\n trigger: 'div[name=\"invoice_line_ids\"] .o_field_x2many_list_row_add a:contains(\"Add a line\")',\n },\n {\n content: \"Select input\",\n trigger: 'div[name=\"invoice_line_ids\"] .o_selected_row .o_list_many2one[name=\"product_id\"] input',\n },\n {\n content: \"Type item\",\n trigger: 'div[name=\"invoice_line_ids\"] .o_selected_row .o_list_many2one[name=\"product_id\"] input',\n run: \"text Account Tax Group Product\",\n },\n {\n content: \"Valid item\",\n trigger: '.ui-menu-item-wrapper:contains(\"Account Tax Group Product\")',\n },\n // Save account.move\n {\n content: \"Save the account move\",\n trigger: '.o_form_button_save',\n },\n ...tour.stepUtils.saveForm(),\n // Edit tax group amount\n {\n content: \"Edit tax group amount\",\n trigger: '.o_tax_group_edit',\n },\n {\n content: \"Modify the input value\",\n trigger: '.o_tax_group_edit_input input',\n run: function (actions) {\n $('.o_tax_group_edit_input input').val(200);\n $('.o_tax_group_edit_input input').select();\n $('.o_tax_group_edit_input input').blur();\n },\n },\n // Check new value for total (with modified tax_group_amount).\n {\n content: \"Valid total amount\",\n trigger: 'span[name=\"amount_total\"]:contains(\"800\")',\n },\n // Modify the quantity of the object\n {\n content: \"Select item quantity\",\n trigger: 'div[name=\"invoice_line_ids\"] tbody tr.o_data_row .o_list_number[name=\"quantity\"]',\n },\n {\n content: \"Change item quantity\",\n trigger: 'div[name=\"invoice_line_ids\"] tbody tr.o_data_row .o_list_number[name=\"quantity\"] input',\n run: 'text 2',\n },\n {\n content: \"Valid the new value\",\n trigger: 'div[name=\"invoice_line_ids\"] tbody tr.o_data_row .o_list_number[name=\"quantity\"] input',\n run: function (actions) {\n let keydownEvent = jQuery.Event('keydown');\n keydownEvent.which = 13;\n this.$anchor.trigger(keydownEvent);\n },\n },\n // Save form\n {\n content: \"Save the account move\",\n trigger: '.o_form_button_save',\n },\n ...tour.stepUtils.saveForm(),\n // Check new tax group value\n {\n content: \"Check new value of tax group\",\n trigger: '.o_tax_group_amount_value:contains(\"120\")',\n },\n {\n content: \"Edit tax value\",\n trigger: '.o_tax_group_edit_input input',\n run: 'text 2'\n },\n {\n content: \"Check new value of total\",\n trigger: '.oe_subtotal_footer_separator:contains(\"1,202\")',\n },\n {\n content: \"Discard changes\",\n trigger: '.o_form_button_cancel',\n },\n {\n content: \"Check tax value is reset\",\n trigger: '.o_tax_group_amount_value:contains(\"120\")',\n },\n]);\n", "odoo.define('point_of_sale.tour.BarcodeScanning', function (require) {\n 'use strict';\n\n const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');\n const { getSteps, startSteps } = require('point_of_sale.tour.utils');\n const Tour = require('web_tour.tour');\n\n startSteps();\n\n\n // Add a product with its barcode\n ProductScreen.do.scan_barcode(\"0123456789\");\n ProductScreen.check.selectedOrderlineHas('Monitor Stand');\n ProductScreen.do.scan_barcode(\"0123456789\");\n ProductScreen.check.selectedOrderlineHas('Monitor Stand', 2);\n\n // Test \"Prices product\" EAN-13 `23.....{NNNDD}` barcode pattern\n ProductScreen.do.scan_ean13_barcode(\"2305000000004\");\n ProductScreen.check.selectedOrderlineHas('Magnetic Board', 1, \"0.00\");\n ProductScreen.do.scan_ean13_barcode(\"2305000123451\");\n ProductScreen.check.selectedOrderlineHas('Magnetic Board', 1, \"123.45\");\n\n // Test \"Weighted product\" EAN-13 `21.....{NNDDD}` barcode pattern\n ProductScreen.do.scan_ean13_barcode(\"2100005000000\");\n ProductScreen.check.selectedOrderlineHas('Wall Shelf Unit', 0, \"0.00\");\n ProductScreen.do.scan_ean13_barcode(\"2100005080002\");\n ProductScreen.check.selectedOrderlineHas('Wall Shelf Unit', 8);\n\n\n Tour.register('BarcodeScanningTour', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n\n // Add the Product 1 with GS1 barcode\n ProductScreen.do.scan_barcode(\"0108431673020125100000001\");\n ProductScreen.check.selectedOrderlineHas('Product 1');\n ProductScreen.do.scan_barcode(\"0108431673020125100000001\");\n ProductScreen.check.selectedOrderlineHas('Product 1', 2);\n\n // Add the Product 2 with normal barcode\n ProductScreen.do.scan_barcode(\"08431673020126\");\n ProductScreen.check.selectedOrderlineHas('Product 2');\n ProductScreen.do.scan_barcode(\"08431673020126\");\n ProductScreen.check.selectedOrderlineHas('Product 2', 2);\n\n // Add the Product 3 with normal barcode\n ProductScreen.do.scan_barcode(\"3760171283370\");\n ProductScreen.check.selectedOrderlineHas('Product 3');\n ProductScreen.do.scan_barcode(\"3760171283370\");\n ProductScreen.check.selectedOrderlineHas('Product 3', 2);\n\n Tour.register('GS1BarcodeScanningTour', { test: true, url: '/pos/ui' }, getSteps());\n});\n", "odoo.define('point_of_sale.tour.Chrome', function (require) {\n 'use strict';\n\n const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');\n const { ReceiptScreen } = require('point_of_sale.tour.ReceiptScreenTourMethods');\n const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');\n const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');\n const { Chrome } = require('point_of_sale.tour.ChromeTourMethods');\n const { getSteps, startSteps } = require('point_of_sale.tour.utils');\n var Tour = require('web_tour.tour');\n\n startSteps();\n\n // Order 1 is at Product Screen\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickHomeCategory();\n ProductScreen.exec.addOrderline('Desk Pad', '1', '2', '2.0');\n Chrome.do.clickTicketButton();\n TicketScreen.check.checkStatus('-0001', 'Ongoing');\n\n // Order 2 is at Payment Screen\n TicketScreen.do.clickNewTicket();\n ProductScreen.exec.addOrderline('Monitor Stand', '3', '4', '12.0');\n ProductScreen.do.clickPayButton();\n PaymentScreen.check.isShown();\n Chrome.do.clickTicketButton();\n TicketScreen.check.checkStatus('-0002', 'Payment');\n\n // Order 3 is at Receipt Screen\n TicketScreen.do.clickNewTicket();\n ProductScreen.exec.addOrderline('Whiteboard Pen', '5', '6', '30.0');\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.check.remainingIs('0.0');\n PaymentScreen.check.validateButtonIsHighlighted(true);\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.isShown();\n Chrome.do.clickTicketButton();\n TicketScreen.check.checkStatus('-0003', 'Receipt');\n\n // Select order 1, should be at Product Screen\n TicketScreen.do.selectOrder('-0001');\n ProductScreen.check.productIsDisplayed('Desk Pad');\n ProductScreen.check.selectedOrderlineHas('Desk Pad', '1.0', '2.0');\n\n // Select order 2, should be at Payment Screen\n Chrome.do.clickTicketButton();\n TicketScreen.do.selectOrder('-0002');\n PaymentScreen.check.emptyPaymentlines('12.0');\n PaymentScreen.check.validateButtonIsHighlighted(false);\n\n // Select order 3, should be at Receipt Screen\n Chrome.do.clickTicketButton();\n TicketScreen.do.selectOrder('-0003');\n ReceiptScreen.check.totalAmountContains('30.0');\n\n // Pay order 1, with change\n Chrome.do.clickTicketButton();\n TicketScreen.do.selectOrder('-0001');\n ProductScreen.check.isShown();\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Cash');\n PaymentScreen.do.pressNumpad('2 0');\n PaymentScreen.check.remainingIs('0.0');\n PaymentScreen.check.changeIs('18.0');\n PaymentScreen.check.validateButtonIsHighlighted(true);\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.totalAmountContains('2.0');\n\n // Order 1 now should have Receipt status\n Chrome.do.clickTicketButton();\n TicketScreen.check.checkStatus('-0001', 'Receipt');\n\n // Select order 3, should still be at Receipt Screen\n // and the total amount doesn't change.\n TicketScreen.do.selectOrder('-0003');\n ReceiptScreen.check.totalAmountContains('30.0');\n\n // click next screen on order 3\n // then delete the new empty order\n ReceiptScreen.do.clickNextOrder();\n ProductScreen.check.orderIsEmpty();\n Chrome.do.clickTicketButton();\n TicketScreen.do.deleteOrder('-0004');\n TicketScreen.do.deleteOrder('-0001');\n\n // After deleting order 1 above, order 2 became\n // the 2nd-row order and it has payment status\n TicketScreen.check.nthRowContains(2, 'Payment')\n TicketScreen.do.deleteOrder('-0002');\n Chrome.do.confirmPopup();\n TicketScreen.do.clickNewTicket();\n\n // Invoice an order\n ProductScreen.exec.addOrderline('Whiteboard Pen', '5', '6');\n ProductScreen.do.clickPartnerButton();\n ProductScreen.do.clickCustomer('Nicole Ford');\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.do.clickInvoiceButton();\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.isShown();\n\n Tour.register('ChromeTour', { test: true, url: '/pos/ui' }, getSteps());\n});\n", "odoo.define('point_of_sale.tour.PaymentScreen', function (require) {\n 'use strict';\n\n const { Chrome } = require('point_of_sale.tour.ChromeTourMethods');\n const { ErrorPopup } = require('point_of_sale.tour.ErrorPopupTourMethods');\n const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');\n const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');\n const { ReceiptScreen } = require('point_of_sale.tour.ReceiptScreenTourMethods');\n const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');\n const { getSteps, startSteps } = require('point_of_sale.tour.utils');\n var Tour = require('web_tour.tour');\n\n startSteps();\n\n ProductScreen.exec.addOrderline('Letter Tray', '10');\n ProductScreen.check.selectedOrderlineHas('Letter Tray', '10.0');\n ProductScreen.do.clickPayButton();\n PaymentScreen.check.emptyPaymentlines('52.8');\n\n PaymentScreen.do.clickPaymentMethod('Cash');\n PaymentScreen.do.pressNumpad('1 1');\n PaymentScreen.check.selectedPaymentlineHas('Cash', '11.00');\n PaymentScreen.check.remainingIs('41.8');\n PaymentScreen.check.changeIs('0.0');\n PaymentScreen.check.validateButtonIsHighlighted(false);\n // remove the selected paymentline with multiple backspace presses\n PaymentScreen.do.pressNumpad('Backspace Backspace');\n PaymentScreen.check.selectedPaymentlineHas('Cash', '0.00');\n PaymentScreen.do.pressNumpad('Backspace');\n PaymentScreen.check.emptyPaymentlines('52.8');\n\n // Pay with bank, the selected line should have full amount\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.check.remainingIs('0.0');\n PaymentScreen.check.changeIs('0.0');\n PaymentScreen.check.validateButtonIsHighlighted(true);\n // remove the line using the delete button\n PaymentScreen.do.clickPaymentlineDelButton('Bank', '52.8');\n\n // Use +10 and +50 to increment the amount of the paymentline\n PaymentScreen.do.clickPaymentMethod('Cash');\n PaymentScreen.do.pressNumpad('+10');\n PaymentScreen.check.remainingIs('42.8');\n PaymentScreen.check.changeIs('0.0');\n PaymentScreen.check.validateButtonIsHighlighted(false);\n PaymentScreen.do.pressNumpad('+50');\n PaymentScreen.check.remainingIs('0.0');\n PaymentScreen.check.changeIs('7.2');\n PaymentScreen.check.validateButtonIsHighlighted(true);\n PaymentScreen.do.clickPaymentlineDelButton('Cash', '60.0');\n\n // Multiple paymentlines\n PaymentScreen.do.clickPaymentMethod('Cash');\n PaymentScreen.do.pressNumpad('1');\n PaymentScreen.check.remainingIs('51.8');\n PaymentScreen.check.changeIs('0.0');\n PaymentScreen.check.validateButtonIsHighlighted(false);\n PaymentScreen.do.clickPaymentMethod('Cash');\n PaymentScreen.do.pressNumpad('5');\n PaymentScreen.check.remainingIs('46.8');\n PaymentScreen.check.changeIs('0.0');\n PaymentScreen.check.validateButtonIsHighlighted(false);\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.do.pressNumpad('2 0');\n PaymentScreen.check.remainingIs('26.8');\n PaymentScreen.check.changeIs('0.0');\n PaymentScreen.check.validateButtonIsHighlighted(false);\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.check.remainingIs('0.0');\n PaymentScreen.check.changeIs('0.0');\n PaymentScreen.check.validateButtonIsHighlighted(true);\n\n Tour.register('PaymentScreenTour', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.clickHomeCategory();\n ProductScreen.exec.addOrderline('Letter Tray', '1', '10');\n ProductScreen.do.clickPayButton();\n\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.do.pressNumpad('1 0 0 0');\n\n PaymentScreen.check.remainingIs('0.0');\n PaymentScreen.check.changeIs('0.0');\n\n Tour.register('PaymentScreenTour2', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.clickHomeCategory();\n ProductScreen.exec.addOrderline('Product Test', '1');\n ProductScreen.do.clickPayButton();\n\n PaymentScreen.check.totalIs('2.00');\n PaymentScreen.do.clickPaymentMethod('Cash');\n\n PaymentScreen.check.remainingIs('0.0');\n PaymentScreen.check.changeIs('0.0');\n\n Chrome.do.clickTicketButton();\n TicketScreen.do.clickNewTicket();\n\n ProductScreen.exec.addOrderline('Product Test', '-1');\n ProductScreen.do.clickPayButton();\n\n PaymentScreen.check.totalIs('-2.00');\n PaymentScreen.do.clickPaymentMethod('Cash');\n\n PaymentScreen.check.remainingIs('0.0');\n PaymentScreen.check.changeIs('0.0');\n\n Tour.register('PaymentScreenRoundingUp', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.clickHomeCategory();\n ProductScreen.exec.addOrderline('Product Test', '1');\n ProductScreen.do.clickPayButton();\n\n PaymentScreen.check.totalIs('1.95');\n PaymentScreen.do.clickPaymentMethod('Cash');\n\n PaymentScreen.check.remainingIs('0.0');\n PaymentScreen.check.changeIs('0.0');\n\n Chrome.do.clickTicketButton();\n TicketScreen.do.clickNewTicket();\n\n ProductScreen.exec.addOrderline('Product Test', '-1');\n ProductScreen.do.clickPayButton();\n\n PaymentScreen.check.totalIs('-1.95');\n PaymentScreen.do.clickPaymentMethod('Cash');\n\n PaymentScreen.check.remainingIs('0.0');\n PaymentScreen.check.changeIs('0.0');\n\n Tour.register('PaymentScreenRoundingDown', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.clickHomeCategory();\n ProductScreen.exec.addOrderline('Product Test 1.2', '1');\n ProductScreen.do.clickPayButton();\n\n PaymentScreen.check.totalIs('1.00');\n PaymentScreen.do.clickPaymentMethod('Cash');\n\n PaymentScreen.check.remainingIs('0.0');\n PaymentScreen.check.changeIs('0.0');\n\n Chrome.do.clickTicketButton();\n TicketScreen.do.clickNewTicket();\n\n ProductScreen.exec.addOrderline('Product Test 1.25', '1');\n ProductScreen.do.clickPayButton();\n\n PaymentScreen.check.totalIs('1.5');\n PaymentScreen.do.clickPaymentMethod('Cash');\n\n PaymentScreen.check.remainingIs('0.0');\n PaymentScreen.check.changeIs('0.0');\n\n Chrome.do.clickTicketButton();\n TicketScreen.do.clickNewTicket();\n\n ProductScreen.exec.addOrderline('Product Test 1.4', '1');\n ProductScreen.do.clickPayButton();\n\n PaymentScreen.check.totalIs('1.5');\n PaymentScreen.do.clickPaymentMethod('Cash');\n\n PaymentScreen.check.remainingIs('0.0');\n PaymentScreen.check.changeIs('0.0');\n\n Chrome.do.clickTicketButton();\n TicketScreen.do.clickNewTicket();\n\n ProductScreen.exec.addOrderline('Product Test 1.2', '1');\n ProductScreen.do.clickPayButton();\n\n PaymentScreen.check.totalIs('1.00');\n PaymentScreen.do.clickPaymentMethod('Cash');\n PaymentScreen.do.pressNumpad('2');\n\n PaymentScreen.check.remainingIs('0.0');\n PaymentScreen.check.changeIs('1.0');\n\n Tour.register('PaymentScreenRoundingHalfUp', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickHomeCategory();\n ProductScreen.exec.addOrderline('Product Test 40', '1');\n ProductScreen.do.clickPartnerButton();\n ProductScreen.do.clickCustomer('Nicole Ford');\n ProductScreen.do.clickPayButton();\n\n PaymentScreen.check.totalIs('40.00');\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.do.pressNumpad('3 8');\n PaymentScreen.check.remainingIs('2.0');\n PaymentScreen.do.clickPaymentMethod('Cash');\n\n PaymentScreen.check.remainingIs('0.0');\n PaymentScreen.check.changeIs('0.0');\n\n PaymentScreen.do.clickInvoiceButton();\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.receiptIsThere();\n ReceiptScreen.do.clickNextOrder();\n\n ProductScreen.do.clickHomeCategory();\n ProductScreen.exec.addOrderline('Product Test 41', '1');\n ProductScreen.do.clickPartnerButton();\n ProductScreen.do.clickCustomer('Nicole Ford');\n ProductScreen.do.clickPayButton();\n\n PaymentScreen.check.totalIs('41.00');\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.do.pressNumpad('3 8');\n PaymentScreen.check.remainingIs('3.0');\n PaymentScreen.do.clickPaymentMethod('Cash');\n\n PaymentScreen.check.remainingIs('0.0');\n PaymentScreen.check.changeIs('0.0');\n\n PaymentScreen.do.clickInvoiceButton();\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.receiptIsThere();\n\n Tour.register('PaymentScreenRoundingHalfUpCashAndBank', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickHomeCategory();\n ProductScreen.exec.addOrderline('Product Test', '1');\n ProductScreen.do.clickPayButton();\n\n PaymentScreen.check.totalIs('1.95');\n PaymentScreen.do.clickPaymentMethod('Cash');\n PaymentScreen.do.pressNumpad('5');\n\n PaymentScreen.check.remainingIs('0.0');\n PaymentScreen.check.changeIs('3.05');\n PaymentScreen.check.totalDueIs('1.95');\n\n Tour.register('PaymentScreenTotalDueWithOverPayment', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickHomeCategory();\n ProductScreen.exec.addOrderline('Magnetic Board', '1');\n ProductScreen.do.clickPayButton();\n\n // Check the popup error is shown when selecting another payment method\n PaymentScreen.check.totalIs('1.90');\n PaymentScreen.do.clickPaymentMethod('Cash');\n PaymentScreen.do.pressNumpad('1 .');\n PaymentScreen.check.selectedPaymentlineHas('Cash', '1.00');\n PaymentScreen.do.pressNumpad('2 4');\n PaymentScreen.check.selectedPaymentlineHas('Cash', '1.24');\n PaymentScreen.do.clickPaymentMethod('Bank');\n ErrorPopup.check.isShown();\n ErrorPopup.check.messageBodyContains( // Verify the value displayed are as expected\n 'The rounding precision is 0.10 so you should set 1.20 or 1.30 as payment amount instead of 1.24.'\n );\n\n Tour.register('CashRoundingPayment', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.exec.addOrderline('Letter Tray', '5');\n ProductScreen.check.selectedOrderlineHas('Letter Tray', '5.0');\n ProductScreen.do.clickPartnerButton();\n ProductScreen.do.clickCustomer('Nicole Ford');\n ProductScreen.do.clickPayButton();\n\n PaymentScreen.do.clickPaymentMethod('New Cash');\n PaymentScreen.do.pressNumpad('5 5');\n PaymentScreen.check.selectedPaymentlineHas('New Cash', '55');\n PaymentScreen.do.clickInvoiceButton();\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.receiptIsThere();\n\n Tour.register('MultipleCashPaymentMethod', { test: true, url: '/pos/ui' }, getSteps());\n});\n", "odoo.define('point_of_sale.tour.ProductConfigurator', function (require) {\n 'use strict';\n\n const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');\n const { ProductConfigurator } = require('point_of_sale.tour.ProductConfiguratorTourMethods');\n const { getSteps, startSteps } = require('point_of_sale.tour.utils');\n var Tour = require('web_tour.tour');\n\n // signal to start generating steps\n // when finished, steps can be taken from getSteps\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n // Go by default to home category\n ProductScreen.do.clickHomeCategory();\n\n // Click on Configurable Chair product\n ProductScreen.do.clickDisplayedProduct('Configurable Chair');\n ProductConfigurator.check.isShown();\n\n // Cancel configuration, not product should be in order\n ProductConfigurator.do.cancelAttributes();\n ProductScreen.check.orderIsEmpty();\n\n // Click on Configurable Chair product\n ProductScreen.do.clickDisplayedProduct('Configurable Chair');\n ProductConfigurator.check.isShown();\n\n // Pick Color\n ProductConfigurator.do.pickColor('Red');\n\n // Pick Radio\n ProductConfigurator.do.pickSelect('Metal');\n\n // Pick Select\n ProductConfigurator.do.pickRadio('Other');\n\n // Fill in custom attribute\n ProductConfigurator.do.fillCustomAttribute('Custom Fabric');\n\n // Confirm configuration\n ProductConfigurator.do.confirmAttributes();\n\n // Check that the product has been added to the order with correct attributes and price\n ProductScreen.check.selectedOrderlineHas('Configurable Chair (Red, Metal, Other: Custom Fabric)', '1.0', '11.0');\n\n // Orderlines with the same attributes should be merged\n ProductScreen.do.clickHomeCategory();\n ProductScreen.do.clickDisplayedProduct('Configurable Chair');\n ProductConfigurator.do.pickColor('Red');\n ProductConfigurator.do.pickSelect('Metal');\n ProductConfigurator.do.pickRadio('Other');\n ProductConfigurator.do.fillCustomAttribute('Custom Fabric');\n ProductConfigurator.do.confirmAttributes();\n ProductScreen.check.selectedOrderlineHas('Configurable Chair (Red, Metal, Other: Custom Fabric)', '2.0', '22.0');\n\n // Orderlines with different attributes shouldn't be merged\n ProductScreen.do.clickHomeCategory();\n ProductScreen.do.clickDisplayedProduct('Configurable Chair');\n ProductConfigurator.do.pickColor('Blue');\n ProductConfigurator.do.pickSelect('Metal');\n ProductConfigurator.do.pickRadio('Leather');\n ProductConfigurator.do.confirmAttributes();\n ProductScreen.check.selectedOrderlineHas('Configurable Chair (Blue, Metal, Leather)', '1.0', '10.0');\n\n Tour.register('ProductConfiguratorTour', { test: true, url: '/pos/ui' }, getSteps());\n\n\n startSteps();\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickHomeCategory();\n ProductScreen.do.clickDisplayedProduct('Configurable Chair');\n ProductConfigurator.check.isShown();\n // Option Other is active, Leather is not -> only 1 option available\n ProductConfigurator.check.numberRadioOptions(1);\n\n Tour.register('InactiveAttributeValueTour', { test: true, url: '/pos/ui' }, getSteps());\n});\n", "odoo.define('point_of_sale.tour.ProductScreen', function (require) {\n 'use strict';\n\n const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');\n const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');\n const { ReceiptScreen } = require('point_of_sale.tour.ReceiptScreenTourMethods');\n const { TextAreaPopup } = require('point_of_sale.tour.TextAreaPopupTourMethods');\n const { getSteps, startSteps } = require('point_of_sale.tour.utils');\n var Tour = require('web_tour.tour');\n\n // signal to start generating steps\n // when finished, steps can be taken from getSteps\n startSteps();\n\n // Go by default to home category\n ProductScreen.do.clickHomeCategory();\n\n // Clicking product multiple times should increment quantity\n ProductScreen.do.clickDisplayedProduct('Desk Organizer');\n ProductScreen.check.selectedOrderlineHas('Desk Organizer', '1.0', '5.10');\n ProductScreen.do.clickDisplayedProduct('Desk Organizer');\n ProductScreen.check.selectedOrderlineHas('Desk Organizer', '2.0', '10.20');\n\n // Clicking product should add new orderline and select the orderline\n // If orderline exists, increment the quantity\n ProductScreen.do.clickDisplayedProduct('Letter Tray');\n ProductScreen.check.selectedOrderlineHas('Letter Tray', '1.0', '5.28');\n ProductScreen.do.clickDisplayedProduct('Desk Organizer');\n ProductScreen.check.selectedOrderlineHas('Desk Organizer', '3.0', '15.30');\n\n // Check effects of clicking numpad buttons\n ProductScreen.do.clickOrderline('Letter Tray', '1');\n ProductScreen.check.selectedOrderlineHas('Letter Tray', '1.0');\n ProductScreen.do.pressNumpad('Backspace');\n ProductScreen.check.selectedOrderlineHas('Letter Tray', '0.0', '0.0');\n ProductScreen.do.pressNumpad('Backspace');\n ProductScreen.check.selectedOrderlineHas('Desk Organizer', '3', '15.30');\n ProductScreen.do.pressNumpad('Backspace');\n ProductScreen.check.selectedOrderlineHas('Desk Organizer', '0.0', '0.0');\n ProductScreen.do.pressNumpad('1');\n ProductScreen.check.selectedOrderlineHas('Desk Organizer', '1.0', '5.1');\n ProductScreen.do.pressNumpad('2');\n ProductScreen.check.selectedOrderlineHas('Desk Organizer', '12.0', '61.2');\n ProductScreen.do.pressNumpad('3');\n ProductScreen.check.selectedOrderlineHas('Desk Organizer', '123.0', '627.3');\n ProductScreen.do.pressNumpad('. 5');\n ProductScreen.check.selectedOrderlineHas('Desk Organizer', '123.5', '629.85');\n ProductScreen.do.pressNumpad('Price');\n ProductScreen.do.pressNumpad('1');\n ProductScreen.check.selectedOrderlineHas('Desk Organizer', '123.5', '123.5');\n ProductScreen.do.pressNumpad('1 .');\n ProductScreen.check.selectedOrderlineHas('Desk Organizer', '123.5', '1,358.5');\n ProductScreen.do.pressNumpad('Disc');\n ProductScreen.do.pressNumpad('5 .');\n ProductScreen.check.selectedOrderlineHas('Desk Organizer', '123.5', '1,290.58');\n ProductScreen.do.pressNumpad('Qty');\n ProductScreen.do.pressNumpad('Backspace');\n ProductScreen.do.pressNumpad('Backspace');\n ProductScreen.check.orderIsEmpty();\n\n // Check different subcategories\n ProductScreen.do.clickSubcategory('Desks');\n ProductScreen.check.productIsDisplayed('Desk Pad');\n ProductScreen.do.clickHomeCategory();\n ProductScreen.do.clickSubcategory('Miscellaneous');\n ProductScreen.check.productIsDisplayed('Whiteboard Pen');\n ProductScreen.do.clickHomeCategory();\n ProductScreen.do.clickSubcategory('Chairs');\n ProductScreen.check.productIsDisplayed('Letter Tray');\n ProductScreen.do.clickHomeCategory();\n \n // Add two orderlines and update quantity\n ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');\n ProductScreen.do.clickDisplayedProduct('Wall Shelf Unit');\n ProductScreen.do.clickOrderline('Whiteboard Pen', '1.0');\n ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '1.0');\n ProductScreen.do.pressNumpad('2');\n ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '2.0');\n ProductScreen.do.clickOrderline('Wall Shelf Unit', '1.0');\n ProductScreen.check.selectedOrderlineHas('Wall Shelf Unit', '1.0');\n ProductScreen.do.pressNumpad('2');\n ProductScreen.check.selectedOrderlineHas('Wall Shelf Unit', '2.0');\n ProductScreen.do.pressNumpad('Backspace');\n ProductScreen.check.selectedOrderlineHas('Wall Shelf Unit', '0.0');\n ProductScreen.do.pressNumpad('Backspace');\n ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '2.0');\n ProductScreen.do.pressNumpad('Backspace');\n ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '0.0');\n ProductScreen.do.pressNumpad('Backspace');\n ProductScreen.check.orderIsEmpty();\n\n // Add multiple orderlines then delete each of them until empty\n ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');\n ProductScreen.do.clickDisplayedProduct('Wall Shelf Unit');\n ProductScreen.do.clickDisplayedProduct('Small Shelf');\n ProductScreen.do.clickDisplayedProduct('Magnetic Board');\n ProductScreen.do.clickDisplayedProduct('Monitor Stand');\n ProductScreen.do.clickOrderline('Whiteboard Pen', '1.0');\n ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '1.0');\n ProductScreen.do.pressNumpad('Backspace');\n ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '0.0');\n ProductScreen.do.pressNumpad('Backspace');\n ProductScreen.check.selectedOrderlineHas('Monitor Stand', '1.0');\n ProductScreen.do.clickOrderline('Wall Shelf Unit', '1.0');\n ProductScreen.check.selectedOrderlineHas('Wall Shelf Unit', '1.0');\n ProductScreen.do.pressNumpad('Backspace');\n ProductScreen.check.selectedOrderlineHas('Wall Shelf Unit', '0.0');\n ProductScreen.do.pressNumpad('Backspace');\n ProductScreen.check.selectedOrderlineHas('Monitor Stand', '1.0');\n ProductScreen.do.clickOrderline('Small Shelf', '1.0');\n ProductScreen.check.selectedOrderlineHas('Small Shelf', '1.0');\n ProductScreen.do.pressNumpad('Backspace');\n ProductScreen.check.selectedOrderlineHas('Small Shelf', '0.0');\n ProductScreen.do.pressNumpad('Backspace');\n ProductScreen.check.selectedOrderlineHas('Monitor Stand', '1.0');\n ProductScreen.do.clickOrderline('Magnetic Board', '1.0');\n ProductScreen.check.selectedOrderlineHas('Magnetic Board', '1.0');\n ProductScreen.do.pressNumpad('Backspace');\n ProductScreen.check.selectedOrderlineHas('Magnetic Board', '0.0');\n ProductScreen.do.pressNumpad('Backspace');\n ProductScreen.check.selectedOrderlineHas('Monitor Stand', '1.0');\n ProductScreen.do.pressNumpad('Backspace');\n ProductScreen.check.selectedOrderlineHas('Monitor Stand', '0.0');\n ProductScreen.do.pressNumpad('Backspace');\n ProductScreen.check.orderIsEmpty();\n\n // Test OrderlineCustomerNoteButton\n ProductScreen.do.clickDisplayedProduct('Desk Organizer');\n ProductScreen.do.clickOrderlineCustomerNoteButton();\n TextAreaPopup.check.isShown();\n TextAreaPopup.do.inputText('Test customer note');\n TextAreaPopup.do.clickConfirm();\n ProductScreen.check.orderlineHasCustomerNote('Desk Organizer', '1', 'Test customer note');\n\n\n Tour.register('ProductScreenTour', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.clickHomeCategory();\n ProductScreen.do.clickDisplayedProduct('Test Product');\n ProductScreen.check.totalAmountIs('100.00');\n ProductScreen.do.changeFiscalPosition('No Tax');\n ProductScreen.check.noDiscountApplied(\"100.00\");\n ProductScreen.check.totalAmountIs('86.96');\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.check.remainingIs('0.00');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.isShown();\n ReceiptScreen.check.noOrderlineContainsDiscount();\n\n Tour.register('FiscalPositionNoTax', { test: true, url: '/pos/ui' }, getSteps());\n});\n\nodoo.define('point_of_sale.tour.FixedPriceNegativeQty', function (require) {\n 'use strict';\n\n const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');\n const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');\n const { ReceiptScreen } = require('point_of_sale.tour.ReceiptScreenTourMethods');\n const { getSteps, startSteps } = require('point_of_sale.tour.utils');\n var Tour = require('web_tour.tour');\n\n startSteps();\n\n ProductScreen.do.clickHomeCategory();\n\n ProductScreen.do.clickDisplayedProduct('Zero Amount Product');\n ProductScreen.check.selectedOrderlineHas('Zero Amount Product', '1.0', '1.0');\n ProductScreen.do.pressNumpad('+/- 1');\n ProductScreen.check.selectedOrderlineHas('Zero Amount Product', '-1.0', '-1.0');\n\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.check.remainingIs('0.00');\n PaymentScreen.do.clickValidate();\n\n ReceiptScreen.check.receiptIsThere();\n\n Tour.register('FixedTaxNegativeQty', { test: true, url: '/pos/ui' }, getSteps());\n});\n\nodoo.define('point_of_sale.tour.OpenCloseCashCount', function (require) {\n 'use strict';\n\n const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');\n const { getSteps, startSteps } = require('point_of_sale.tour.utils');\n var Tour = require('web_tour.tour');\n\n startSteps();\n\n ProductScreen.do.enterOpeningAmount('90');\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.check.checkSecondCashClosingDetailsLineAmount('10.00', '-');\n\n Tour.register('CashClosingDetails', { test: true, url: '/pos/ui' }, getSteps());\n});\n\nodoo.define('point_of_sale.tour.RoundGloballyTax', function (require) {\n 'use strict';\n\n const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');\n const { getSteps, startSteps } = require('point_of_sale.tour.utils');\n var Tour = require('web_tour.tour');\n\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickHomeCategory();\n ProductScreen.do.clickDisplayedProduct('Test Product');\n ProductScreen.check.totalAmountIs('115.00');\n\n Tour.register('RoundGloballyAmoundTour', { test: true, url: '/pos/ui' }, getSteps());\n});\n\nodoo.define('point_of_sale.tour.ShowTaxExcludedTour', function (require) {\n 'use strict';\n\n const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');\n const { getSteps, startSteps } = require('point_of_sale.tour.utils');\n var Tour = require('web_tour.tour');\n\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n\n ProductScreen.do.clickHomeCategory();\n\n ProductScreen.do.clickDisplayedProduct('Test Product');\n ProductScreen.check.selectedOrderlineHas('Test Product', '1.0', '100.0');\n ProductScreen.check.totalAmountIs('110.0');\n\n Tour.register('ShowTaxExcludedTour', { test: true, url: '/pos/ui' }, getSteps());\n});\n\nodoo.define('point_of_sale.tour.limitedProductPricelistLoading', function (require) {\n 'use strict';\n\n const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');\n const { getSteps, startSteps } = require('point_of_sale.tour.utils');\n var Tour = require('web_tour.tour');\n\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n\n ProductScreen.do.scan_barcode(\"0100100\");\n ProductScreen.check.selectedOrderlineHas('Test Product 1', '1.0', '80.0');\n\n ProductScreen.do.scan_barcode(\"0100200\");\n ProductScreen.check.selectedOrderlineHas('Test Product 2', '1.0', '100.0');\n\n ProductScreen.do.scan_barcode(\"0100300\");\n ProductScreen.check.selectedOrderlineHas('Test Product 3', '1.0', '50.0');\n\n Tour.register('limitedProductPricelistLoading', { test: true, url: '/pos/ui' }, getSteps());\n});\n", "odoo.define('point_of_sale.tour.ReceiptScreen', function (require) {\n 'use strict';\n\n const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');\n const { ReceiptScreen } = require('point_of_sale.tour.ReceiptScreenTourMethods');\n const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');\n const { NumberPopup } = require('point_of_sale.tour.NumberPopupTourMethods');\n const { getSteps, startSteps } = require('point_of_sale.tour.utils');\n const Tour = require('web_tour.tour');\n\n startSteps();\n\n // press close button in receipt screen\n ProductScreen.exec.addOrderline('Letter Tray', '10', '5');\n ProductScreen.check.selectedOrderlineHas('Letter Tray', '10');\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.check.validateButtonIsHighlighted(true);\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.receiptIsThere();\n // letter tray has 10% tax (search SRC)\n ReceiptScreen.check.totalAmountContains('55.0');\n ReceiptScreen.do.clickNextOrder();\n\n // send email in receipt screen\n ProductScreen.do.clickHomeCategory();\n ProductScreen.exec.addOrderline('Desk Pad', '6', '5', '30.0');\n ProductScreen.exec.addOrderline('Whiteboard Pen', '6', '6', '36.0');\n ProductScreen.exec.addOrderline('Monitor Stand', '6', '1', '6.0');\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Cash');\n PaymentScreen.do.pressNumpad('7 0');\n PaymentScreen.check.remainingIs('2.0');\n PaymentScreen.do.pressNumpad('0');\n PaymentScreen.check.remainingIs('0.00');\n PaymentScreen.check.changeIs('628.0');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.receiptIsThere();\n ReceiptScreen.check.totalAmountContains('72.0');\n ReceiptScreen.do.setEmail('test@receiptscreen.com');\n ReceiptScreen.do.clickSend();\n ReceiptScreen.check.emailIsSuccessful();\n ReceiptScreen.do.clickNextOrder();\n\n // order with tip\n // check if tip amount is displayed\n ProductScreen.exec.addOrderline('Desk Pad', '6', '5');\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickTipButton();\n NumberPopup.do.pressNumpad('1');\n NumberPopup.check.inputShownIs('1');\n NumberPopup.do.clickConfirm();\n PaymentScreen.check.emptyPaymentlines('31.0');\n PaymentScreen.do.clickPaymentMethod('Cash');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.receiptIsThere();\n ReceiptScreen.check.totalAmountContains('$ 30.00 + $ 1.00 tip');\n ReceiptScreen.do.clickNextOrder();\n\n // Test customer note in receipt\n ProductScreen.exec.addOrderline('Desk Pad', '1', '5');\n ProductScreen.exec.addCustomerNote('Test customer note')\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.customerNoteIsThere('Test customer note');\n\n Tour.register('ReceiptScreenTour', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.clickHomeCategory();\n ProductScreen.exec.addOrderline('Test Product', '1');\n ProductScreen.do.clickPricelistButton();\n ProductScreen.do.selectPriceList('special_pricelist');\n ProductScreen.check.discountOriginalPriceIs('7.0');\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Cash');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.discountAmountIs('0.7');\n\n ReceiptScreen.do.clickNextOrder();\n ProductScreen.exec.addOrderline('Test Product', '1');\n ProductScreen.do.pressNumpad('Price');\n ProductScreen.do.pressNumpad('9');\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Cash');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.noDiscountAmount();\n\n Tour.register('ReceiptScreenDiscountWithPricelistTour', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.clickHomeCategory();\n ProductScreen.do.clickDisplayedProduct('Product A');\n ProductScreen.do.enterLotNumber('123456789');\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Cash');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.trackingMethodIsLot();\n\n Tour.register('ReceiptTrackingMethodTour', { test: true, url: '/pos/ui' }, getSteps());\n});\n", "odoo.define('point_of_sale.tour.TicketScreen', function (require) {\n 'use strict';\n\n const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');\n const { ReceiptScreen } = require('point_of_sale.tour.ReceiptScreenTourMethods');\n const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');\n const { PartnerListScreen } = require('point_of_sale.tour.PartnerListScreenTourMethods');\n const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');\n const { ErrorPopup } = require('point_of_sale.tour.ErrorPopupTourMethods');\n const { Chrome } = require('point_of_sale.tour.ChromeTourMethods');\n const { getSteps, startSteps } = require('point_of_sale.tour.utils');\n var Tour = require('web_tour.tour');\n\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickHomeCategory();\n ProductScreen.exec.addOrderline('Desk Pad', '1', '2');\n ProductScreen.do.clickPartnerButton();\n ProductScreen.do.clickCustomer('Nicole Ford');\n Chrome.do.clickTicketButton();\n TicketScreen.check.nthRowContains(2, 'Nicole Ford');\n TicketScreen.do.clickNewTicket();\n ProductScreen.exec.addOrderline('Desk Pad', '1', '3');\n ProductScreen.do.clickPartnerButton();\n ProductScreen.do.clickCustomer('Brandon Freeman');\n ProductScreen.do.clickPayButton();\n PaymentScreen.check.isShown();\n Chrome.do.clickTicketButton();\n TicketScreen.check.nthRowContains(3, 'Brandon Freeman');\n TicketScreen.do.clickNewTicket();\n ProductScreen.exec.addOrderline('Desk Pad', '2', '4');\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.isShown();\n Chrome.do.clickTicketButton();\n TicketScreen.check.nthRowContains(4, 'Receipt');\n TicketScreen.do.selectFilter('Receipt');\n TicketScreen.check.nthRowContains(2, 'Receipt');\n TicketScreen.do.selectFilter('Payment');\n TicketScreen.check.nthRowContains(2, 'Payment');\n TicketScreen.do.selectFilter('Ongoing');\n TicketScreen.check.nthRowContains(2, 'Ongoing');\n TicketScreen.do.selectFilter('All active orders');\n TicketScreen.check.nthRowContains(4, 'Receipt');\n TicketScreen.do.search('Customer', 'Nicole');\n TicketScreen.check.nthRowContains(2, 'Nicole');\n TicketScreen.do.search('Customer', 'Brandon');\n TicketScreen.check.nthRowContains(2, 'Brandon');\n TicketScreen.do.search('Receipt Number', '-0003');\n TicketScreen.check.nthRowContains(2, 'Receipt');\n // Close the TicketScreen to see the current order which is in ReceiptScreen.\n // This is just to remove the search string in the search bar.\n TicketScreen.do.clickDiscard();\n ReceiptScreen.check.isShown();\n // Open again the TicketScreen to check the Paid filter.\n Chrome.do.clickTicketButton();\n TicketScreen.do.selectFilter('Paid');\n TicketScreen.check.nthRowContains(2, '-0003');\n // Pay the order that was in PaymentScreen.\n TicketScreen.do.selectFilter('Payment');\n TicketScreen.do.selectOrder('-0002');\n PaymentScreen.do.clickPaymentMethod('Cash');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.isShown();\n ReceiptScreen.do.clickNextOrder();\n ProductScreen.check.isShown();\n // Check that the Paid filter will show the 2 synced orders.\n Chrome.do.clickTicketButton();\n TicketScreen.do.selectFilter('Paid');\n TicketScreen.check.nthRowContains(2, 'Brandon Freeman');\n TicketScreen.check.nthRowContains(3, '-0003');\n // Invoice order\n TicketScreen.do.selectOrder('-0003');\n TicketScreen.check.orderWidgetIsNotEmpty();\n TicketScreen.do.clickControlButton('Invoice');\n Chrome.do.confirmPopup();\n PartnerListScreen.check.isShown();\n PartnerListScreen.do.clickPartner('Colleen Diaz');\n TicketScreen.check.partnerIs('Colleen Diaz');\n // Reprint receipt\n TicketScreen.do.clickControlButton('Print Receipt');\n ReceiptScreen.check.isShown();\n ReceiptScreen.do.clickBack();\n // When going back, the ticket screen should be in its previous state.\n TicketScreen.check.filterIs('Paid');\n\n // Test refund //\n TicketScreen.do.clickDiscard();\n ProductScreen.check.isShown();\n ProductScreen.check.orderIsEmpty();\n ProductScreen.do.clickRefund();\n // Filter should be automatically 'Paid'.\n TicketScreen.check.filterIs('Paid');\n TicketScreen.do.selectOrder('-0003');\n TicketScreen.check.partnerIs('Colleen Diaz');\n TicketScreen.do.clickOrderline('Desk Pad');\n TicketScreen.do.pressNumpad('3');\n // Error should show because 2 is more than the number\n // that can be refunded.\n ErrorPopup.do.clickConfirm();\n TicketScreen.do.clickDiscard();\n ProductScreen.check.isShown();\n ProductScreen.check.orderIsEmpty();\n ProductScreen.do.clickRefund();\n TicketScreen.do.selectOrder('-0003');\n TicketScreen.do.clickOrderline('Desk Pad');\n TicketScreen.do.pressNumpad('1');\n TicketScreen.check.toRefundTextContains('To Refund: 1.00');\n TicketScreen.do.confirmRefund();\n ProductScreen.check.isShown();\n ProductScreen.check.selectedOrderlineHas('Desk Pad', '-1.00');\n // Try changing the refund line to positive number.\n // Error popup should show.\n ProductScreen.do.pressNumpad('2');\n ErrorPopup.do.clickConfirm();\n // Change the refund line quantity to -3 -- not allowed\n // so error popup.\n ProductScreen.do.pressNumpad('+/- 3');\n ErrorPopup.do.clickConfirm();\n // Change the refund line quantity to -2 -- allowed.\n ProductScreen.do.pressNumpad('+/- 2');\n ProductScreen.check.selectedOrderlineHas('Desk Pad', '-2.00');\n // Check if the amount being refunded changed to 2.\n ProductScreen.do.clickRefund();\n TicketScreen.do.selectOrder('-0003');\n TicketScreen.check.toRefundTextContains('Refunding 2.00');\n TicketScreen.do.clickDiscard();\n // Pay the refund order.\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.isShown();\n ReceiptScreen.do.clickNextOrder();\n // Check refunded quantity.\n ProductScreen.do.clickRefund();\n TicketScreen.do.selectOrder('-0003');\n TicketScreen.check.refundedNoteContains('2.00 Refunded');\n\n Tour.register('TicketScreenTour', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickHomeCategory();\n ProductScreen.do.clickDisplayedProduct('Product Test');\n ProductScreen.check.totalAmountIs('100.00');\n ProductScreen.do.changeFiscalPosition('No Tax');\n ProductScreen.check.totalAmountIs('86.96');\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.check.remainingIs('0.00');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.isShown();\n ReceiptScreen.do.clickNextOrder();\n ProductScreen.do.clickRefund();\n TicketScreen.do.selectOrder('-0001');\n TicketScreen.do.clickOrderline('Product Test');\n TicketScreen.do.pressNumpad('1');\n TicketScreen.check.toRefundTextContains('To Refund: 1.00');\n TicketScreen.do.confirmRefund();\n ProductScreen.check.isShown();\n ProductScreen.check.totalAmountIs('-86.96');\n\n Tour.register('FiscalPositionNoTaxRefund', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickHomeCategory();\n ProductScreen.do.clickDisplayedProduct('Product A');\n ProductScreen.do.enterLotNumber('123456789');\n ProductScreen.check.selectedOrderlineHas('Product A', '1.00');\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.isShown();\n ReceiptScreen.do.clickNextOrder();\n ProductScreen.do.clickRefund();\n TicketScreen.do.selectOrder('-0001');\n TicketScreen.do.clickOrderline('Product A');\n TicketScreen.do.pressNumpad('1');\n TicketScreen.check.toRefundTextContains('To Refund: 1.00');\n TicketScreen.do.confirmRefund();\n ProductScreen.check.isShown();\n ProductScreen.do.clickLotIcon();\n ProductScreen.check.checkFirstLotNumber('123456789');\n\n Tour.register('LotRefundTour', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickHomeCategory();\n ProductScreen.do.clickDisplayedProduct(\"Test Product\");\n ProductScreen.check.checkTaxAmount(\"9.09\");\n ProductScreen.check.totalAmountIs(\"100.00\");\n ProductScreen.do.changeFiscalPosition(\"test fp\");\n ProductScreen.check.totalAmountIs(\"100.00\");\n ProductScreen.check.checkTaxAmount(\"4.76\");\n\n Tour.register(\"FiscalPositionTwoTaxIncluded\", { test: true, url: \"/pos/ui\" }, getSteps());\n});\n", "odoo.define('point_of_sale.tour.ChromeTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n\n class Do {\n confirmPopup() {\n return [\n {\n content: 'confirm popup',\n trigger: '.popups .modal-dialog .button.confirm',\n },\n ];\n }\n clickTicketButton() {\n return [\n {\n trigger: '.pos-topheader .ticket-button',\n },\n {\n trigger: '.subwindow .ticket-screen',\n run: () => {},\n },\n ];\n }\n }\n\n return createTourMethods('Chrome', Do);\n});\n", "odoo.define('point_of_sale.tour.ErrorPopupTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n\n class Do {\n clickConfirm() {\n return [\n {\n content: 'click confirm button',\n trigger: '.popup-error .footer .cancel',\n },\n ];\n }\n }\n\n class Check {\n isShown() {\n return [\n {\n content: 'error popup is shown',\n trigger: '.modal-dialog .popup-error',\n run: () => {},\n },\n ];\n }\n\n messageBodyContains(text) {\n return [\n {\n content: `check '${text}' is in the body of the popup`,\n trigger: `.modal-dialog .popup-error .body:contains(${text})`,\n }\n ];\n }\n }\n\n return createTourMethods('ErrorPopup', Do, Check);\n});\n", "odoo.define('point_of_sale.tour.NumberPopupTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n\n class Do {\n /**\n * Note: Maximum of 2 characters because NumberBuffer only allows 2 consecutive\n * fast inputs. Fast inputs is the case in tours.\n *\n * @param {String} keys space-separated input keys\n */\n pressNumpad(keys) {\n const numberChars = '0 1 2 3 4 5 6 7 8 9 C'.split(' ');\n const modeButtons = '+1 +10 +2 +20 +5 +50'.split(' ');\n const decimalSeparators = ', .'.split(' ');\n function generateStep(key) {\n let trigger;\n if (numberChars.includes(key)) {\n trigger = `.popup-numpad .number-char:contains(\"${key}\")`;\n } else if (modeButtons.includes(key)) {\n trigger = `.popup-numpad .mode-button:contains(\"${key}\")`;\n } else if (key === 'Backspace') {\n trigger = `.popup-numpad .numpad-backspace`;\n } else if (decimalSeparators.includes(key)) {\n trigger = `.popup-numpad .number-char.dot`;\n }\n return {\n content: `'${key}' pressed in numpad`,\n trigger,\n };\n }\n return keys.split(' ').map(generateStep);\n }\n clickConfirm() {\n return [\n {\n content: 'click confirm button',\n trigger: '.popup-number .footer .confirm',\n },\n ];\n }\n }\n\n class Check {\n isShown() {\n return [\n {\n content: 'number popup is shown',\n trigger: '.modal-dialog .popup-number',\n run: () => {},\n },\n ];\n }\n inputShownIs(val) {\n return [\n {\n content: 'number input element check',\n trigger: '.modal-dialog .popup-number .popup-input',\n run: () => {},\n },\n {\n content: `input shown is '${val}'`,\n trigger: `.modal-dialog .popup-number .popup-input:contains(\"${val}\")`,\n run: () => {},\n },\n ];\n }\n }\n\n return createTourMethods('NumberPopup', Do, Check);\n});\n", "odoo.define('point_of_sale.tour.PartnerListScreenTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n\n class Do {\n clickPartner(name) {\n return [\n {\n content: `click partner '${name}' from partner list screen`,\n trigger: `.partnerlist-screen .partner-list-contents .partner-line td:contains(\"${name}\")`,\n },\n ];\n }\n clickPartnerDetailsButton(name) {\n return [\n {\n content: `click partner details '${name}' from partner list screen`,\n trigger: `.partner-line:contains('${name}') .edit-partner-button`,\n }\n ]\n }\n clickBack() {\n return [\n {\n trigger: \".partnerlist-screen .button.back\",\n },\n ];\n }\n }\n\n class Check {\n isShown() {\n return [\n {\n content: 'partner list screen is shown',\n trigger: '.pos-content .partnerlist-screen',\n run: () => {},\n },\n ];\n }\n }\n\n class Execute {}\n\n return createTourMethods('PartnerListScreen', Do, Check, Execute);\n});\n", "odoo.define('point_of_sale.tour.PaymentScreenTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n\n class Do {\n clickPaymentMethod(name) {\n return [\n {\n content: `click '${name}' payment method`,\n trigger: `.paymentmethods .button.paymentmethod:contains(\"${name}\")`,\n },\n ];\n }\n\n /**\n * Delete the paymentline having the given payment method name and amount.\n * @param {String} name payment method\n * @param {String} amount\n */\n clickPaymentlineDelButton(name, amount) {\n return [\n {\n content: `delete ${name} paymentline with ${amount} amount`,\n trigger: `.paymentlines .paymentline .payment-name:contains(\"${name}\") ~ .delete-button`,\n },\n ];\n }\n\n clickEmailButton() {\n return [\n {\n content: `click email button`,\n trigger: `.payment-buttons .js_email`,\n },\n ];\n }\n\n clickInvoiceButton() {\n return [{ content: 'click invoice button', trigger: '.payment-buttons .js_invoice' }];\n }\n\n clickValidate() {\n return [\n {\n content: 'validate payment',\n trigger: `.payment-screen .button.next.highlight`,\n },\n ];\n }\n\n /**\n * Press the numpad in sequence based on the given space-separated keys.\n * Note: Maximum of 2 characters because NumberBuffer only allows 2 consecutive\n * fast inputs. Fast inputs is the case in tours.\n *\n * @param {String} keys space-separated numpad keys\n */\n pressNumpad(keys) {\n const numberChars = '. +/- 0 1 2 3 4 5 6 7 8 9'.split(' ');\n const modeButtons = '+10 +20 +50'.split(' ');\n function generateStep(key) {\n let trigger;\n if (numberChars.includes(key)) {\n trigger = `.payment-numpad .number-char:contains(\"${key}\")`;\n } else if (modeButtons.includes(key)) {\n trigger = `.payment-numpad .mode-button:contains(\"${key}\")`;\n } else if (key === 'Backspace') {\n trigger = `.payment-numpad .number-char img[alt=\"Backspace\"]`;\n }\n return {\n content: `'${key}' pressed in payment numpad`,\n trigger,\n };\n }\n return keys.split(' ').map(generateStep);\n }\n\n clickBack() {\n return [\n {\n content: 'click back button',\n trigger: '.payment-screen .button.back',\n },\n ];\n }\n\n clickTipButton() {\n return [\n {\n trigger: '.payment-screen .button.js_tip',\n },\n ]\n }\n clickShipLaterButton() {\n return [\n {\n content: 'click ship later button',\n trigger: '.button:contains(\"Ship Later\")',\n },\n ]\n }\n }\n\n class Check {\n isShown() {\n return [\n {\n content: 'payment screen is shown',\n trigger: '.pos .payment-screen',\n run: () => {},\n },\n ];\n }\n /**\n * Check if change is the provided amount.\n * @param {String} amount\n */\n changeIs(amount) {\n return [\n {\n content: `change is ${amount}`,\n trigger: `.payment-status-change .amount:contains(\"${amount}\")`,\n run: () => {},\n },\n ];\n }\n\n /**\n * Check if the remaining is the provided amount.\n * @param {String} amount\n */\n remainingIs(amount) {\n return [\n {\n content: `remaining amount is ${amount}`,\n trigger: `.payment-status-remaining .amount:contains(\"${amount}\")`,\n run: () => {},\n },\n ];\n }\n\n /**\n * Check if validate button is highlighted.\n * @param {Boolean} isHighlighted\n */\n validateButtonIsHighlighted(isHighlighted = true) {\n return [\n {\n content: `validate button is ${\n isHighlighted ? 'highlighted' : 'not highligted'\n }`,\n trigger: isHighlighted\n ? `.payment-screen .button.next.highlight`\n : `.payment-screen .button.next:not(:has(.highlight))`,\n run: () => {},\n },\n ];\n }\n\n /**\n * Check if the paymentlines are empty. Also provide the amount to pay.\n * @param {String} amountToPay\n */\n emptyPaymentlines(amountToPay) {\n return [\n {\n content: `there are no paymentlines`,\n trigger: `.paymentlines-empty`,\n run: () => {},\n },\n {\n content: `amount to pay is '${amountToPay}'`,\n trigger: `.paymentlines-empty .total:contains(\"${amountToPay}\")`,\n run: () => {},\n },\n ];\n }\n\n /**\n * Check if the selected paymentline has the given payment method and amount.\n * @param {String} paymentMethodName\n * @param {String} amount\n */\n selectedPaymentlineHas(paymentMethodName, amount) {\n return [\n {\n content: `line paid via '${paymentMethodName}' is selected`,\n trigger: `.paymentlines .paymentline.selected .payment-name:contains(\"${paymentMethodName}\")`,\n run: () => {},\n },\n {\n content: `amount tendered in the line is '${amount}'`,\n trigger: `.paymentlines .paymentline.selected .payment-amount:contains(\"${amount}\")`,\n run: () => {},\n },\n ];\n }\n totalIs(amount) {\n return [\n {\n content: `total is ${amount}`,\n trigger: `.total:contains(\"${amount}\")`,\n run: () => {},\n },\n ];\n }\n totalDueIs(amount) {\n return [\n {\n content: `total due is ${amount}`,\n trigger: `.payment-status-total-due:contains(\"${amount}\")`,\n run: () => {},\n },\n ];\n }\n isInvoiceButtonChecked() {\n return [\n {\n content: 'check invoice button is checked',\n trigger: '.js_invoice.highlight',\n run: () => {},\n }\n ]\n }\n isInvoiceButtonNotChecked() {\n return [\n {\n content: \"check invoice button is checked\",\n trigger: \".js_invoice:not(.highlight)\",\n run: () => {},\n },\n ];\n }\n }\n\n class Execute {\n pay(method, amount) {\n const steps = [];\n steps.push(...this._do.clickPaymentMethod(method));\n for (let char of amount.split('')) {\n steps.push(...this._do.pressNumpad(char));\n }\n steps.push(...this._check.validateButtonIsHighlighted());\n steps.push(...this._do.clickValidate());\n return steps;\n }\n }\n\n return createTourMethods('PaymentScreen', Do, Check, Execute);\n});\n", "odoo.define('point_of_sale.tour.ProductConfiguratorTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n\n class Do {\n pickRadio(name) {\n return [\n {\n content: `picking radio attribute with name ${name}`,\n trigger: `.product-configurator-popup .attribute-name-cell label:contains('${name}')`,\n },\n ];\n }\n\n pickSelect(name) {\n return [\n {\n content: `picking select attribute with name ${name}`,\n trigger: `.product-configurator-popup .configurator_select:has(option:contains('${name}'))`,\n run: `text ${name}`,\n },\n ];\n }\n\n pickColor(name) {\n return [\n {\n content: `picking color attribute with name ${name}`,\n trigger: `.product-configurator-popup .configurator_color[data-color='${name}']`,\n },\n ];\n }\n\n fillCustomAttribute(value) {\n return [\n {\n content: `filling custom attribute with value ${value}`,\n trigger: `.product-configurator-popup .custom_value`,\n run: `text ${value}`,\n },\n ];\n }\n\n confirmAttributes() {\n return [\n {\n content: `confirming product configuration`,\n trigger: `.product-configurator-popup .button.confirm`,\n },\n ];\n }\n\n cancelAttributes() {\n return [\n {\n content: `canceling product configuration`,\n trigger: `.product-configurator-popup .button.cancel`,\n },\n ];\n }\n }\n\n class Check {\n isShown() {\n return [\n {\n content: 'product configurator is shown',\n trigger: '.product-configurator-popup:not(:has(.oe_hidden))',\n run: () => {},\n },\n ];\n }\n\n numberRadioOptions(number) {\n return [\n {\n trigger: `.product-configurator-popup .attribute-name-cell`,\n run: () => {\n const radio_options = $('.product-configurator-popup .attribute-name-cell').length;\n if (radio_options !== number) {\n throw new Error(`Expected ${number} radio options, got ${radio_options}`);\n }\n }\n },\n ];\n }\n }\n\n return createTourMethods('ProductConfigurator', Do, Check);\n});\n", "odoo.define('point_of_sale.tour.ProductScreenTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n const { TextAreaPopup } = require('point_of_sale.tour.TextAreaPopupTourMethods');\n\n class Do {\n clickDisplayedProduct(name) {\n return [\n {\n content: `click product '${name}'`,\n trigger: `.product-list .product-name:contains(\"${name}\")`,\n },\n ];\n }\n\n clickOrderline(name, quantity) {\n return [\n {\n content: `selecting orderline with product '${name}' and quantity '${quantity}'`,\n trigger: `.order .orderline:not(:has(.selected)) .product-name:contains(\"${name}\") ~ .info-list em:contains(\"${quantity}\")`,\n },\n {\n content: `orderline with product '${name}' and quantity '${quantity}' has been selected`,\n trigger: `.order .orderline.selected .product-name:contains(\"${name}\") ~ .info-list em:contains(\"${quantity}\")`,\n run: () => {},\n },\n ];\n }\n\n clickSubcategory(name) {\n return [\n {\n content: `selecting '${name}' subcategory`,\n trigger: `.products-widget > .products-widget-control .category-simple-button:contains(\"${name}\")`,\n },\n {\n content: `'${name}' subcategory selected`,\n trigger: `.breadcrumbs .breadcrumb-button:contains(\"${name}\")`,\n run: () => {},\n },\n ];\n }\n\n clickHomeCategory() {\n return [\n {\n content: `click Home subcategory`,\n trigger: `.breadcrumbs .breadcrumb-home`,\n },\n ];\n }\n\n /**\n * Press the numpad in sequence based on the given space-separated keys.\n * NOTE: Maximum of 2 characters because NumberBuffer only allows 2 consecutive\n * fast inputs. Fast inputs is the case in tours.\n *\n * @param {String} keys space-separated numpad keys\n */\n pressNumpad(keys) {\n const numberChars = '. 0 1 2 3 4 5 6 7 8 9'.split(' ');\n const modeButtons = 'Qty Price Disc'.split(' ');\n function generateStep(key) {\n let trigger;\n if (numberChars.includes(key)) {\n trigger = `.numpad .number-char:contains(\"${key}\")`;\n } else if (modeButtons.includes(key)) {\n trigger = `.numpad .mode-button:contains(\"${key}\")`;\n } else if (key === 'Backspace') {\n trigger = `.numpad .numpad-backspace`;\n } else if (key === '+/-') {\n trigger = `.numpad .numpad-minus`;\n }\n return {\n content: `'${key}' pressed in product screen numpad`,\n trigger,\n };\n }\n return keys.split(' ').map(generateStep);\n }\n\n clickPayButton(shouldCheck = true) {\n const steps = [{ content: 'click pay button', trigger: '.product-screen .actionpad .button.pay' }];\n if (shouldCheck) {\n steps.push({\n content: 'now in payment screen',\n trigger: '.pos-content .payment-screen',\n run: () => {},\n });\n }\n return steps;\n }\n\n clickPartnerButton() {\n return [\n { content: 'click customer button', trigger: '.actionpad .button.set-partner' },\n {\n content: 'partner screen is shown',\n trigger: '.pos-content .partnerlist-screen',\n run: () => {},\n },\n ];\n }\n\n clickCustomer(name) {\n return [\n {\n content: `select customer '${name}'`,\n trigger: `.partnerlist-screen .partner-line td:contains(\"${name}\")`,\n },\n ];\n }\n\n clickOrderlineCustomerNoteButton() {\n return [\n {\n content: 'click customer note button',\n trigger: '.control-buttons .control-button span:contains(\"Customer Note\")',\n }\n ]\n }\n clickRefund() {\n return [\n {\n trigger: '.control-button:contains(\"Refund\")',\n },\n ];\n }\n confirmOpeningPopup() {\n return [{ trigger: '.opening-cash-control .button:contains(\"Open session\")' }];\n }\n clickPricelistButton() {\n return [{ trigger: '.o_pricelist_button' }];\n }\n selectPriceList(name) {\n return [\n {\n content: `select price list '${name}'`,\n trigger: `.selection-item:contains(\"${name}\")`,\n },\n ];\n }\n enterOpeningAmount(amount) {\n return [\n {\n content: 'enter opening amount',\n trigger: '.cash-input-sub-section > .pos-input',\n run: 'text ' + amount,\n },\n ];\n }\n changeFiscalPosition(name) {\n return [\n {\n content: 'click fiscal position button',\n trigger: '.o_fiscal_position_button',\n },\n {\n content: 'fiscal position screen is shown',\n trigger: `.selection-item:contains(\"${name}\")`,\n },\n ];\n }\n scan_barcode(barcode) {\n return [\n {\n content: `input barcode '${barcode}'`,\n trigger: \"input.ean\",\n run: `text ${barcode}`,\n },\n {\n content: `button scan barcode '${barcode}'`,\n trigger: \"li.barcode\",\n run: 'click',\n }\n ];\n }\n scan_ean13_barcode(barcode) {\n return [\n {\n content: `input barcode '${barcode}'`,\n trigger: \"input.ean\",\n run: `text ${barcode}`,\n },\n {\n content: `button scan EAN-13 barcode '${barcode}'`,\n trigger: \"li.custom_ean\",\n run: 'click',\n }\n ];\n }\n clickLotIcon() {\n return [\n {\n content: 'click lot icon',\n trigger: '.line-lot-icon',\n },\n ];\n }\n enterLotNumber(number) {\n return [\n {\n content: 'enter lot number',\n trigger: '.list-line-input:first()',\n run: 'text ' + number,\n },\n {\n content: 'click validate lot number',\n trigger: '.popup .button.confirm',\n }\n ];\n }\n }\n\n class Check {\n isShown() {\n return [\n {\n content: 'product screen is shown',\n trigger: '.product-screen',\n run: () => {},\n },\n ];\n }\n selectedOrderlineHas(name, quantity, price) {\n const res = [\n {\n // check first if the order widget is there and has orderlines\n content: 'order widget has orderlines',\n trigger: '.order .orderlines',\n run: () => {},\n },\n {\n content: `'${name}' is selected`,\n trigger: `.order .orderline.selected .product-name:contains(\"${name}\")`,\n run: function () {}, // it's a check\n },\n ];\n if (quantity) {\n res.push({\n content: `selected line has ${quantity} quantity`,\n trigger: `.order .orderline.selected .product-name:contains(\"${name}\") ~ .info-list em:contains(\"${quantity}\")`,\n run: function () {}, // it's a check\n });\n }\n if (price) {\n res.push({\n content: `selected line has total price of ${price}`,\n trigger: `.order .orderline.selected .product-name:contains(\"${name}\") ~ .price:contains(\"${price}\")`,\n run: function () {}, // it's a check\n });\n }\n return res;\n }\n orderIsEmpty() {\n return [\n {\n content: `order is empty`,\n trigger: `.order .order-empty`,\n run: () => {},\n },\n ];\n }\n\n productIsDisplayed(name) {\n return [\n {\n content: `'${name}' should be displayed`,\n trigger: `.product-list .product-name:contains(\"${name}\")`,\n run: () => {},\n },\n ];\n }\n totalAmountIs(amount) {\n return [\n {\n content: `order total amount is '${amount}'`,\n trigger: `.order-container .order .summary .value:contains(\"${amount}\")`,\n run: () => {},\n }\n ]\n }\n modeIsActive(mode) {\n return [\n {\n content: `'${mode}' is active`,\n trigger: `.numpad button.selected-mode:contains('${mode}')`,\n run: function () {},\n },\n ];\n }\n orderlineHasCustomerNote(name, quantity, note) {\n return [\n {\n content: `line has ${quantity} quantity`,\n trigger: `.order .orderline .product-name:contains(\"${name}\") ~ .info-list em:contains(\"${quantity}\")`,\n run: function () {}, // it's a check\n },\n {\n content: `line has '${note}' as customer note`,\n trigger: `.order .orderline .info-list .orderline-note:contains(\"${note}\")`,\n run: function () {}, // it's a check\n },\n ]\n }\n checkSecondCashClosingDetailsLineAmount(amount, sign) {\n return [\n {\n content: 'Click close session button',\n trigger: '.fa-sign-out',\n },\n {\n content: 'Check closing details',\n trigger: `.cash-overview tr:nth-child(2) td:contains(\"${amount}\")`,\n run: () => {}, // it's a check\n },\n {\n content: 'Check closing details',\n trigger: `.cash-overview tr:nth-child(2) .cash-sign:contains(\"${sign}\")`,\n run: () => {}, // it's a check\n },\n ];\n }\n noDiscountApplied(originalPrice) {\n return [\n {\n content: 'no discount is applied',\n trigger: `.info:not(:contains(${originalPrice}))`,\n },\n ];\n }\n discountOriginalPriceIs(original_price) {\n return [\n {\n content: `discount original price is shown`,\n trigger: `s:contains('${original_price}')`,\n run: function () {},\n },\n ];\n }\n checkFirstLotNumber(number) {\n return [\n {\n content: 'Check lot number',\n trigger: `.list-line-input:propValue('${number}')`,\n run: () => {}, // it's a check\n },\n ];\n }\n checkOrderlinesNumber(number) {\n return [\n {\n content: `check orderlines number`,\n trigger: `.order .orderlines .orderline`,\n run: () => {\n const orderline_amount = $('.order .orderlines .orderline').length;\n if (orderline_amount !== number) {\n throw new Error(`Expected ${number} orderlines, got ${orderline_amount}`);\n }\n },\n },\n ];\n }\n checkTaxAmount(number) {\n return [\n {\n content: `check order tax amount`,\n trigger: `.subentry:contains(\"${number}\")`,\n },\n ];\n }\n }\n\n class Execute {\n /**\n * Create an orderline for the given `productName` and `quantity`.\n * - If `unitPrice` is provided, price of the product of the created line\n * is changed to that value.\n * - If `expectedTotal` is provided, the created orderline (which is the currently\n * selected orderline) is checked if it contains the correct quantity and total\n * price.\n *\n * @param {string} productName\n * @param {string} quantity\n * @param {string} unitPrice\n * @param {string} expectedTotal\n */\n addOrderline(productName, quantity, unitPrice = undefined, expectedTotal = undefined) {\n const res = this._do.clickDisplayedProduct(productName);\n if (unitPrice) {\n res.push(...this._do.pressNumpad('Price'));\n res.push(...this._check.modeIsActive('Price'));\n res.push(...this._do.pressNumpad(unitPrice.toString().split('').join(' ')));\n res.push(...this._do.pressNumpad('Qty'));\n res.push(...this._check.modeIsActive('Qty'));\n }\n for (let char of (quantity.toString() == '1' ? '' : quantity.toString())) {\n if ('.0123456789'.includes(char)) {\n res.push(...this._do.pressNumpad(char));\n } else if ('-'.includes(char)) {\n res.push(...this._do.pressNumpad('+/-'));\n }\n }\n if (expectedTotal) {\n res.push(...this._check.selectedOrderlineHas(productName, quantity, expectedTotal));\n } else {\n res.push(...this._check.selectedOrderlineHas(productName, quantity));\n }\n return res;\n }\n addMultiOrderlines(...list) {\n const steps = [];\n for (let [product, qty, price] of list) {\n steps.push(...this.addOrderline(product, qty, price));\n }\n return steps;\n }\n addCustomerNote(note) {\n const res = [];\n res.push(...this._do.clickOrderlineCustomerNoteButton());\n res.push(...TextAreaPopup._do.inputText(note));\n res.push(...TextAreaPopup._do.clickConfirm());\n return res;\n }\n }\n\n return createTourMethods('ProductScreen', Do, Check, Execute);\n});\n", "odoo.define('point_of_sale.tour.ReceiptScreenTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n\n class Do {\n clickNextOrder() {\n return [\n {\n content: 'go to next screen',\n trigger: '.receipt-screen .button.next.highlight',\n },\n ];\n }\n setEmail(email) {\n return [\n {\n trigger: '.receipt-screen .input-email input',\n run: `text ${email}`,\n },\n ];\n }\n clickSend(isHighlighted = true) {\n return [\n {\n trigger: `.receipt-screen .input-email .send${isHighlighted ? '.highlight' : ''}`,\n },\n ];\n }\n clickBack() {\n return [\n {\n trigger: '.receipt-screen .button.back',\n },\n ];\n }\n }\n\n class Check {\n isShown() {\n return [\n {\n content: 'receipt screen is shown',\n trigger: '.pos .receipt-screen',\n run: () => {},\n },\n ];\n }\n\n receiptIsThere() {\n return [\n {\n content: 'there should be the receipt',\n trigger: '.receipt-screen .pos-receipt',\n run: () => {},\n },\n ];\n }\n\n totalAmountContains(value) {\n return [\n {\n trigger: `.receipt-screen .top-content h1:contains(\"${value}\")`,\n run: () => {},\n },\n ];\n }\n\n emailIsSuccessful() {\n return [\n {\n trigger: `.receipt-screen .notice .successful`,\n run: () => {},\n },\n ];\n }\n\n customerNoteIsThere(note) {\n return [\n {\n trigger: `.receipt-screen .orderlines .pos-receipt-left-padding:contains(\"${note}\")`\n }\n ]\n }\n discountAmountIs(value) {\n return [\n {\n trigger: `.pos-receipt>div:contains(\"Discounts\")>span:contains(\"${value}\")`,\n run: () => {},\n },\n ];\n }\n noDiscountAmount() {\n return [\n {\n trigger: `.pos-receipt:not(:contains(\"Discounts\"))`,\n run: () => {},\n },\n ];\n }\n noOrderlineContainsDiscount() {\n return [\n {\n trigger: `.orderlines:not(:contains('->'))`,\n run: () => { },\n },\n ];\n }\n trackingMethodIsLot() {\n return [\n {\n content: `tracking method is Lot`,\n trigger: `li:contains(\"Lot Number\")`,\n run: () => {},\n },\n ];\n }\n }\n\n class Execute {\n nextOrder() {\n return [...this._check.isShown(), ...this._do.clickNextOrder()];\n }\n }\n\n return createTourMethods('ReceiptScreen', Do, Check, Execute);\n});\n", "odoo.define('point_of_sale.tour.SelectionPopupTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n\n class Do {\n clickItem(name) {\n return [\n {\n content: `click selection '${name}'`,\n trigger: `.selection-item:contains(\"${name}\")`,\n },\n ];\n }\n }\n\n class Check {\n hasSelectionItem(name) {\n return [\n {\n content: `selection popup has '${name}'`,\n trigger: `.selection-item:contains(\"${name}\")`,\n run: () => {},\n },\n ];\n }\n isShown() {\n return [\n {\n content: 'selection popup is shown',\n trigger: '.modal-dialog .popup-selection',\n run: () => {},\n },\n ];\n }\n }\n\n return createTourMethods('SelectionPopup', Do, Check);\n});\n", "odoo.define('point_of_sale.tour.TextAreaPopupTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n\n class Do {\n inputText(val) {\n return [\n {\n content: `input text '${val}'`,\n trigger: `.modal-dialog .popup-textarea textarea`,\n run: `text ${val}`,\n },\n ];\n }\n clickConfirm() {\n return [\n {\n content: 'confirm text input popup',\n trigger: '.modal-dialog .confirm',\n },\n ];\n }\n }\n\n class Check {\n isShown() {\n return [\n {\n content: 'text input popup is shown',\n trigger: '.modal-dialog .popup-textarea',\n run: () => {},\n },\n ];\n }\n }\n\n return createTourMethods('TextAreaPopup', Do, Check);\n});\n", "odoo.define('point_of_sale.tour.TextInputPopupTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n\n class Do {\n inputText(val) {\n return [\n {\n content: `input text '${val}'`,\n trigger: `.modal-dialog .popup-textinput input`,\n run: `text ${val}`,\n },\n ];\n }\n clickConfirm() {\n return [\n {\n content: 'confirm text input popup',\n trigger: '.modal-dialog .confirm',\n },\n ];\n }\n }\n\n class Check {\n isShown() {\n return [\n {\n content: 'text input popup is shown',\n trigger: '.modal-dialog .popup-textinput',\n run: () => {},\n },\n ];\n }\n }\n\n return createTourMethods('TextInputPopup', Do, Check);\n});\n", "odoo.define('point_of_sale.tour.TicketScreenTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n\n class Do {\n clickNewTicket() {\n return [{ trigger: '.ticket-screen .highlight' }];\n }\n clickDiscard() {\n return [{ trigger: '.ticket-screen button.discard' }];\n }\n selectOrder(orderName) {\n return [\n {\n trigger: `.ticket-screen .order-row > .col:nth-child(2):contains(\"${orderName}\")`,\n },\n ];\n }\n deleteOrder(orderName) {\n return [\n {\n trigger: `.ticket-screen .orders > .order-row > .col:contains(\"${orderName}\") ~ .col[name=\"delete\"]`,\n },\n ];\n }\n selectFilter(name) {\n return [\n {\n trigger: `.pos-search-bar .filter`,\n },\n {\n trigger: `.pos-search-bar .filter ul`,\n run: () => {},\n },\n {\n trigger: `.pos-search-bar .filter ul li:contains(\"${name}\")`,\n },\n ];\n }\n search(field, searchWord) {\n return [\n {\n trigger: '.pos-search-bar input',\n run: `text ${searchWord}`,\n },\n {\n /**\n * Manually trigger keyup event to show the search field list\n * because the previous step do not trigger keyup event.\n */\n trigger: '.pos-search-bar input',\n run: function () {\n document\n .querySelector('.pos-search-bar input')\n .dispatchEvent(new KeyboardEvent('keyup', { key: '' }));\n },\n },\n {\n trigger: `.pos-search-bar .search ul li:contains(\"${field}\")`,\n },\n ];\n }\n settleTips() {\n return [\n {\n trigger: '.ticket-screen .buttons .settle-tips',\n },\n ];\n }\n clickControlButton(name) {\n return [\n {\n trigger: `.ticket-screen .control-button:contains(\"${name}\")`,\n },\n ];\n }\n clickOrderline(name) {\n return [\n {\n trigger: `.ticket-screen .orderline:not(:has(.selected)) .product-name:contains(\"${name}\")`,\n },\n {\n trigger: `.ticket-screen .orderline.selected .product-name:contains(\"${name}\")`,\n run: () => {},\n },\n ];\n }\n pressNumpad(key) {\n let trigger;\n if ('.0123456789'.includes(key)) {\n trigger = `.numpad .number-char:contains(\"${key}\")`;\n } else if (key === 'Backspace') {\n trigger = `.numpad .numpad-backspace`;\n } else if (key === '+/-') {\n trigger = `.numpad .numpad-minus`;\n }\n return [\n {\n trigger,\n },\n ];\n }\n confirmRefund() {\n return [\n {\n trigger: '.ticket-screen .button.pay',\n },\n ];\n }\n }\n\n class Check {\n checkStatus(orderName, status) {\n return [\n {\n trigger: `.ticket-screen .order-row > .col:nth-child(2):contains(\"${orderName}\") ~ .col:nth-child(6):contains(${status})`,\n run: () => {},\n },\n ];\n }\n /**\n * Check if the nth row contains the given string.\n * Note that 1st row is the header-row.\n */\n nthRowContains(n, string) {\n return [\n {\n trigger: `.ticket-screen .orders > .order-row:nth-child(${n}):contains(\"${string}\")`,\n run: () => {},\n },\n ];\n }\n noNewTicketButton() {\n return [\n {\n trigger: '.ticket-screen .controls .buttons:nth-child(1):has(.discard)',\n run: () => {},\n },\n ];\n }\n orderWidgetIsNotEmpty() {\n return [\n {\n trigger: '.ticket-screen:not(:has(.order-empty))',\n run: () => {},\n },\n ];\n }\n filterIs(name) {\n return [\n {\n trigger: `.ticket-screen .pos-search-bar .filter span:contains(\"${name}\")`,\n run: () => {},\n },\n ];\n }\n partnerIs(name) {\n return [\n {\n trigger: `.ticket-screen .set-partner:contains(\"${name}\")`,\n run: () => {},\n },\n ];\n }\n toRefundTextContains(text) {\n return [\n {\n trigger: `.ticket-screen .to-refund-highlight:contains(\"${text}\")`,\n run: () => {},\n },\n ];\n }\n refundedNoteContains(text) {\n return [\n {\n trigger: `.ticket-screen .refund-note:contains(\"${text}\")`,\n run: () => {},\n },\n ];\n }\n tipContains(amount) {\n return [\n {\n trigger: `.ticket-screen .tip-cell:contains(\"${amount}\")`,\n run: () => {},\n },\n ];\n }\n }\n\n class Execute {}\n\n return createTourMethods('TicketScreen', Do, Check, Execute);\n});\n", "odoo.define('point_of_sale.tour.utils', function (require) {\n 'use strict';\n\n const config = require('web.config');\n\n /**\n * USAGE\n * -----\n *\n * ```\n * const { startSteps, getSteps, createTourMethods } = require('point_of_sale.utils');\n * const { Other } = require('point_of_sale.tour.OtherMethods');\n *\n * // 1. Define classes Do, Check and Execute having methods that\n * // each return array of tour steps.\n * class Do {\n * click() {\n * return [{ content: 'click button', trigger: '.button' }];\n * }\n * }\n * class Check {\n * isHighligted() {\n * return [{ content: 'button is highlighted', trigger: '.button.highlight', run: () => {} }];\n * }\n * }\n * // Notice that Execute has access to methods defined in Do and Check classes\n * // Also, we can compose steps from other module.\n * class Execute {\n * complexSteps() {\n * return [...this._do.click(), ...this._check.isHighlighted(), ...Other._exec.complicatedSteps()];\n * }\n * }\n *\n * // 2. Instantiate these class definitions using `createTourMethods`.\n * // The returned object gives access to the defined methods above\n * // thru the do, check and exec properties.\n * // - do gives access to the methods defined in Do class\n * // - check gives access to the methods defined in Check class\n * // - exec gives access to the methods defined in Execute class\n * const Screen = createTourMethods('Screen', Do, Check, Execute);\n *\n * // 3. Call `startSteps` to start empty steps.\n * startSteps();\n *\n * // 4. Call the tour methods to populate the steps created by `startSteps`.\n * Screen.do.click(); // return of this method call is added to steps created by startSteps\n * Screen.check.isHighlighted() // same as above\n * Screen.exec.complexSteps() // same as above\n *\n * // 5. Call `getSteps` which returns the generated tour steps.\n * const steps = getSteps();\n * ```\n */\n let steps = [];\n\n function startSteps() {\n // always start by waiting for loading to finish\n steps = [\n {\n content: 'wait for loading to finish',\n trigger: 'body:not(:has(.loader))',\n run: function () {},\n },\n ];\n }\n\n function getSteps() {\n return steps;\n }\n\n // this is the method decorator\n // when the method is called, the generated steps are added\n // to steps\n const methodProxyHandler = {\n apply(target, thisArg, args) {\n const res = target.call(thisArg, ...args);\n if (config.isDebug()) {\n // This step is added before the real steps.\n // Very useful when debugging because we know which\n // method call failed and what were the parameters.\n const constructor = thisArg.constructor.name.split(' ')[1];\n const methodName = target.name.split(' ')[1];\n const argList = args\n .map((a) => (typeof a === 'string' ? `'${a}'` : `${a}`))\n .join(', ');\n steps.push({\n content: `DOING \"${constructor}.${methodName}(${argList})\"`,\n trigger: '.pos',\n run: () => {},\n });\n }\n steps.push(...res);\n return res;\n },\n };\n\n // we proxy get of the method to decorate the method call\n const proxyHandler = {\n get(target, key) {\n const method = target[key];\n if (!method) {\n throw new Error(`Tour method '${key}' is not available.`);\n }\n return new Proxy(method.bind(target), methodProxyHandler);\n },\n };\n\n /**\n * Creates an object with `do`, `check` and `exec` properties which are instances of\n * the given `Do`, `Check` and `Execute` classes, respectively. Calling methods\n * automatically adds the returned steps to the steps created by `startSteps`.\n *\n * There are however underscored version (_do, _check, _exec).\n * Calling methods thru the underscored version does not automatically\n * add the returned steps to the current steps array. Useful when composing\n * steps from other methods.\n *\n * @param {String} name\n * @param {Function} Do class containing methods which return array of tour steps\n * @param {Function} Check similar to Do class but the steps are mainly for checking\n * @param {Function} Execute class containing methods which return array of tour steps\n * but has access to methods of Do and Check classes via .do and .check,\n * respectively. Here, we define methods that return tour steps based\n * on the combination of steps from Do and Check.\n */\n function createTourMethods(name, Do, Check = class {}, Execute = class {}) {\n Object.defineProperty(Do, 'name', { value: `${name}.do` });\n Object.defineProperty(Check, 'name', { value: `${name}.check` });\n Object.defineProperty(Execute, 'name', {\n value: `${name}.exec`,\n });\n const methods = { do: new Do(), check: new Check(), exec: new Execute() };\n // Allow Execute to have access to methods defined in Do and Check\n // via do and exec, respectively.\n methods.exec._do = methods.do;\n methods.exec._check = methods.check;\n return {\n Do,\n Check,\n Execute,\n [name]: {\n do: new Proxy(methods.do, proxyHandler),\n check: new Proxy(methods.check, proxyHandler),\n exec: new Proxy(methods.exec, proxyHandler),\n _do: methods.do,\n _check: methods.check,\n _exec: methods.exec,\n },\n };\n }\n\n return { startSteps, getSteps, createTourMethods };\n});\n", "/* global posmodel */\nodoo.define('point_of_sale.tour.pricelist', function (require) {\n \"use strict\";\n\n var Tour = require('web_tour.tour');\n var utils = require('web.utils');\n var round_di = utils.round_decimals;\n\n function assert (condition, message) {\n if (! condition) {\n throw message || \"Assertion failed\";\n }\n }\n\n function assertProductPrice(product, pricelist_name, quantity, expected_price) {\n return function () {\n var pricelist = _.findWhere(posmodel.pricelists, {name: pricelist_name});\n var frontend_price = product.get_price(pricelist, quantity);\n frontend_price = round_di(frontend_price, posmodel.dp['Product Price']);\n\n var diff = Math.abs( expected_price - frontend_price );\n\n assert(diff < 0.001,\n JSON.stringify({\n product: product.id,\n product_display_name: product.display_name,\n pricelist_name: pricelist_name,\n quantity: quantity\n }) + ' DOESN\\'T MATCH -> ' + expected_price + ' != ' + frontend_price);\n\n return Promise.resolve();\n };\n }\n\n // The global posmodel is only present when the posmodel is instanciated\n // So, wait for everythiong to be loaded\n var steps = [{ // Leave category displayed by default\n content: 'waiting for loading to finish',\n extra_trigger: 'body .pos:not(:has(.loader))', // Pos has finished loading\n trigger: 'body:not(:has(.o_loading_indicator))', // WebClient has finished Loading\n run: function () {\n var product_wall_shelf = posmodel.db.search_product_in_category(0, 'Wall Shelf Unit')[0];\n var product_small_shelf = posmodel.db.search_product_in_category(0, 'Small Shelf')[0];\n var product_magnetic_board = posmodel.db.search_product_in_category(0, 'Magnetic Board')[0];\n var product_monitor_stand = posmodel.db.search_product_in_category(0, 'Monitor Stand')[0];\n var product_desk_pad = posmodel.db.search_product_in_category(0, 'Desk Pad')[0];\n var product_letter_tray = posmodel.db.search_product_in_category(0, 'Letter Tray')[0];\n var product_whiteboard = posmodel.db.search_product_in_category(0, 'Whiteboard')[0];\n\n assertProductPrice(product_letter_tray, 'Public Pricelist', 0, 4.8)()\n .then(assertProductPrice(product_letter_tray, 'Public Pricelist', 1, 4.8))\n .then(assertProductPrice(product_letter_tray, 'Fixed', 1, 1))\n .then(assertProductPrice(product_wall_shelf, 'Fixed', 1, 2))\n .then(assertProductPrice(product_small_shelf, 'Fixed', 1, 13.95))\n .then(assertProductPrice(product_wall_shelf, 'Percentage', 1, 0))\n .then(assertProductPrice(product_small_shelf, 'Percentage', 1, 0.03))\n .then(assertProductPrice(product_magnetic_board, 'Percentage', 1, 1.98))\n .then(assertProductPrice(product_wall_shelf, 'Formula', 1, 6.86))\n .then(assertProductPrice(product_small_shelf, 'Formula', 1, 2.99))\n .then(assertProductPrice(product_magnetic_board, 'Formula', 1, 11.98))\n .then(assertProductPrice(product_monitor_stand, 'Formula', 1, 8.19))\n .then(assertProductPrice(product_desk_pad, 'Formula', 1, 6.98))\n .then(assertProductPrice(product_wall_shelf, 'min_quantity ordering', 1, 2))\n .then(assertProductPrice(product_wall_shelf, 'min_quantity ordering', 2, 1))\n .then(assertProductPrice(product_letter_tray, 'Category vs no category', 1, 2))\n .then(assertProductPrice(product_letter_tray, 'Category', 1, 2))\n .then(assertProductPrice(product_wall_shelf, 'Product template', 1, 1))\n .then(assertProductPrice(product_wall_shelf, 'Dates', 1, 2))\n .then(assertProductPrice(product_small_shelf, 'Pricelist base rounding', 1, 13.95))\n .then(assertProductPrice(product_whiteboard, 'Public Pricelist', 1, 3.2))\n .then(function () {\n $('.pos').addClass('done-testing');\n });\n },\n }, {\n trigger: '.opening-cash-control .button:contains(\"Open session\")',\n }];\n\n steps = steps.concat([{\n content: \"wait for unit tests to finish\",\n trigger: \".pos.done-testing\",\n run: function () {}, // it's a check\n }, {\n content: \"click category switch\",\n trigger: \".breadcrumb-home\",\n run: 'click',\n }, {\n content: \"click pricelist button\",\n trigger: \".control-button.o_pricelist_button\",\n }, {\n content: \"verify default pricelist is set\",\n trigger: \".selection-item.selected:contains('Public Pricelist')\",\n run: function () {}, // it's a check\n }, {\n content: \"select fixed pricelist\",\n trigger: \".selection-item:contains('Fixed')\",\n }, {\n content: \"open partner list\",\n trigger: \"button.set-partner\",\n }, {\n content: \"select Deco Addict\",\n trigger: \".partner-line:contains('Deco Addict')\",\n }, {\n content: \"click pricelist button\",\n trigger: \".control-button.o_pricelist_button\",\n }, {\n content: \"verify pricelist changed\",\n trigger: \".selection-item.selected:contains('Public Pricelist')\",\n run: function () {}, // it's a check\n }, {\n content: \"cancel pricelist dialog\",\n trigger: \".button.cancel:visible\",\n }, {\n content: \"open customer list\",\n trigger: \"button.set-partner\",\n }, {\n content: \"select Lumber Inc\",\n trigger: \".partner-line:contains('Lumber Inc')\",\n }, {\n content: \"click pricelist button\",\n trigger: \".control-button.o_pricelist_button\",\n }, {\n content: \"verify pricelist remained public pricelist ('Not loaded' is not available)\",\n trigger: \".selection-item.selected:contains('Public Pricelist')\",\n run: function () {}, // it's a check\n }, {\n content: \"cancel pricelist dialog\",\n trigger: \".button.cancel:visible\",\n }, {\n content: \"click pricelist button\",\n trigger: \".control-button.o_pricelist_button\",\n }, {\n content: \"select fixed pricelist\",\n trigger: \".selection-item:contains('min_quantity ordering')\",\n }, {\n content: \"order 1 kg shelf\",\n trigger: \".product:contains('Wall Shelf')\",\n }, {\n content: \"change qty to 2 kg\",\n trigger: \".numpad button.input-button:visible:contains('2')\",\n }, {\n content: \"qty of Wall Shelf line should be 2\",\n trigger: \".order-container .orderlines .orderline.selected .product-name:contains('Wall Shelf')\",\n extra_trigger: \".order-container .orderlines .orderline.selected .product-name:contains('Wall Shelf') ~ .info-list .info em:contains('2.0')\",\n run: function() {},\n }, {\n content: \"verify that unit price of shelf changed to $1\",\n trigger: \".total > .value:contains('$ 2.00')\",\n run: function() {},\n }, {\n content: \"order different shelf\",\n trigger: \".product:contains('Small Shelf')\",\n }, {\n content: \"Small Shelf line should be selected with quantity 1\",\n trigger: \".order-container .orderlines .orderline.selected .product-name:contains('Small Shelf')\",\n extra_trigger: \".order-container .orderlines .orderline.selected .product-name:contains('Small Shelf') ~ .info-list .info em:contains('1.0')\",\n run: function() {}\n }, {\n content: \"change to price mode\",\n trigger: \".numpad button:contains('Price')\",\n }, {\n content: \"make sure price mode is activated\",\n trigger: \".numpad button.selected-mode:contains('Price')\",\n run: function() {},\n }, {\n content: \"manually override the unit price of these shelf to $5\",\n trigger: \".numpad button.input-button:visible:contains('5')\",\n }, {\n content: \"Small Shelf line should be selected with unit price of 5\",\n trigger: \".order-container .orderlines .orderline.selected .product-name:contains('Small Shelf')\",\n extra_trigger: \".order-container .orderlines .orderline.selected .product-name:contains('Small Shelf') ~ .price:contains('5.0')\",\n }, {\n content: \"change back to qty mode\",\n trigger: \".numpad button:contains('Qty')\",\n }, {\n content: \"make sure qty mode is activated\",\n trigger: \".numpad button.selected-mode:contains('Qty')\",\n run: function() {},\n }, {\n content: \"click pricelist button\",\n trigger: \".control-button.o_pricelist_button\",\n }, {\n content: \"select public pricelist\",\n trigger: \".selection-item:contains('Public Pricelist')\",\n }, {\n content: \"verify that the boni shelf have been recomputed and the shelf have not (their price was manually overridden)\",\n trigger: \".total > .value:contains('$ 8.96')\",\n }, {\n content: \"click pricelist button\",\n trigger: \".control-button.o_pricelist_button\",\n }, {\n content: \"select fixed pricelist\",\n trigger: \".selection-item:contains('min_quantity ordering')\",\n }, {\n content: \"close the Point of Sale frontend\",\n trigger: \".header-button\",\n }, {\n content: \"confirm closing the frontend\",\n trigger: \".header-button\",\n run: function() {}, //it's a check,\n }]);\n\n Tour.register('pos_pricelist', { test: true, url: '/pos/ui' }, steps);\n});\n\nodoo.define('point_of_sale.tour.acceptance', function (require) {\n \"use strict\";\n\n var Tour = require(\"web_tour.tour\");\n\n function add_product_to_order(product_name) {\n return [{\n content: 'buy ' + product_name,\n trigger: '.product-list .product-name:contains(\"' + product_name + '\")',\n }, {\n content: 'the ' + product_name + ' have been added to the order',\n trigger: '.order .product-name:contains(\"' + product_name + '\")',\n run: function () {},\n }];\n }\n\n function set_fiscal_position_on_order(fp_name) {\n return [{\n content: 'set fiscal position',\n trigger: '.control-button.o_fiscal_position_button',\n }, {\n content: 'choose fiscal position ' + fp_name + ' to add to the order',\n trigger: '.popups .popup .selection .selection-item:contains(\"' + fp_name + '\")',\n }, {\n content: 'the fiscal position ' + fp_name + ' has been set to the order',\n trigger: '.control-button.o_fiscal_position_button:contains(\"' + fp_name + '\")',\n run: function () {},\n }];\n }\n\n function press_payment_numpad(val) {\n return [{\n content: `press ${val} on payment screen numpad`,\n trigger: `.payment-numpad .input-button:contains(\"${val}\"):visible`,\n }]\n }\n\n function press_product_numpad(val) {\n return [{\n content: `press ${val} on product screen numpad`,\n trigger: `.numpad .input-button:contains(\"${val}\"):visible`,\n }]\n }\n\n function selected_payment_has(name, val) {\n return [{\n content: `selected payment is ${name} and has ${val}`,\n trigger: `.paymentlines .paymentline.selected .payment-name:contains(\"${name}\")`,\n extra_trigger: `.paymentlines .paymentline.selected .payment-name:contains(\"${name}\") ~ .payment-amount:contains(\"${val}\")`,\n run: function () {},\n }]\n }\n\n function selected_orderline_has({ product, price = null, quantity = null }) {\n const result = [];\n if (price !== null) {\n result.push({\n content: `Selected line has product '${product}' and price '${price}'`,\n trigger: `.order-container .orderlines .orderline.selected .product-name:contains(\"${product}\") ~ span.price:contains(\"${price}\")`,\n run: function () {},\n });\n }\n if (quantity !== null) {\n result.push({\n content: `Selected line has product '${product}' and quantity '${quantity}'`,\n trigger: `.order-container .orderlines .orderline.selected .product-name:contains('${product}') ~ .info-list .info em:contains('${quantity}')`,\n run: function () {},\n });\n }\n return result;\n }\n\n function verify_order_total(total_str) {\n return [{\n content: 'order total contains ' + total_str,\n trigger: '.order .total .value:contains(\"' + total_str + '\")',\n run: function () {}, // it's a check\n }];\n }\n\n function goto_payment_screen_and_select_payment_method() {\n return [{\n content: \"go to payment screen\",\n trigger: '.button.pay',\n }, {\n content: \"pay with cash\",\n trigger: '.paymentmethod:contains(\"Cash\")',\n }];\n }\n\n function finish_order() {\n return [{\n content: \"validate the order\",\n trigger: '.payment-screen .button.next.highlight:visible',\n }, {\n content: \"verify that the order has been successfully sent to the backend\",\n trigger: \".js_connected:visible\",\n run: function () {},\n }, {\n content: \"click Next Order\",\n trigger: '.receipt-screen .button.next.highlight:visible',\n }, {\n content: \"check if we left the receipt screen\",\n trigger: '.pos-content .screen:not(:has(.receipt-screen))',\n run: function () {},\n }];\n }\n\n var steps = [{\n content: 'waiting for loading to finish',\n trigger: 'body:not(:has(.loader))',\n run: function () {},\n }, { // Leave category displayed by default\n content: \"click category switch\",\n trigger: \".breadcrumb-home\",\n }];\n\n steps = steps.concat(add_product_to_order('Desk Organizer'));\n steps = steps.concat(verify_order_total('5.10'));\n\n steps = steps.concat(add_product_to_order('Desk Organizer'));\n steps = steps.concat(verify_order_total('10.20'));\n steps = steps.concat(goto_payment_screen_and_select_payment_method());\n\n /* add payment line of only 5.20\n status:\n order-total := 10.20\n total-payment := 11.70\n expect:\n remaining := 0.00\n change := 1.50\n */\n steps = steps.concat(press_payment_numpad('5'));\n steps = steps.concat(selected_payment_has('Cash', '5.0'));\n steps = steps.concat([{\n content: \"verify remaining\",\n trigger: '.payment-status-remaining .amount:contains(\"5.20\")',\n run: function () {},\n }, {\n content: \"verify change\",\n trigger: '.payment-status-change .amount:contains(\"0.00\")',\n run: function () {},\n }]);\n\n /* make additional payment line of 6.50\n status:\n order-total := 10.20\n total-payment := 11.70\n expect:\n remaining := 0.00\n change := 1.50\n */\n steps = steps.concat([{\n content: \"pay with cash\",\n trigger: '.paymentmethod:contains(\"Cash\")',\n }]);\n steps = steps.concat(selected_payment_has('Cash', '5.2'));\n steps = steps.concat(press_payment_numpad('6'))\n steps = steps.concat(selected_payment_has('Cash', '6.0'));\n steps = steps.concat([{\n content: \"verify remaining\",\n trigger: '.payment-status-remaining .amount:contains(\"0.00\")',\n run: function () {},\n }, {\n content: \"verify change\",\n trigger: '.payment-status-change .amount:contains(\"0.80\")',\n run: function () {},\n }]);\n\n steps = steps.concat(finish_order());\n\n // test opw-672118 orderline subtotal rounding\n steps = steps.concat(add_product_to_order('Desk Organizer'));\n steps = steps.concat(selected_orderline_has({product: 'Desk Organizer', quantity: '1.0'}));\n steps = steps.concat(press_product_numpad('.'))\n steps = steps.concat(selected_orderline_has({product: 'Desk Organizer', quantity: '0.0', price: '0.0'}));\n steps = steps.concat(press_product_numpad('9'))\n steps = steps.concat(selected_orderline_has({product: 'Desk Organizer', quantity: '0.9', price: '4.59'}));\n steps = steps.concat(press_product_numpad('9'))\n steps = steps.concat(selected_orderline_has({product: 'Desk Organizer', quantity: '0.99', price: '5.05'}));\n steps = steps.concat(goto_payment_screen_and_select_payment_method());\n steps = steps.concat(selected_payment_has('Cash', '5.05'));\n steps = steps.concat(finish_order());\n\n // Test fiscal position one2many map (align with backend)\n steps = steps.concat(add_product_to_order('Letter Tray'));\n steps = steps.concat(selected_orderline_has({product: 'Letter Tray', quantity: '1.0'}));\n steps = steps.concat(verify_order_total('5.28'));\n steps = steps.concat(set_fiscal_position_on_order('FP-POS-2M'));\n steps = steps.concat(verify_order_total('5.52'));\n\n steps = steps.concat([{\n content: \"open closing the Point of Sale frontend popup\",\n trigger: \".header-button\",\n }, {\n content: \"close the Point of Sale frontend\",\n trigger: \".close-pos-popup .button:contains('Discard')\",\n run: function() {}, //it's a check,\n }]);\n\n Tour.register('pos_basic_order', { test: true, url: '/pos/ui' }, steps);\n\n});\n", "/** @odoo-module **/\nimport { browser } from \"@web/core/browser/browser\";\nconst localStorage = browser.localStorage;\n\nimport publicWidget from \"web.public.widget\";\n\nconst localStorageKey = 'widgetAndWysiwygLifecycle';\nif (!localStorage.getItem(localStorageKey)) {\n localStorage.setItem(localStorageKey, '[]');\n}\n\nexport function addLifecycleStep(step) {\n const oldValue = localStorage.getItem(localStorageKey);\n const newValue = JSON.stringify(JSON.parse(oldValue).concat(step));\n localStorage.setItem(localStorageKey, newValue);\n}\n\npublicWidget.registry.CountdownPatch = publicWidget.Widget.extend({\n selector: \".s_countdown\",\n disabledInEditableMode: false,\n\n /**\n * @override\n */\n async start() {\n addLifecycleStep('widgetStart');\n await this._super(...arguments);\n this.el.classList.add(\"public_widget_started\");\n },\n /**\n * @override\n */\n destroy() {\n this.el.classList.remove(\"public_widget_started\");\n addLifecycleStep('widgetStop');\n this._super(...arguments);\n },\n});\n", "odoo.define('website.tour.automatic_editor', function (require) {\n'use strict';\n\nconst wTourUtils = require(\"website.tour_utils\");\n\nwTourUtils.registerWebsitePreviewTour('automatic_editor_on_new_website', {\n test: true,\n url: '/',\n},\n[\n {\n content: \"Select the language dropdown\",\n trigger: 'iframe .js_language_selector .dropdown-toggle'\n },\n {\n content: \"click on Add a language\",\n trigger: 'iframe a.o_add_language',\n },\n {\n content: \"type Parseltongue\",\n trigger: 'div[name=\"lang_ids\"] .o_input_dropdown input',\n run: 'text Parseltongue',\n },\n {\n content: 'select Parseltongue',\n trigger: '.dropdown-item:contains(Parseltongue)',\n in_modal: false,\n },\n {\n content: \"load parseltongue\",\n extra_trigger: '.modal div[name=\"lang_ids\"] .rounded-pill .o_tag_badge_text:contains(Parseltongue)',\n trigger: '.modal-footer button[name=lang_install]',\n },\n {\n content: \"Select the language dropdown\",\n trigger: 'iframe .js_language_selector .dropdown-toggle',\n },\n {\n content: \"Select parseltongue\",\n trigger: 'iframe a.js_change_lang[data-url_code=pa_GB]',\n extra_trigger: 'iframe a.js_change_lang .o_lang_flag',\n },\n {\n content: \"Check that we're on parseltongue and then go to settings\",\n trigger: 'iframe html[lang=pa-GB]',\n run: () => {\n // Now go through the settings for a new website. A frontend_lang\n // cookie was set during previous steps. It should not be used when\n // redirecting to the frontend in the following steps.\n window.location.href = '/web#action=website.action_website_configuration';\n }\n },\n {\n content: \"create a new website\",\n trigger: 'button[name=\"action_website_create_new\"]',\n },\n {\n content: \"insert website name\",\n trigger: 'div[name=\"name\"] input',\n run: 'text Website EN'\n },\n {\n content: \"validate the website creation modal\",\n trigger: 'button.btn-primary'\n },\n {\n content: \"skip configurator\",\n // This trigger targets the skip button, it doesn't have a more\n // explicit class or ID.\n trigger: '.o_configurator_container .container-fluid .btn.btn-link'\n },\n {\n content: \"make hover button appear\",\n trigger: '.o_theme_preview',\n run: () => {\n $('.o_theme_preview .o_button_area').attr('style', 'visibility: visible; opacity: 1;');\n },\n },\n {\n content: \"Install a theme\",\n trigger: 'button[name=\"button_choose_theme\"]'\n },\n {\n content: \"Check that the editor is loaded\",\n trigger: 'iframe body.editor_enable',\n timeout: 30000,\n run: () => null, // it's a check\n },\n {\n content: \"exit edit mode\",\n trigger: '.o_we_website_top_actions button.btn-primary:contains(\"Save\")',\n },\n {\n content: \"wait for editor to close\",\n trigger: 'iframe body:not(.editor_enable)',\n run: () => null, // It's a check\n }\n]);\n});\n", "/** @odoo-module */\n\nimport wTourUtils from 'website.tour_utils';\n\nconst carouselInnerSelector = \"iframe .carousel-inner\";\n\nwTourUtils.registerWebsitePreviewTour(\"carousel_content_removal\", {\n test: true,\n url: '/',\n edition: true,\n}, [{\n trigger: \"#snippet_structure .oe_snippet:has(span:contains('Carousel')) .oe_snippet_thumbnail\",\n content: \"Drag the Carousel block and drop it in your page.\",\n run: \"drag_and_drop iframe #wrap\",\n},\n{\n trigger: \"iframe .carousel .carousel-item.active .carousel-content\",\n content: \"Select the active carousel item.\",\n}, {\n trigger: \"iframe .oe_overlay.oe_active .oe_snippet_remove\",\n content: \"Remove the active carousel item.\",\n},\n{\n trigger: \"iframe .carousel .carousel-item.active .container:not(:has(*))\",\n content: \"Check for a carousel slide with an empty container tag\",\n run: function () {},\n}]);\n\nwTourUtils.registerWebsitePreviewTour(\"snippet_carousel\", {\n test: true,\n url: \"/\",\n edition: true,\n}, [\n wTourUtils.dragNDrop({id: \"s_carousel\", name: \"Carousel\"}),\n wTourUtils.clickOnSnippet(\".carousel .carousel-item.active\"),\n // Slide to the right.\n wTourUtils.changeOption(\"CarouselItem\", 'we-button[data-switch-to-slide=\"right\"]'),\n {\n content: \"Check if the second slide is active\",\n trigger: `${carouselInnerSelector} > div.active:nth-child(2)`,\n run: () => {}, // This is a check.\n },\n // Add a slide (with the \"CarouselItem\" option).\n wTourUtils.changeOption(\"CarouselItem\", \"we-button[data-add-slide-item]\"),\n {\n content: \"Check if there are four slides and if the third one is active\",\n trigger: `${carouselInnerSelector}:has(div:nth-child(4)) > div.active:nth-child(3)`,\n run: () => {}, // This is a check.\n },\n // Remove a slide.\n wTourUtils.changeOption(\"CarouselItem\", \"we-button[data-remove-slide]\"),\n {\n content: \"Check if there are three slides and if the second one is active\",\n trigger: `${carouselInnerSelector}:has(div:nth-child(3)) > div.active:nth-child(2)`,\n run: () => {}, // This is a check.\n }, {\n trigger: \"iframe .carousel .carousel-control-prev\",\n content: \"Slide the carousel to the left with the arrows.\",\n }, {\n content: \"Check if the first slide is active\",\n trigger: `${carouselInnerSelector} > div.active:nth-child(1)`,\n run: () => {}, // This is a check.\n },\n // Add a slide (with the \"Carousel\" option).\n wTourUtils.changeOption(\"Carousel\", \"we-button[data-add-slide]\"),\n {\n content: \"Check if there are four slides and if the second one is active\",\n trigger: `${carouselInnerSelector}:has(div:nth-child(4)) > div.active:nth-child(2)`,\n run: () => {}, // This is a check.\n }, {\n content: \"Check if the slide indicator was correctly updated\",\n trigger: \"we-customizeblock-options span:contains(' (2/4)')\",\n run: () => {},\n },\n // Check if we can still remove a slide.\n wTourUtils.changeOption(\"CarouselItem\", \"we-button[data-remove-slide]\"),\n {\n content: \"Check if there are three slides and if the first one is active\",\n trigger: `${carouselInnerSelector}:has(div:nth-child(3)) > div.active:nth-child(1)`,\n run: () => {}, // This is a check.\n },\n // Slide to the left.\n wTourUtils.changeOption(\"CarouselItem\", 'we-button[data-switch-to-slide=\"left\"]'),\n {\n content: \"Check if the third slide is active\",\n trigger: `${carouselInnerSelector} > div.active:nth-child(3)`,\n run: () => {}, // This is a check.\n },\n ...wTourUtils.clickOnSave(),\n // Check that saving always sets the first slide as active.\n {\n content: \"Check that the first slide became the active one\",\n trigger: `${carouselInnerSelector} > div.active:nth-child(1)`,\n run: () => {}, // This is a check.\n },\n]);\n", "/** @odoo-module */\n\nimport wTourUtils from 'website.tour_utils';\n\nwTourUtils.registerWebsitePreviewTour('client_action_iframe_fallback', {\n test: true,\n url: '/',\n},\n[\n {\n content: \"Ensure we are on the expected page\",\n trigger: 'body iframe html[data-view-xmlid=\"website.homepage\"]',\n run: () => {}, // It's a check.\n }, {\n content: \"Ensure the iframe fallback is not loaded in test mode\",\n trigger: 'body',\n run: () => {\n if (document.querySelector('iframe[src=\"/website/iframefallback\"]')) {\n console.error(\"The iframe fallback shouldn't be inside the DOM.\");\n }\n },\n },\n]);\n", "/** @odoo-module */\n\nimport tour from 'web_tour.tour';\n\nconst testUrl = '/test_client_action_redirect';\n\nconst goToFrontendSteps = [{\n content: \"Go to the frontend\",\n trigger: 'body',\n run: () => {\n window.location.href = testUrl;\n },\n}, {\n content: \"Check we are in the frontend\",\n trigger: 'body:not(:has(.o_website_preview)) #test_contact_FE',\n run: () => null, // it's a check\n}];\nconst goToBackendSteps = [{\n content: \"Go to the backend\",\n trigger: 'body',\n run: () => {\n window.location.href = `/@${testUrl}`;\n },\n}, {\n content: \"Check we are in the backend\",\n trigger: '.o_website_preview',\n run: () => null, // it's a check\n}];\nconst checkEditorSteps = [{\n content: \"Check that the editor is loaded\",\n trigger: 'iframe body.editor_enable',\n timeout: 30000,\n run: () => null, // it's a check\n}, {\n content: \"exit edit mode\",\n trigger: '.o_we_website_top_actions button.btn-primary:contains(\"Save\")',\n}, {\n content: \"wait for editor to close\",\n trigger: 'iframe body:not(.editor_enable)',\n run: () => null, // It's a check\n}];\n\ntour.register('client_action_redirect', {\n test: true,\n url: testUrl,\n},\n[\n // Case 1: From frontend, click on `enable_editor=1` link without `/@/` in it\n ...goToFrontendSteps,\n {\n content: \"Click on the link to frontend\",\n trigger: '#test_contact_FE',\n },\n ...checkEditorSteps,\n\n // Case 2: From frontend, click on `enable_editor=1` link with `/@/` in it\n ...goToFrontendSteps,\n {\n content: \"Click on the link to backend\",\n trigger: '#test_contact_BE',\n },\n ...checkEditorSteps,\n\n // Case 3: From backend, click on `enable_editor=1` link without `/@/` in it\n // TODO: This will be fixed in another fix related to the listening of the\n // URL changes from the client action.\n // ...goToBackendSteps,\n // {\n // content: \"Click on the link to frontend (2)\",\n // trigger: 'iframe #test_contact_FR',\n // },\n // ...checkEditorSteps,\n\n // Case 4: From backend, click on `enable_editor=1` link with `/@/` in it\n ...goToBackendSteps,\n {\n content: \"Click on the link to backend (2)\",\n trigger: 'iframe #test_contact_BE',\n },\n ...checkEditorSteps,\n]);\n", "/** @odoo-module */\n\nimport wTourUtils from 'website.tour_utils';\n\nconst snippets = [\n {\n id: 's_text_image',\n name: 'Text - Image',\n },\n {\n id: \"s_banner\",\n name: \"Banner\",\n },\n {\n id: \"s_popup\",\n name: \"Popup\",\n },\n];\nfunction checkEyeIcon(snippetName, visible) {\n const eyeIcon = visible ? \"fa-eye\" : \"fa-eye-slash\";\n const openOrClose = visible ? \"open\" : \"close\";\n const endExplanation = `should be ${openOrClose} in the \"Invisible Elements\" panel`;\n const invisibleElPanel = \"o_we_invisible_el_panel\";\n return {\n content: `The eye icon of ${snippetName} ${endExplanation}`,\n trigger:\n `.${invisibleElPanel} .o_we_invisible_entry:contains(\"${snippetName}\") i.${eyeIcon}`,\n run: () => {}, // it is a check\n };\n}\nfunction checkEyesIconAfterSave(footerIsHidden = true) {\n const eyeIconChecks = [\n checkEyeIcon(\"Header\", false),\n checkEyeIcon(\"Text - Image\", true),\n checkEyeIcon(\"Popup\", false),\n checkEyeIcon(\"Banner\", true),\n ];\n if (footerIsHidden) {\n eyeIconChecks.push(checkEyeIcon(\"Footer\", false));\n }\n return eyeIconChecks;\n}\nwTourUtils.registerWebsitePreviewTour('conditional_visibility_1', {\n edition: true,\n url: '/',\n test: true,\n}, [\nwTourUtils.dragNDrop(snippets[0]),\nwTourUtils.clickOnSnippet(snippets[0]),\nwTourUtils.changeOption('ConditionalVisibility', 'we-toggler'),\n{\n content: 'click on conditional visibility',\n trigger: '[data-name=\"visibility_conditional\"]',\n run: 'click',\n},\n{\n content: 'click on utm medium toggler',\n trigger: '[data-save-attribute=\"visibilityValueUtmMedium\"] we-toggler',\n run: 'click',\n},\n{\n trigger: '[data-save-attribute=\"visibilityValueUtmMedium\"] we-selection-items .o_we_m2o_search input',\n content: 'Search for Email',\n run: 'text Email',\n},\n{\n trigger: '[data-save-attribute=\"visibilityValueUtmMedium\"] we-selection-items [data-add-record=\"Email\"]',\n content: 'click on Email',\n run: 'click',\n},\n...wTourUtils.clickOnSave(),\n{\n content: 'Check if the rule was applied',\n extra_trigger: '.o_website_preview:only-child',\n trigger: 'iframe #wrap',\n run: function (actions) {\n const style = window.getComputedStyle(this.$anchor[0].getElementsByClassName('s_text_image')[0]);\n if (style.display !== 'none') {\n console.error('error This item should be invisible and only visible if utm_medium === email');\n }\n },\n},\n...wTourUtils.clickOnEditAndWaitEditMode(),\n{\n content: 'Check if the element is visible as it should always be visible in edit view',\n trigger: 'iframe #wrap .s_text_image',\n run: function (actions) {\n const style = window.getComputedStyle((this.$anchor[0]));\n if (style.display === 'none') {\n console.error('error This item should now be visible because utm_medium === email');\n }\n },\n},\n]);\n\nwTourUtils.registerWebsitePreviewTour(\"conditional_visibility_3\", {\n edition: true,\n test: true,\n url: \"/\",\n},\n[\ncheckEyeIcon(\"Text - Image\", true),\n// Drag a \"Banner\" snippet on the website.\nwTourUtils.dragNDrop(snippets[1]),\n// Click on the \"Banner\" snippet.\nwTourUtils.clickOnSnippet(snippets[1]),\nwTourUtils.changeOption(\"ConditionalVisibility\", \"we-toggler\"),\nwTourUtils.changeOption(\"ConditionalVisibility\", '[data-name=\"visibility_conditional\"]'),\ncheckEyeIcon(\"Banner\", true),\n{\n content: \"click on 'Blocks'\",\n trigger: \"#snippets_menu button:contains('Blocks')\",\n},\n// Drag a \"Popup\" snippet on the website.\nwTourUtils.dragNDrop(snippets[2]),\n{\n content: \"Toggle the visibility of the popup\",\n in_modal: false,\n trigger: \".o_we_invisible_el_panel .o_we_invisible_entry:contains('Popup')\",\n},\ncheckEyeIcon(\"Popup\", false),\n{\n content: \"Click on footer\",\n trigger: \"iframe #wrapwrap footer\",\n},\nwTourUtils.changeOption(\"HideFooter\", \"we-checkbox\"),\ncheckEyeIcon(\"Footer\", false),\n{\n content: \"Click on Header\",\n trigger: \"iframe #wrapwrap header\",\n},\nwTourUtils.changeOption(\"TopMenuVisibility\", \"we-toggler\"),\nwTourUtils.changeOption(\"TopMenuVisibility\", '[data-visibility=\"hidden\"]'),\ncheckEyeIcon(\"Header\", false),\n{\n content: \"Toggle the visibility of the Banner snippet\",\n trigger: \".o_we_invisible_el_panel .o_we_invisible_entry:contains('Banner')\",\n},\ncheckEyeIcon(\"Banner\", false),\n...wTourUtils.clickOnSave(),\n...wTourUtils.clickOnEditAndWaitEditMode(),\n...checkEyesIconAfterSave(),\n]);\n\nwTourUtils.registerWebsitePreviewTour(\"conditional_visibility_4\", {\n edition: true,\n test: true,\n url: \"/\",\n},\n[\n// Click on the \"Text-Image\" snippet.\nwTourUtils.clickOnSnippet(snippets[0]),\n{\n content: \"Click on the 'move down' option\",\n trigger: \"iframe we-button.o_we_user_value_widget.fa-angle-down\",\n},\n...checkEyesIconAfterSave(),\n{\n content: \"Check the order on the 'Invisible Elements' panel\",\n trigger: \".o_we_invisible_el_panel div:nth-child(3):contains('Banner')\",\n run: () => {}, // it is a check\n},\n{\n content: \"Toggle the visibility of the Footer\",\n trigger: \".o_we_invisible_el_panel .o_we_invisible_entry:contains('Footer')\",\n},\n{\n content: \"Check that the footer is visible\",\n trigger: \"iframe #wrapwrap footer\",\n run: () => {}, // it is a check\n},\n// Click on the \"Banner\" snippet.\nwTourUtils.clickOnSnippet(snippets[1]),\n{\n content: \"Drag the 'Banner' snippet to the end of the page\",\n trigger: \"iframe .o_overlay_move_options .ui-draggable-handle\",\n run: \"drag_and_drop iframe #wrapwrap footer\",\n},\n...checkEyesIconAfterSave(false),\n{\n content: \"Check the order on the 'Invisible Elements' panel\",\n trigger: \".o_we_invisible_el_panel div:nth-child(3):contains('Text - Image')\",\n run: () => {}, // it is a check\n},\n]);\n\nwTourUtils.registerWebsitePreviewTour(\"conditional_visibility_5\", {\n edition: true,\n test: true,\n url: \"/\",\n}, [\n wTourUtils.dragNDrop(snippets[0]),\n {\n content: \"Click on the image of the dragged snippet\",\n trigger: \"iframe .s_text_image img\",\n },\n wTourUtils.changeOption(\"DeviceVisibility\", 'we-button[data-toggle-device-visibility=\"no_desktop\"]'),\n {\n content: \"Check that the Column has been added in the 'Invisible Elements' panel\",\n trigger: \".o_we_invisible_el_panel .o_we_invisible_entry:contains('Column')\",\n run: () => {}, // it is a check\n },\n {\n content: \"Click on the 'Text - Image' snippet\",\n trigger: \"iframe .s_text_image\",\n },\n wTourUtils.changeOption(\"ConditionalVisibility\", 'we-button[data-toggle-device-visibility=\"no_desktop\"]'),\n {\n content: \"Check that the 'Text - Image' is the parent of 'Column' in the 'Invisible Elements' panel\",\n trigger: \".o_we_invisible_el_panel .o_we_invisible_root_parent.o_we_invisible_entry:contains('Text - Image') + ul .o_we_invisible_entry.o_we_sublevel_1:contains('Column')\",\n run: () => {}, // it is a check\n },\n {\n content: \"Click on the 'Text - Image' entry on the 'Invisible Elements' panel\",\n trigger: \".o_we_invisible_el_panel .o_we_invisible_root_parent.o_we_invisible_entry:contains('Text - Image')\",\n },\n {\n content: \"Check that the snippet is visible on the website\",\n trigger: \"iframe .s_text_image.o_snippet_desktop_invisible.o_snippet_override_invisible\",\n run: () => {}, // it is a check\n },\n wTourUtils.changeOption(\"ConditionalVisibility\", 'we-button[data-toggle-device-visibility=\"no_mobile\"]'),\n {\n content: \"Check that the 'Text - Image' has been removed from the 'Invisible Elements' panel\",\n trigger: \".o_we_invisible_el_panel:not(.o_we_invisible_entry:contains('Text - Image'))\",\n run: () => {}, // it is a check\n },\n {\n content: \"Click on the 'Column' entry on the 'Invisible Elements' panel\",\n trigger: \".o_we_invisible_el_panel .o_we_invisible_entry:contains('Column')\",\n },\n {\n content: \"Check that the column is visible on the website\",\n trigger: \"iframe .s_text_image .row > .o_snippet_desktop_invisible.o_snippet_override_invisible\",\n run: () => {}, // it is a check\n },\n wTourUtils.changeOption(\"DeviceVisibility\", 'we-button[data-toggle-device-visibility=\"no_mobile\"]'),\n {\n content: \"Check that the column has been removed from the 'Invisible Elements' panel\",\n trigger: \"#oe_snippets:not(:has(.o_we_invisible_entry:contains('Column')))\",\n run: () => {}, // it is a check\n },\n {\n content: \"Activate mobile preview\",\n trigger: \".o_we_website_top_actions button[data-action='mobile']\",\n },\n {\n content: \"Check that the 'Text - Image' is the parent of 'Column' in the 'Invisible Elements' panel\",\n trigger: \".o_we_invisible_el_panel .o_we_invisible_root_parent.o_we_invisible_entry:contains('Text - Image') + ul .o_we_invisible_entry.o_we_sublevel_1:contains('Column')\",\n run: () => {}, // it is a check\n },\n]);\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\n\ntour.register('conditional_visibility_2', {\n test: true,\n url: '/?utm_medium=Email',\n},\n[{\n content: 'The content previously hidden should now be visible',\n trigger: 'body #wrap',\n run: function (actions) {\n const style = window.getComputedStyle(this.$anchor[0].getElementsByClassName('s_text_image')[0]);\n if (style.display === 'none') {\n console.error('error This item should now be visible because utm_medium === email');\n }\n },\n},\n]);\n", "/** @odoo-module */\n\nimport tour from 'web_tour.tour';\n\ntour.register('configurator_translation', {\n test: true,\n url: '/website/configurator',\n},\n[\n // Configurator first screen\n {\n content: \"click next\",\n trigger: 'button.o_configurator_show',\n },\n // Make sure \"Back\" works\n {\n content: \"use browser's Back\",\n trigger: 'a.o_change_website_type',\n run: () => {\n window.history.back();\n },\n }, {\n content: \"return to description screen\",\n trigger: 'button.o_configurator_show',\n },\n // Description screen\n {\n content: \"select a website type\",\n trigger: 'a.o_change_website_type',\n }, {\n content: \"insert a website industry\",\n trigger: '.o_configurator_industry input',\n run: 'text ab',\n }, {\n content: \"select a website industry from the autocomplete\",\n trigger: '.o_configurator_industry_wrapper ul li a:contains(\"in fr\")',\n }, {\n content: \"select an objective\",\n trigger: '.o_configurator_purpose_dd a',\n }, {\n content: \"choose from the objective list\",\n trigger: 'a.o_change_website_purpose',\n },\n // Palette screen\n {\n content: \"chose a palette card\",\n trigger: '.palette_card',\n },\n // Features screen\n {\n content: \"select confidentialit\u00e9\",\n trigger: '.card:contains(Parseltongue_privacy)',\n }, {\n content: \"Click on build my website\",\n trigger: 'button.btn-primary',\n }, {\n content: \"Loader should be shown\",\n trigger: '.o_website_loader_container',\n run: function () {}, // it's a check\n }, {\n content: \"Wait untill the configurator is finished\",\n trigger: '#oe_snippets.o_loaded',\n timeout: 30000,\n }, {\n // Check the content of the save button to make sure the website is in\n // Parseltongue. (The editor should be in the website's default language,\n // which should be parseltongue in this test.)\n content: \"exit edit mode\",\n trigger: '.o_we_website_top_actions button.btn-primary:contains(\"Save_Parseltongue\")',\n }, {\n content: \"wait for editor to be closed\",\n trigger: 'iframe body:not(.editor_enable)',\n run: function () {}, // It's a check.\n }\n]);\n", "/** @odoo-module */\n\nimport wTourUtils from \"website.tour_utils\";\n\nconst snippets = [\n { id: \"s_popup\", name: \"Popup\" },\n { id: \"s_popup\", name: \"Custom Popup\" },\n];\nwTourUtils.registerWebsitePreviewTour(\n \"custom_popup_snippet\",\n {\n test: true,\n url: \"/\",\n edition: true,\n },\n [\n wTourUtils.dragNDrop(snippets[0]),\n wTourUtils.clickOnSnippet(snippets[0]),\n {\n content: \"save this snippet to save later\",\n trigger: \".o_we_user_value_widget.fa-save\",\n },\n {\n content: \"confirm and reload custom snippet\",\n trigger: \".modal-footer > .btn.btn-primary\",\n },\n wTourUtils.dragNDrop(snippets[1]),\n {\n content: \"check whether new custom popup is visible or not.\",\n trigger: \"iframe #wrap.o_editable [data-name='Custom Popup']\",\n run: () => {}, // This is a check\n },\n ]\n);\n", "odoo.define(\"website.tour.default_shape_gets_palette_colors\", function (require) {\n\"use strict\";\n\nconst wTourUtils = require('website.tour_utils');\n\nwTourUtils.registerWebsitePreviewTour(\"default_shape_gets_palette_colors\", {\n test: true,\n url: '/',\n edition: true,\n}, [\n wTourUtils.dragNDrop({\n id: 's_text_image',\n name: 'Text - Image',\n }),\n wTourUtils.clickOnSnippet({\n id: 's_text_image',\n name: 'Text - Image',\n }),\n wTourUtils.changeOption('ColoredLevelBackground', 'Shape'),\n {\n content: \"Check that shape does not have a background-image in its inline style\",\n trigger: 'iframe #wrap .s_text_image .o_we_shape',\n run: () => {\n const shape = $('iframe:not(.o_ignore_in_tour)').contents().find('#wrap .s_text_image .o_we_shape')[0];\n if (shape.style.backgroundImage) {\n console.error(\"error The default shape has a background-image in its inline style (should rely on the class)\");\n }\n },\n },\n]);\n});\n", "/** @odoo-module **/\n\nimport wTourUtils from \"website.tour_utils\";\n\nwTourUtils.registerWebsitePreviewTour(\"test_drag_and_drop_on_non_editable\", {\n test: true,\n url: \"/\",\n edition: true,\n}, [\n wTourUtils.dragNDrop({\n id: \"s_company_team\",\n name: \"Team\",\n }),\n {\n content: \"Click on an editable media in non editable env.\",\n trigger: \"iframe .s_company_team .o_not_editable > .o_editable_media\",\n },\n wTourUtils.goBackToBlocks(),\n Object.assign(wTourUtils.dragNDrop({\n id: \"s_text_highlight\",\n name: \"Text Highlight\",\n }), {\n content: \"Drag and drop the Text Highlight building block next to the Team block media.\",\n run: \"drag_and_drop iframe .s_company_team .o_not_editable > .o_editable_media\",\n }),\n {\n content: \"Verify that the Text Highlight building block isn't in a non editable element.\",\n trigger: \"iframe .s_company_team :not(.o_not_editable) > .s_text_highlight\",\n run: () => null, // it's a check\n },\n]);\n", "odoo.define(\"website.tour.edit_link_popover\", function (require) {\n\"use strict\";\n\nconst { browser } = require('@web/core/browser/browser');\nconst { patch } = require(\"@web/core/utils/patch\");\nconst wTourUtils = require('website.tour_utils');\n\nconst FIRST_PARAGRAPH = 'iframe #wrap .s_text_image p:nth-child(2)';\n\nconst clickFooter = [{\n content: \"Save the link by clicking outside the URL input (not on a link element)\",\n trigger: 'iframe footer h5:first',\n}, {\n content: \"Wait delayed click on footer\",\n trigger: '.o_we_customize_panel we-title:contains(\"Footer\")',\n run: function () {}, // it's a check\n}];\n\nconst clickEditLink = [{\n content: \"Click on Edit Link in Popover\",\n trigger: 'iframe .o_edit_menu_popover .o_we_edit_link',\n}, {\n content: \"Ensure popover is closed\",\n trigger: 'iframe html:not(:has(.o_edit_menu_popover))', // popover should be closed\n run: function () {}, // it's a check\n in_modal: false,\n}];\n\nwTourUtils.registerWebsitePreviewTour('edit_link_popover', {\n test: true,\n url: '/',\n edition: true,\n}, [\n // 1. Test links in page content (web_editor)\n wTourUtils.dragNDrop({\n id: 's_text_image',\n name: 'Text - Image',\n }),\n {\n content: \"Click on a paragraph\",\n trigger: FIRST_PARAGRAPH,\n run: 'text Paragraph', // Make sure the selection is set in the paragraph\n },\n {\n content: \"Click on 'Link' to open Link Dialog\",\n trigger: \"#toolbar #create-link\",\n },\n {\n content: \"Type the link URL /contactus\",\n trigger: '#o_link_dialog_url_input',\n run: 'text /contactus'\n },\n ...clickFooter,\n {\n content: \"Click on newly created link\",\n trigger: `${FIRST_PARAGRAPH} a`,\n },\n {\n content: \"Popover should be shown\",\n trigger: 'iframe .o_edit_menu_popover .o_we_url_link:contains(\"Contact Us\")', // At this point preview is loaded\n run: function () {}, // it's a check\n },\n ...clickEditLink,\n {\n content: \"Type the link URL /\",\n trigger: '#o_link_dialog_url_input',\n run: \"text /\"\n },\n ...clickFooter,\n {\n content: \"Click on link\",\n trigger: `${FIRST_PARAGRAPH} a`,\n },\n {\n content: \"Popover should be shown with updated preview data\",\n trigger: 'iframe .o_edit_menu_popover .o_we_url_link:contains(\"Home\")',\n run: function () {}, // it's a check\n },\n {\n content: \"Click on Remove Link in Popover\",\n trigger: 'iframe .o_edit_menu_popover .o_we_remove_link',\n },\n {\n content: \"Link should be removed\",\n trigger: `${FIRST_PARAGRAPH}:not(:has(a))`,\n run: function () {}, // it's a check\n },\n {\n content: \"Ensure popover is closed\",\n trigger: 'iframe html:not(:has(.o_edit_menu_popover))', // popover should be closed\n run: function () {}, // it's a check\n },\n // 2. Test links in navbar (website)\n {\n content: \"Click navbar menu Home\",\n trigger: 'iframe #top_menu a:contains(\"Home\")',\n },\n {\n content: \"Popover should be shown (2)\",\n trigger: 'iframe .o_edit_menu_popover .o_we_url_link:contains(\"Home\")',\n run: function () {}, // it's a check\n },\n ...clickEditLink,\n {\n content: \"Change the URL\",\n trigger: '#url_input',\n run: \"text /contactus\"\n },\n {\n content: \"Save the Edit Menu modal\",\n trigger: '.modal-footer .btn-primary',\n },\n {\n content: \"Click on the Home menu again\",\n extra_trigger: 'div:not(.o_loading_dummy) > #oe_snippets',\n trigger: 'iframe #top_menu a:contains(\"Home\")[href=\"/contactus\"]',\n },\n {\n content: \"Popover should be shown with updated preview data (2)\",\n trigger: 'iframe .o_edit_menu_popover .o_we_url_link:contains(\"Contact Us\")',\n run: function () {}, // it's a check\n },\n {\n content: \"Click on Edit Menu in Popover\",\n trigger: 'iframe .o_edit_menu_popover .js_edit_menu',\n },\n {\n content: \"Edit Menu (tree) should open\",\n trigger: '.o_website_dialog .oe_menu_editor',\n run: function () {}, // it's a check\n },\n {\n content: \"Close modal\",\n trigger: '.modal-footer .btn-secondary',\n },\n // 3. Test other links (CTA in navbar & links in footer)\n {\n content: \"Click CTA in navbar\",\n trigger: 'iframe #top_menu_container a.btn-primary[href=\"/contactus\"]',\n },\n {\n content: \"Popover should be shown (3)\",\n trigger: 'iframe .o_edit_menu_popover .o_we_url_link:contains(\"Contact Us\")',\n run: function () {}, // it's a check\n },\n {\n content: \"Toolbar should be shown (3)\",\n trigger: '#toolbar:has(#o_link_dialog_url_input:propValue(/contactus))',\n run: function () {}, // it's a check\n },\n {\n content: \"Click 'Home' link in footer\",\n trigger: 'iframe footer a[href=\"/\"]',\n },\n {\n content: \"Popover should be shown (4)\",\n trigger: 'iframe .o_edit_menu_popover .o_we_url_link:contains(\"Home\")',\n run: function () {}, // it's a check\n },\n {\n content: \"Toolbar should be shown (4)\",\n trigger: '#toolbar:has(#o_link_dialog_url_input:propValue(/))',\n run: function () {}, // it's a check\n },\n // 4. Popover should close when clicking non-link element\n ...clickFooter,\n // 5. Double click should not open popover but should open toolbar link\n {\n content: \"Double click on link\",\n extra_trigger: 'iframe html:not(:has(.o_edit_menu_popover))', // popover should be closed\n trigger: 'iframe footer a[href=\"/\"]',\n run: function (actions) {\n // Create range to simulate real double click, see pull request\n const range = document.createRange();\n range.selectNodeContents(this.$anchor[0]);\n const sel = window.getSelection();\n sel.removeAllRanges();\n sel.addRange(range);\n actions.click();\n actions.dblclick();\n },\n },\n {\n content: \"Ensure popover is opened on double click, and so is right panel edit link\",\n trigger: 'html:has(#o_link_dialog_url_input)',\n extra_trigger: 'iframe html:has(.o_edit_menu_popover)',\n run: function () {}, // it's a check\n },\n {\n content: \"Ensure that a click on the link popover link opens a new window in edit mode\",\n trigger: 'iframe .o_edit_menu_popover a.o_we_url_link[target=\"_blank\"]',\n extra_trigger: 'iframe .o_edit_menu_popover a.o_we_full_url[target=\"_blank\"]',\n run: (actions) => {\n // We do not want to open a link in a tour\n patch(browser, 'window_open_action', {\n open: (url) => {\n if (window.location.hostname === url.hostname && url.pathname.startsWith('/@/')) {\n document.querySelector('body').classList.add('new_backend_window_opened');\n }\n }\n }, { pure: true });\n actions.click();\n },\n },\n {\n content: \"Ensure that link is opened correctly in edit mode\",\n trigger: '.new_backend_window_opened',\n run: () => {}, // it's a check\n },\n]);\n});\n", "odoo.define(\"website.tour.edit_megamenu\", function (require) {\n\"use strict\";\n\nconst wTourUtils = require('website.tour_utils');\n\nconst toggleMegaMenu = (stepOptions) => Object.assign({}, {\n content: \"Toggles the mega menu.\",\n trigger: 'iframe #top_menu .nav-item a.o_mega_menu_toggle',\n run: function () {\n // If the mega menu is displayed inside the extra menu items, it should\n // already be displayed.\n if (!this.$anchor[0].closest('.o_extra_menu_items')) {\n this.$anchor[0].dispatchEvent(new Event('click'))\n }\n },\n}, stepOptions);\n\nwTourUtils.registerWebsitePreviewTour('edit_megamenu', {\n test: true,\n url: '/',\n edition: true,\n}, [\n // Add a megamenu item to the top menu.\n {\n content: \"Click on a menu item\",\n trigger: 'iframe #top_menu .nav-item a',\n },\n {\n content: \"Click on 'Link' to open Link Dialog\",\n trigger: 'iframe .o_edit_menu_popover a.js_edit_menu',\n },\n {\n content: \"Trigger the link dialog (click 'Add Mega Menu Item')\",\n extra_trigger: '.o_website_dialog:visible',\n trigger: '.modal-body a:eq(1)',\n },\n {\n content: \"Write a label for the new menu item\",\n trigger: '.modal-dialog .o_website_dialog input',\n run: 'text Megaaaaa!'\n },\n {\n content: \"Confirm the mega menu label\",\n trigger: '.modal-footer .btn-primary',\n },\n {\n content: \"Save the website menu with a new mega menu\",\n trigger: '.modal-footer .btn-primary',\n extra_trigger: '.oe_menu_editor [data-is-mega-menu=\"true\"] .js_menu_label:contains(\"Megaaaaa!\")',\n },\n // Edit a menu item\n wTourUtils.clickOnExtraMenuItem({extra_trigger: '#oe_snippets.o_loaded'}, true),\n toggleMegaMenu({extra_trigger: 'iframe #top_menu .nav-item a.o_mega_menu_toggle:contains(\"Megaaaaa!\")'}),\n {\n content: \"Select the last menu link of the first column\",\n trigger: 'iframe .s_mega_menu_odoo_menu .row > div:first-child .nav > :nth-child(6)', // 6th is the last one\n },\n {\n content: \"Hit the delete button to remove the menu link\",\n trigger: 'iframe .oe_overlay .oe_snippet_remove',\n },\n {\n content: \"Check that the last menu link was deleted\",\n trigger: 'iframe .s_mega_menu_odoo_menu .row > div:first-child .nav:not(:has(> :nth-child(6)))',\n run: () => null,\n },\n {\n content: \"Clicks on the first title item.\",\n trigger: 'iframe .o_mega_menu h4',\n },\n {\n content: \"Press enter.\",\n trigger: 'iframe .o_mega_menu h4',\n run: function (actions) {\n this.$anchor[0].dispatchEvent(new window.InputEvent('input', {bubbles: true, inputType: 'insertParagraph'}));\n },\n },\n {\n content: \"The menu should still be visible. Edit a menu item.\",\n trigger: 'iframe .o_mega_menu h4',\n run: 'text New Menu Item',\n },\n {\n // If this step fails, it means that a patch inside bootstrap was lost.\n content: \"Press the 'down arrow' key.\",\n trigger: 'iframe .o_mega_menu h4',\n run: function (actions) {\n this.$anchor[0].dispatchEvent(new window.KeyboardEvent(\"keydown\", { key: \"ArrowDown\" }));\n },\n },\n ...wTourUtils.clickOnSave(),\n wTourUtils.clickOnExtraMenuItem({extra_trigger: 'iframe body:not(.editor_enable)'}, true),\n toggleMegaMenu(),\n {\n content: \"The menu item should have been renamed.\",\n trigger: 'iframe .o_mega_menu h4:contains(\"New Menu Item\")',\n run: function () {}, // it's a check\n },\n]);\nwTourUtils.registerWebsitePreviewTour('edit_megamenu_big_icons_subtitles', {\n test: true,\n url: '/',\n edition: true,\n}, [\n // Add a megamenu item to the top menu.\n {\n content: \"Click on a menu item\",\n trigger: 'iframe #top_menu .nav-item a',\n },\n {\n content: \"Click on 'Link' to open Link Dialog\",\n trigger: 'iframe .o_edit_menu_popover a.js_edit_menu',\n },\n {\n content: \"Trigger the link dialog (click 'Add Mega Menu Item')\",\n extra_trigger: \".o_website_dialog\",\n trigger: '.modal-body a:eq(1)',\n },\n {\n content: \"Write a label for the new menu item\",\n trigger: '.modal-dialog .o_website_dialog input',\n run: 'text Megaaaaa2!',\n },\n {\n content: \"Confirm the mega menu label\",\n trigger: '.modal-footer .btn-primary',\n },\n {\n content: \"Save the website menu with a new mega menu\",\n trigger: '.modal-footer .btn-primary',\n extra_trigger: '.oe_menu_editor [data-is-mega-menu=\"true\"] .js_menu_label:contains(\"Megaaaaa2!\")',\n },\n {\n content: \"Check for the new mega menu\",\n trigger: 'iframe #top_menu:has(.nav-item a.o_mega_menu_toggle:contains(\"Megaaaaa2!\"))',\n run: function () {}, // it's a check\n },\n // Edit a menu item\n wTourUtils.clickOnExtraMenuItem({extra_trigger: '#oe_snippets.o_loaded'}, true),\n toggleMegaMenu({extra_trigger: 'iframe #top_menu .nav-item a.o_mega_menu_toggle:contains(\"Megaaaaa2!\")'}),\n {\n content: \"Select the first menu link of the first column\",\n trigger: 'iframe .s_mega_menu_odoo_menu .row > div:first-child .nav > :first-child',\n },\n wTourUtils.changeOption(\"MegaMenuLayout\", \"we-toggler\"),\n {\n content: \"Select Big Icons Subtitles mega menu\",\n trigger: '[data-select-label=\"Big Icons Subtitles\"]',\n },\n {\n content: \"Select the h4 of first menu link of the first column\",\n trigger: 'iframe .s_mega_menu_big_icons_subtitles .row > div:first-child .nav > :first-child h4',\n run: function (actions) {\n // Clicking on the h4 element first time leads to the selection of\n // the entire a.nav-link, due to presence of `o_default_snippet_text` class\n // hence, specify the selection on the h4 element\n actions.click();\n const iframeDocument = document.querySelector('.o_iframe').contentDocument;\n const range = iframeDocument.createRange();\n range.selectNodeContents(this.$anchor[0]);\n const sel = iframeDocument.getSelection();\n sel.removeAllRanges();\n sel.addRange(range);\n },\n },\n {\n content: \"Convert it to Bold\",\n trigger: '#oe_snippets #toolbar #bold',\n },\n ...wTourUtils.clickOnSave(),\n wTourUtils.clickOnExtraMenuItem({extra_trigger: 'iframe body:not(.editor_enable)'}, true),\n toggleMegaMenu(),\n {\n content: \"The menu item should only convert selected text to Bold.\",\n trigger: 'iframe .s_mega_menu_big_icons_subtitles .row > div:first-child .nav > :first-child font:not(:has(strong))',\n run: function () {}, // it's a check\n },\n]);\n});\n", "/** @odoo-module */\n\nimport wTourUtils from 'website.tour_utils';\n\nwTourUtils.registerWebsitePreviewTour('edit_menus', {\n test: true,\n url: '/',\n}, [\n // Add a megamenu item from the menu.\n {\n content: \"open site menu\",\n extra_trigger: \"iframe #wrapwrap\",\n trigger: 'button[data-menu-xmlid=\"website.menu_site\"]',\n },\n {\n content: \"Click on Edit Menu\",\n trigger: 'a[data-menu-xmlid=\"website.menu_edit_menu\"]',\n },\n {\n content: \"Trigger the link dialog (click 'Add Mega Menu Item')\",\n extra_trigger: '.o_website_dialog:visible',\n trigger: '.modal-body a:eq(1)',\n },\n {\n content: \"Write a label for the new menu item\",\n trigger: '.modal-dialog .o_website_dialog input',\n run: 'text Megaaaaa!'\n },\n {\n content: \"Confirm the mega menu label\",\n trigger: '.modal-footer .btn-primary',\n },\n {\n content: \"Save the new menu\",\n trigger: '.modal-footer .btn-primary',\n extra_trigger: '.oe_menu_editor [data-is-mega-menu=\"true\"] .js_menu_label:contains(\"Megaaaaa!\")',\n },\n wTourUtils.clickOnExtraMenuItem({extra_trigger: 'body:not(:has(.oe_menu_editor))'}, true),\n {\n content: \"There should be a new megamenu item.\",\n trigger: 'iframe #top_menu .nav-item a.o_mega_menu_toggle:contains(\"Megaaaaa!\")',\n run: () => {}, // It's a check.\n },\n // Add a menu item in edit mode.\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n content: \"Click on a menu item\",\n trigger: 'iframe #top_menu .nav-item a',\n },\n {\n content: \"Click on Edit Menu\",\n trigger: 'iframe .o_edit_menu_popover a.js_edit_menu',\n },\n {\n content: \"Trigger the link dialog (click 'Add Menu Item')\",\n extra_trigger: '.o_website_dialog:visible',\n trigger: '.modal-body a:eq(0)',\n },\n {\n content: \"Confirm the new menu entry without a label\",\n extra_trigger: '.modal-dialog .o_website_dialog input:eq(0)',\n trigger: '.modal-footer .btn-primary',\n },\n {\n content: \"It didn't save without a label. Fill label input.\",\n extra_trigger: '.o_website_dialog:eq(1):visible',\n trigger: '.modal-dialog .o_website_dialog input:eq(0)',\n run: 'text Random!',\n },\n {\n content: \"Confirm the new menu entry without a url\",\n trigger: '.modal-footer .btn-primary',\n },\n {\n content: \"It didn't save without a url. Fill url input.\",\n trigger: '.modal-dialog .o_website_dialog input:eq(1)',\n extra_trigger: '.modal-dialog .o_website_dialog input.is-invalid',\n run: 'text #',\n },\n {\n content: \"Confirm the new menu entry\",\n trigger: '.modal-footer .btn-primary',\n },\n {\n content: \"Save the website menu with the new entry\",\n trigger: '.modal-footer .btn-primary',\n extra_trigger: '.oe_menu_editor .js_menu_label:contains(\"Random!\")',\n },\n // Edit the new menu item from the \"edit link\" popover button\n wTourUtils.clickOnExtraMenuItem({extra_trigger: '#oe_snippets.o_loaded'}, true),\n {\n content: \"Menu should have a new link item\",\n trigger: 'iframe #top_menu .nav-item a:contains(\"Random!\")',\n },\n {\n content: \"navbar shouldn't have any zwnbsp and no o_link_in_selection class\",\n trigger: 'iframe nav.navbar:not(:has(.o_link_in_selection)):not(:contains(\"\\ufeff\"))',\n run: () => {}, // It's a check.\n },\n {\n content: \"Click on Edit Link\",\n trigger: 'iframe .o_edit_menu_popover a.o_we_edit_link',\n },\n {\n content: \"Change the label\",\n trigger: '.modal-dialog .o_website_dialog input:eq(0)',\n run: 'text Modnar',\n },\n {\n content: \"Confirm the new label\",\n trigger: '.modal-footer .btn-primary',\n },\n ...wTourUtils.clickOnSave(),\n wTourUtils.clickOnExtraMenuItem({extra_trigger: 'iframe body:not(.editor_enable)'}, true),\n {\n content: \"Label should have changed\",\n extra_trigger: \"iframe body:not(.editor_enable)\",\n trigger: 'iframe #top_menu .nav-item a:contains(\"Modnar\")',\n run: () => {}, // it's a check\n },\n // Edit the menu item from the \"edit menu\" popover button\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n wTourUtils.clickOnExtraMenuItem({}, true),\n {\n content: \"Click on the 'Modnar' link\",\n trigger: 'iframe #top_menu .nav-item a:contains(\"Modnar\")',\n },\n {\n content: \"Click on the popover Edit Menu button\",\n trigger: 'iframe .o_edit_menu_popover a.js_edit_menu',\n },\n {\n content: \"Click on the dialog Edit Menu button\",\n trigger: '.oe_menu_editor .js_menu_label:contains(\"Modnar\")',\n run: function () {\n const liEl = this.$anchor[0].closest('[data-menu-id]');\n liEl.querySelector('button.js_edit_menu').click();\n },\n },\n {\n content: \"Change the label\",\n trigger: '.modal-dialog .o_website_dialog input:eq(0)',\n run: 'text Modnar !!',\n },\n {\n content: \"Confirm the new menu label\",\n trigger: '.modal-footer .btn-primary',\n },\n {\n content: \"Save the website menu with the new menu label\",\n trigger: '.modal-footer .btn-primary',\n extra_trigger: '.oe_menu_editor .js_menu_label:contains(\"Modnar !!\")',\n },\n // Drag a block to be able to scroll later.\n wTourUtils.dragNDrop({id: 's_media_list', name: 'Media List'}),\n ...wTourUtils.clickOnSave(),\n wTourUtils.clickOnExtraMenuItem({extra_trigger: 'iframe body:not(.editor_enable)'}, true),\n {\n content: \"Label should have changed\",\n trigger: 'iframe #top_menu .nav-item a:contains(\"Modnar !!\")',\n run: () => {}, // It's a check.\n },\n // Nest menu item from the menu.\n {\n content: \"open site menu\",\n trigger: 'button[data-menu-xmlid=\"website.menu_site\"]',\n },\n {\n content: \"Click on Edit Menu\",\n trigger: 'a[data-menu-xmlid=\"website.menu_edit_menu\"]',\n },\n {\n content: \"Drag item into parent\",\n trigger: '.oe_menu_editor li:contains(\"Contact us\") > .ui-sortable-handle',\n // Menu rows are 38px tall.\n run: \"drag_move_and_drop [50,38]@.oe_menu_editor li:contains('Home') > .ui-sortable-handle => .oe_menu_editor li:contains('Home') .ui-sortable-placeholder\",\n },\n {\n content: \"Wait for drop\",\n trigger: '.oe_menu_editor li:contains(\"Home\") ul li:contains(\"Contact us\")',\n run: () => {}, // It's a check.\n },\n // Drag the Mega menu to the first position.\n {\n content: \"Drag Mega at the top\",\n trigger: '.oe_menu_editor li:contains(\"Megaaaaa!\") > .ui-sortable-handle',\n run: \"drag_move_and_drop [0,0]@.oe_menu_editor li:contains('Home') > .ui-sortable-handle => .oe_menu_editor li\",\n },\n {\n content: \"Wait for drop\",\n trigger: '.oe_menu_editor:first-child:contains(\"Megaaaaa!\")',\n run: () => {}, // It's a check.\n },\n {\n content: \"Save the website menu with new nested menus\",\n trigger: '.modal-footer .btn-primary',\n },\n {\n content: \"Menu item should have a child\",\n trigger: 'iframe #top_menu .nav-item a.dropdown-toggle:contains(\"Home\")',\n },\n // Check that with the auto close of dropdown menus, the dropdowns remain\n // openable.\n {\n content: \"When menu item is opened, child item must appear in the shown menu\",\n trigger: 'iframe #top_menu .nav-item:contains(\"Home\") ul.show li a.dropdown-item:contains(\"Contact us\")[href=\"/contactus\"]',\n run: function () {\n // Scroll down.\n this.$anchor[0].closest('body').querySelector('.o_footer_copyright_name')\n .scrollIntoView(true);\n },\n },\n {\n content: \"The Home menu should be closed\",\n trigger: 'iframe #top_menu .nav-item:contains(\"Home\"):has(ul:not(.show))',\n run: () => {}, // It's a check.\n },\n {\n content: \"Open the Home menu after scroll\",\n trigger: 'iframe #top_menu .nav-item a.dropdown-toggle:contains(\"Home\")',\n },\n {\n content: \"Check that the Home menu is opened\",\n trigger: 'iframe #top_menu .nav-item:contains(\"Home\") ul.show li' +\n ' a.dropdown-item:contains(\"Contact us\")[href=\"/contactus\"]',\n run: () => {}, // It's a check.\n },\n {\n content: \"Close the Home menu\",\n trigger: 'iframe #top_menu .nav-item:has(a.dropdown-toggle:contains(\"Home\"))',\n },\n {\n content: \"Check that the Home menu is closed\",\n trigger: 'iframe #top_menu .nav-item:contains(\"Home\"):has(ul:not(.show))',\n run: () => {}, // It's a check.\n },\n {\n content: \"Open the mega menu\",\n trigger: 'iframe #top_menu .nav-item a.o_mega_menu_toggle:contains(\"Megaaaaa!\")',\n },\n {\n content: \"When the mega menu is opened, scroll up\",\n trigger: 'iframe #top_menu .o_mega_menu_toggle.show',\n run: function () {\n const marginTopOfMegaMenu = getComputedStyle(\n this.$anchor[0].closest('.dropdown').querySelector('.o_mega_menu'))['margin-top'];\n if (marginTopOfMegaMenu !== '0px') {\n console.error('The margin-top of the mega menu should be 0px');\n }\n // Scroll up.\n this.$anchor[0].closest('body').querySelector('.s_media_list_item:nth-child(2)')\n .scrollIntoView(true);\n }\n },\n {\n content: \"Check that the mega menu is closed\",\n trigger: 'iframe #top_menu .nav-item:contains(\"Megaaaaa!\"):has(div[data-name=\"Mega Menu\"]:not(.show))',\n run:() => {}, // It's a check.\n },\n {\n content: \"Open the mega menu after scroll\",\n trigger: 'iframe #top_menu .nav-item a.o_mega_menu_toggle:contains(\"Megaaaaa!\")',\n },\n {\n content: \"Check that the mega menu is opened\",\n trigger: 'iframe #top_menu .nav-item:has(a.o_mega_menu_toggle:contains(\"Megaaaaa!\")) ' +\n '.s_mega_menu_odoo_menu',\n run: () => {}, // It's a check.\n },\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n content: \"Open nested menu item\",\n trigger: 'iframe #top_menu .nav-item:contains(\"Home\"):nth(1) .dropdown-toggle',\n },\n {\n // If this step fails, it means that a patch inside bootstrap was lost.\n content: \"Press the 'down arrow' key.\",\n trigger: 'iframe #top_menu .nav-item:contains(\"Home\") li:contains(\"Contact us\")',\n run: function (actions) {\n this.$anchor[0].dispatchEvent(new window.KeyboardEvent(\"keydown\", { key: \"ArrowDown\" }));\n },\n },\n]);\n", "/** @odoo-module **/\n\nimport tour from \"web_tour.tour\";\nimport wTourUtils from \"website.tour_utils\";\n\ntour.register('edit_translated_page_redirect', {\n test: true,\n url: '/nl/contactus',\n}, [\n {\n content: \"Enter backend\",\n trigger: 'a.o_frontend_to_backend_edit_btn',\n },\n {\n content: \"Check the data-for attribute\",\n trigger: 'iframe main:has([data-for=\"contactus_form\"])',\n run: () => {}, // it's a check\n },\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n content: \"Go to /nl\",\n trigger: \"body\",\n run: () => {\n // After checking the presence of the editor dashboard, we visit a\n // translated version of the homepage. The homepage is a special\n // case (there is no trailing slash), so we test it separately.\n location.href = '/nl';\n },\n },\n {\n content: \"Enter backend\",\n trigger: 'a.o_frontend_to_backend_edit_btn',\n },\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n]);\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\nimport wTourUtils from 'website.tour_utils';\n\ntour.register(\"editable_root_as_custom_snippet\", {\n test: true,\n}, [\n wTourUtils.clickOnSnippet('.s_title.custom[data-oe-model][data-oe-id][data-oe-field][data-oe-xpath]'),\n wTourUtils.changeOption('SnippetSave', 'we-button'),\n {\n content: \"Confirm modal\",\n trigger: '.modal-footer .btn-primary',\n },\n {\n content: \"Wait for the custom snippet to appear in the panel\",\n trigger: '.oe_snippet[name=\"Custom Title\"]',\n run: () => null,\n },\n ...wTourUtils.clickOnSave(),\n {\n content: \"Go to homepage\",\n trigger: 'iframe a[href=\"/\"].nav-link',\n },\n {\n content: \"Wait to land on homepage\",\n trigger: 'iframe a[href=\"/\"].nav-link.active',\n run: () => null,\n },\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n wTourUtils.dragNDrop({id: 's_title', name: 'Custom Title'}),\n {\n content: \"Check that the custom snippet does not have branding\",\n trigger: 'iframe #wrap .s_title.custom:not([data-oe-model]):not([data-oe-id]):not([data-oe-field]):not([data-oe-xpath])',\n run: () => null,\n },\n]);\n", "odoo.define('website.tour.focus_blur_snippets', function (require) {\n'use strict';\n\nconst { loadJS } = require('@web/core/assets');\nconst wTourUtils = require(\"website.tour_utils\");\n\nconst blockIDToData = {\n parent: {\n selector: 'iframe .s_focusblur',\n name: 'section',\n overlayIndex: 2,\n },\n child1: {\n selector: 'iframe .s_focusblur_child1',\n name: 'first child',\n overlayIndex: 1,\n },\n child2: {\n selector: 'iframe .s_focusblur_child2',\n name: 'second child',\n overlayIndex: 0,\n },\n};\n\nfunction clickAndCheck(blockID, expected) {\n const blockData = blockIDToData[blockID] || {};\n\n return [{\n content: blockID ? `Enable the ${blockData.name}` : 'Disable all blocks',\n trigger: blockData.selector || 'iframe #wrapwrap',\n }, {\n content: 'Once the related overlays are enabled/disabled, check that the focus/blur calls have been correct.',\n trigger: blockID\n ? `iframe .oe_overlay.ui-draggable:eq(${blockData.overlayIndex}).oe_active`\n : `iframe #oe_manipulators:not(:has(.oe_active))`,\n run: function (actions) {\n const result = window.focusBlurSnippetsResult;\n window.focusBlurSnippetsResult = [];\n\n if (expected.length !== result.length\n || !expected.every((item, i) => item === result[i])) {\n console.error(`\n Expected: ${expected.toString()}\n Result: ${result.toString()}\n `);\n }\n },\n }];\n}\n\nwindow.focusBlurSnippetsResult = [];\n\nwTourUtils.registerWebsitePreviewTour(\"focus_blur_snippets\", {\n test: true,\n url: \"/\",\n edition: true,\n}, [\n {\n content: 'First load our custom JS options',\n trigger: \"body\",\n run: function () {\n loadJS('/website/static/tests/tour_utils/focus_blur_snippets_options.js').then(function () {\n $('iframe:not(.o_ignore_in_tour)').contents().find('body').addClass('focus_blur_snippets_options_loaded');\n });\n },\n },\n {\n content: 'Drag the custom block into the page',\n trigger: '#snippet_structure .oe_snippet:has(.oe_snippet_body.s_focusblur) .oe_snippet_thumbnail',\n extra_trigger: 'iframe body.focus_blur_snippets_options_loaded',\n run: 'drag_and_drop iframe #wrap',\n },\n ...clickAndCheck('parent', ['focus parent']),\n ...clickAndCheck(null, ['blur parent']),\n ...clickAndCheck('child1', ['focus parent', 'focus child1']),\n ...clickAndCheck('child1', []),\n ...clickAndCheck(null, ['blur parent', 'blur child1']),\n ...clickAndCheck('parent', ['focus parent']),\n ...clickAndCheck('child1', ['blur parent', 'focus parent', 'focus child1']),\n ...clickAndCheck('child2', ['blur parent', 'blur child1', 'focus parent', 'focus child2']),\n ...clickAndCheck('parent', ['blur parent', 'blur child2', 'focus parent']),\n]);\n});\n", "/** @odoo-module **/\nimport wTourUtils from 'website.tour_utils';\n\nfunction waitForCSSReload() {\n // TODO we should find a better way to wait for this in tests after CSS\n // reload, it is currently done multiple different ways depending on the\n // test.\n return [\n {\n // This step is here because the option is applied but triggers a\n // reloading of the CC value, so if the second value is sent too\n // soon, it will be ignored. Clicking on the snippet tab and back\n // will ensure that the mutex is cleared, and therefore we can apply\n // the saturation step.\n content: \"Click on the blocks tab and back on theme to ensure the changes are applied\",\n trigger: '.o_we_add_snippet_btn',\n },\n {\n content: \"Go back to theme options\",\n trigger: '.o_we_customize_theme_btn',\n extra_trigger: '#o_scroll',\n },\n {\n content: \"Wait for no loading\",\n trigger: 'body:not(:has(.o_we_ui_loading)) iframe body:not(:has(.o_we_ui_loading))',\n run: () => null,\n },\n ];\n}\n\nwTourUtils.registerWebsitePreviewTour('website_gray_color_palette', {\n test: true,\n url: '/',\n edition: true,\n}, [\n {\n content: \"Go to theme options\",\n trigger: '.o_we_customize_theme_btn',\n },\n {\n content: \"Toggle gray color palette\",\n trigger: '.o_we_gray_preview.o_we_collapse_toggler',\n },\n {\n content: \"Drag the hue slider\",\n trigger: '.o_we_slider_tint[data-param=\"gray-hue\"]',\n run: () => {\n const slider = document.querySelector('.o_we_slider_tint[data-param=\"gray-hue\"] input');\n slider.value = 100;\n slider.dispatchEvent(new InputEvent('change', {bubbles: true}));\n },\n },\n {\n content: \"Check the preview of the gray 900 after hue change\",\n trigger: '[variable=\"900\"][style=\"background-color: rgb(36, 41, 33) !important;\"]',\n run: () => {}, // This is a check.\n },\n ...waitForCSSReload(),\n {\n content: \"Drag the saturation slider\",\n trigger: '.o_we_user_value_widget[data-param=\"gray-extra-saturation\"]',\n run: () => {\n const slider = document.querySelector('.o_we_user_value_widget[data-param=\"gray-extra-saturation\"] input');\n slider.value = 15;\n slider.dispatchEvent(new InputEvent('change', {bubbles: true}));\n }\n },\n {\n content: \"Check the preview of the gray 900 after saturation change\",\n trigger: '[variable=\"900\"][style=\"background-color: rgb(34, 47, 27) !important;\"]',\n run: () => {}, // This is a check.\n },\n ...waitForCSSReload(),\n {\n content: \"Wait for the iframe to be loaded\",\n trigger: 'iframe body',\n run: () => {\n const iframeEl = document.querySelector('.o_website_preview .o_iframe');\n const styles = iframeEl.contentWindow.getComputedStyle(iframeEl.contentDocument.documentElement);\n if (styles.getPropertyValue('--900').toString().replace(/ /g, '') !== '#222F1B') {\n console.error('The value for the gray 900 is not right');\n }\n }\n },\n]);\n", "/** @odoo-module **/\n\nimport wTourUtils from 'website.tour_utils';\n\nwTourUtils.registerWebsitePreviewTour('website_replace_grid_image', {\n test: true,\n url: '/',\n edition: true,\n}, [\n wTourUtils.dragNDrop({\n id: 's_text_image',\n name: 'Text - Image',\n }),\n wTourUtils.clickOnSnippet({\n id: 's_text_image',\n name: 'Text - Image',\n }),\n {\n content: \"Toggle to grid mode\",\n trigger: '.o_we_user_value_widget[data-name=\"grid_mode\"]',\n },\n {\n content: \"Replace image\",\n trigger: 'iframe .s_text_image img',\n run: 'dblclick',\n },\n {\n content: \"Pick new image\",\n trigger: '.o_select_media_dialog img[title=\"s_banner_default_image.jpg\"]',\n },\n {\n content: \"Add new image column\",\n trigger: '.o_we_user_value_widget[data-add-element=\"image\"]',\n },\n {\n content: \"Replace new image\",\n trigger: 'iframe .s_text_image img[src=\"/web/image/website.s_text_image_default_image\"]',\n run: 'dblclick',\n },\n {\n content: \"Pick new image\",\n trigger: '.o_select_media_dialog img[title=\"s_banner_default_image.jpg\"]',\n },\n ...wTourUtils.clickOnSave()\n]);\n\nwTourUtils.registerWebsitePreviewTour(\"scroll_to_new_grid_item\", {\n test: true,\n url: \"/\",\n edition: true,\n}, [\n // Drop enough snippets to scroll.\n wTourUtils.dragNDrop({id: \"s_text_image\", name: \"Text - Image\"}),\n wTourUtils.dragNDrop({id: \"s_image_text\", name: \"Image - Text\"}),\n wTourUtils.dragNDrop({id: \"s_image_text\", name: \"Image - Text\"}),\n // Toggle the first snippet to grid mode.\n wTourUtils.clickOnSnippet({id: \"s_text_image\", name: \"Text - Image\"}),\n wTourUtils.changeOption(\"layout_column\", 'we-button[data-name=\"grid_mode\"]'),\n // Add a new grid item.\n wTourUtils.changeOption(\"layout_column\", 'we-button[data-add-element=\"image\"]'),\n {\n content: \"Check that the page scrolled to the new grid item\",\n trigger: \"iframe .s_text_image .o_grid_item:nth-child(3)\",\n run: function () {\n // Leave some time to the page to scroll.\n setTimeout(() => {\n const newItemPosition = this.$anchor[0].getBoundingClientRect();\n if (newItemPosition.top < 0) {\n console.error(\"The page did not scroll to the new grid item.\");\n }\n document.body.classList.add(\"o_scrolled_to_grid_item\");\n }, 500);\n },\n }, {\n content: \"Make sure the scroll check is done\",\n trigger: \".o_scrolled_to_grid_item\",\n run: () => {}, // This is a check.\n },\n]);\n", "/** @odoo-module **/\n\nimport wTourUtils from \"website.tour_utils\";\n\n// TODO remove this test; it is badly written: you just have to change the fact\n// that editor_enable is added on the body to silently make it useless +\n// useless extra_trigger + useless auto: true + ... A better duplicate of it has\n// been made with \"website_no_dirty_page\".\nwTourUtils.registerWebsitePreviewTour('homepage_edit_discard', {\n test: true,\n url: '/',\n edition: true,\n}, [{\n trigger: \"#oe_snippets button[data-action=\\\"cancel\\\"]:not([disabled])\",\n extra_trigger: \"body:not(:has(.o_dialog))\",\n content: \"Click Discard to Discard all Changes.\",\n position: \"bottom\",\n}, {\n trigger: \"iframe body:not(.editor_enable)\",\n auto: true,\n run: () => null,\n}]);\n", "/* global ace */\nodoo.define('website.test.html_editor', function (require) {\n'use strict';\n\nconst wTourUtils = require('website.tour_utils');\n\nconst adminCssModif = '#wrap {display: none;}';\nconst demoCssModif = '// demo_edition';\n\nwTourUtils.registerWebsitePreviewTour('html_editor_language', {\n url: '/test_page',\n test: true,\n},\n [\n {\n content: \"open site menu\",\n extra_trigger: \"iframe body:not(.editor_enable)\",\n trigger: 'button[data-menu-xmlid=\"website.menu_site\"]',\n },\n {\n content: \"open html editor\",\n trigger: 'a[data-menu-xmlid=\"website.menu_ace_editor\"]',\n },\n {\n content: \"add something in the page's English version\",\n trigger: 'div.ace_line .ace_xml:contains(\"rumbler\")',\n run: () => {\n ace.edit('ace-view-editor').getSession().insert({\n row: 1,\n column: 1,\n }, '
\\n');\n },\n },\n {\n content: \"save the html editor\",\n extra_trigger: 'div.ace_line .ace_xml:contains(\"br\")',\n trigger: \".o_ace_view_editor button[data-action=save]\",\n },\n {\n content: \"check that the page has the modification\",\n trigger: 'iframe #wrapwrap:has(br)',\n run: () => {}, // it's a check\n },\n {\n content: \"check that the page has not lost the original text\",\n trigger: 'iframe #wrapwrap:contains(\"rommelpot\")',\n run: () => {}, // it's a check\n },\n ]\n);\n\nwTourUtils.registerWebsitePreviewTour('html_editor_multiple_templates', {\n url: '/generic',\n edition: true,\n test: true,\n},\n [\n {\n content: \"drop a snippet\",\n trigger: \".oe_snippet:has(.s_cover) .oe_snippet_thumbnail\",\n // id starting by 'oe_structure..' will actually create an inherited view\n run: \"drag_and_drop iframe #oe_structure_test_ui\",\n },\n ...wTourUtils.clickOnSave(),\n // 2. Edit generic view\n {\n content: \"open site menu\",\n extra_trigger: \"iframe body:not(.editor_enable)\",\n trigger: 'button[data-menu-xmlid=\"website.menu_site\"]',\n },\n {\n content: \"open html editor\",\n trigger: 'a[data-menu-xmlid=\"website.menu_ace_editor\"]',\n },\n {\n content: \"add something in the generic view\",\n trigger: 'div.ace_line .ace_xml:contains(\"Generic\")',\n run: function () {\n ace.edit('ace-view-editor').getSession().insert({row: 3, column: 1}, '

somenewcontent

\\n');\n },\n },\n // 3. Edit oe_structure specific view\n {\n content: \"select oe_structure specific view\",\n trigger: 'div.ace_line .ace_xml:contains(\"somenewcontent\")',\n run: function () {\n var viewId = $('#ace-view-list option:contains(\"oe_structure_test_ui\")').val();\n $('#ace-view-list').val(viewId).trigger('change');\n },\n },\n {\n content: \"add something in the oe_structure specific view\",\n extra_trigger: '#ace-view-id:contains(\"test.generic_view_oe_structure_test_ui\")', // If no xml_id it should show key\n trigger: 'div.ace_line .ace_xml:contains(\"s_cover\")',\n run: function () {\n ace.edit('ace-view-editor').getSession().insert({row: 2, column: 1}, '

anothernewcontent

\\n');\n },\n },\n {\n content: \"save the html editor\",\n extra_trigger: 'div.ace_line .ace_xml:contains(\"anothernewcontent\")',\n trigger: \".o_ace_view_editor button[data-action=save]\",\n },\n {\n content: \"check that the page has both modification\",\n extra_trigger: 'iframe #wrapwrap:contains(\"anothernewcontent\")',\n trigger: 'iframe #wrapwrap:contains(\"somenewcontent\")',\n run: function () {}, // it's a check\n },\n ]\n);\n\nwTourUtils.registerWebsitePreviewTour('test_html_editor_scss', {\n url: '/contactus',\n test: true,\n},\n [\n // 1. Open Html Editor and select a scss file\n {\n content: \"open site menu\",\n extra_trigger: 'iframe #wrap:visible', // ensure state for later\n trigger: 'button[data-menu-xmlid=\"website.menu_site\"]',\n },\n {\n content: \"open html editor\",\n trigger: 'a[data-menu-xmlid=\"website.menu_ace_editor\"]',\n },\n {\n content: \"open type switcher\",\n trigger: '.o_ace_type_switcher button',\n },\n {\n content: \"select scss files\",\n trigger: '.o_ace_type_switcher_choice[data-type=\"scss\"]',\n },\n {\n content: \"select 'user_custom_rules'\",\n trigger: 'body:has(#ace-scss-list option:contains(\"user_custom_rules\"))',\n run: function () {\n var scssId = $('#ace-scss-list option:contains(\"user_custom_rules\")').val();\n $('#ace-scss-list').val(scssId).trigger('change');\n },\n },\n // 2. Edit that file and ensure it was saved then reset it\n {\n content: \"add some scss content in the file\",\n trigger: 'div.ace_line .ace_comment:contains(\"footer {\")',\n run: function () {\n ace.edit('ace-view-editor').getSession().insert({row: 2, column: 0}, `${adminCssModif}\\n`);\n },\n },\n {\n content: \"save the html editor\",\n extra_trigger: `div.ace_line:contains(\"${adminCssModif}\")`,\n trigger: \".o_ace_view_editor button[data-action=save]\",\n },\n {\n content: \"check that the scss modification got applied\",\n trigger: 'iframe body:has(#wrap:hidden)',\n run: function () {}, // it's a check\n timeout: 30000, // SCSS compilation might take some time\n },\n {\n content: \"reset view (after reload, html editor should have been reopened where it was)\",\n trigger: '#ace-view-id button[data-action=\"reset\"]:not([disabled])',\n },\n {\n content: \"confirm reset warning\",\n trigger: '.modal-footer .btn-primary',\n },\n {\n content: \"check that the scss file was reset correctly, wrap content should now be visible again\",\n trigger: 'iframe #wrap:visible',\n run: function () {}, // it's a check\n timeout: 30000, // SCSS compilation might take some time\n },\n // 3. Customize again that file (will be used in second part of the test\n // to ensure restricted user can still use the HTML Editor)\n {\n content: \"add some scss content in the file\",\n trigger: 'div.ace_line .ace_comment:contains(\"footer {\")',\n run: function () {\n ace.edit('ace-view-editor').getSession().insert({row: 2, column: 0}, `${adminCssModif}\\n`);\n },\n },\n {\n content: \"save the html editor\",\n extra_trigger: `div.ace_line:contains(\"${adminCssModif}\")`,\n trigger: '.o_ace_view_editor button[data-action=save]',\n },\n {\n content: \"check that the scss modification got applied\",\n trigger: 'iframe body:has(#wrap:hidden)',\n run: function () {}, // it's a check\n },\n ]\n);\n\nwTourUtils.registerWebsitePreviewTour('test_html_editor_scss_2', {\n url: '/',\n test: true,\n},\n [\n // This part of the test ensures that a restricted user can still use\n // the HTML Editor if someone else made a customization previously.\n\n // 4. Open Html Editor and select a scss file\n {\n content: \"open site menu\",\n trigger: 'button[data-menu-xmlid=\"website.menu_site\"]',\n },\n {\n content: \"open html editor\",\n trigger: 'a[data-menu-xmlid=\"website.menu_ace_editor\"]',\n },\n {\n content: \"open type switcher\",\n trigger: '.o_ace_type_switcher button',\n },\n {\n content: \"select scss files\",\n trigger: '.o_ace_type_switcher_choice[data-type=\"scss\"]',\n },\n {\n content: \"select 'user_custom_rules'\",\n trigger: 'body:has(#ace-scss-list option:contains(\"user_custom_rules\"))',\n run: function () {\n var scssId = $('#ace-scss-list option:contains(\"user_custom_rules\")').val();\n $('#ace-scss-list').val(scssId).trigger('change');\n },\n },\n // 5. Edit that file and ensure it was saved then reset it\n {\n content: \"add some scss content in the file\",\n trigger: `div.ace_line:contains(\"${adminCssModif}\")`, // ensure the admin modification is here\n run: function () {\n ace.edit('ace-view-editor').getSession().insert({row: 2, column: 0}, `${demoCssModif}\\n`);\n },\n },\n {\n content: \"save the html editor\",\n extra_trigger: `div.ace_line:contains(\"${demoCssModif}\")`,\n trigger: \".o_ace_view_editor button[data-action=save]\",\n },\n {\n content: \"reset view (after reload, html editor should have been reopened where it was)\",\n trigger: '#ace-view-id button[data-action=\"reset\"]:not([disabled])',\n timeout: 30000, // SCSS compilation might take some time\n },\n {\n content: \"confirm reset warning\",\n trigger: '.modal-footer .btn-primary',\n },\n {\n content: \"check that the scss file was reset correctly\",\n extra_trigger: `body:not(:has(div.ace_line:contains(\"${adminCssModif}\")))`,\n trigger: `body:not(:has(div.ace_line:contains(\"${demoCssModif}\")))`,\n run: function () {}, // it's a check\n timeout: 30000, // SCSS compilation might take some time\n },\n ]\n);\n\n});\n", "/** @odoo-module */\n\nimport wTourUtils from 'website.tour_utils';\nimport { boundariesIn, setSelection, nodeSize } from '@web_editor/js/editor/odoo-editor/src/utils/utils';\n\nconst clickOnImgStep = {\n content: \"Click somewhere else to save.\",\n trigger: 'iframe #wrap .s_text_image img',\n};\n\nwTourUtils.registerWebsitePreviewTour('link_tools', {\n test: true,\n url: '/',\n edition: true,\n}, [\n // 1. Create a new link from scratch.\n wTourUtils.dragNDrop({\n id: 's_text_image',\n name: 'Text - Image',\n }),\n {\n content: \"Replace first paragraph, to insert a new link\",\n trigger: 'iframe #wrap .s_text_image p',\n run: 'text Go to odoo: '\n },\n {\n content: \"Open link tools\",\n trigger: \"#toolbar #create-link\",\n },\n {\n content: \"Type the link URL odoo.com\",\n trigger: '#o_link_dialog_url_input',\n run: 'text odoo.com'\n },\n clickOnImgStep,\n {\n content: \"Select the newly created link\",\n trigger: 'iframe #wrap .s_text_image a[href=\"http://odoo.com\"]:contains(\"odoo.com\")',\n run() {\n setSelection(this.$anchor[0], 0, this.$anchor[0], nodeSize(this.$anchor[0]));\n }\n },\n // Remove the link.\n {\n content: \"Click on the newly created link\",\n trigger: 'iframe #wrap .s_text_image a[href=\"http://odoo.com\"]:contains(\"odoo.com\")',\n },\n {\n content: \"Remove the link.\",\n trigger: 'iframe .popover:contains(\"http://odoo.com\") a .fa-chain-broken',\n },\n {\n content: \"Check that the link was removed\",\n trigger: 'iframe #wrap .s_text_image p:contains(\"Go to odoo:\"):not(:has(a))',\n run: () => {}, // It's a check.\n },\n // Recreate the link.\n {\n content: \"Select first paragraph, to insert a new link\",\n trigger: 'iframe #wrap .s_text_image p',\n },\n {\n content: \"Open link tools\",\n trigger: \"#toolbar #create-link\",\n },\n {\n content: \"Type the link URL odoo.com\",\n trigger: '#o_link_dialog_url_input',\n run: 'text odoo.com'\n },\n clickOnImgStep,\n // 2. Edit the link with the link tools.\n {\n content: \"Click on the newly created link, change content to odoo website\",\n trigger: 'iframe .s_text_image a[href=\"http://odoo.com\"]:contains(\"odoo.com\")',\n run: 'text odoo website',\n },\n {\n content: \"Link tools, should be open, change the url\",\n trigger: '#o_link_dialog_url_input',\n run: 'text_blur odoo.be'\n },\n\n ...wTourUtils.clickOnSave(),\n // 3. Edit a link after saving the page.\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n content: \"The new link content should be odoo website and url odoo.be\",\n trigger: 'iframe .s_text_image a[href=\"http://odoo.be\"]:contains(\"odoo website\")',\n },\n {\n content: \"The new link content should be odoo website and url odoo.be\",\n trigger: '#toolbar button[data-bs-original-title=\"Link Style\"]',\n },\n {\n content: \"Click on the secondary style button.\",\n trigger: '#toolbar we-button[data-value=\"secondary\"]',\n },\n ...wTourUtils.clickOnSave(),\n {\n content: \"The link should have the secondary button style.\",\n trigger: 'iframe .s_text_image a.btn.btn-secondary[href=\"http://odoo.be\"]:contains(\"odoo website\")',\n run: () => {}, // It's a check.\n },\n // 4. Add link on image.\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n wTourUtils.dragNDrop({\n id: 's_three_columns',\n name: 'Columns',\n }),\n {\n content: \"Click on the first image.\",\n trigger: 'iframe .s_three_columns .row > :nth-child(1) img',\n },\n {\n content: \"Activate link.\",\n trigger: '.o_we_customize_panel we-row:contains(\"Media\") we-button.fa-link',\n },\n {\n content: \"Set URL.\",\n trigger: '.o_we_customize_panel we-input:contains(\"Your URL\") input',\n run: 'text odoo.com',\n },\n {\n content: \"Deselect image.\",\n trigger: 'iframe .s_three_columns .row > :nth-child(2) img',\n },\n {\n content: \"Re-select image.\",\n trigger: 'iframe .s_three_columns .row > :nth-child(1) img',\n },\n {\n content: \"Check that the second image is not within a link.\",\n trigger: 'iframe .s_three_columns .row > :nth-child(2) div > img',\n run: () => {}, // It's a check.\n },\n {\n content: \"Check that link tools appear.\",\n trigger: 'iframe .popover div a:contains(\"http://odoo.com\")',\n run: () => {}, // It's a check.\n },\n ...wTourUtils.clickOnSave(),\n {\n content: \"Check that the first image was saved.\",\n trigger: 'iframe .s_three_columns .row > :nth-child(1) div > a > img',\n run: () => {}, // It's a check.\n },\n {\n content: \"Check that the second image was saved.\",\n trigger: 'iframe .s_three_columns .row > :nth-child(2) div > img',\n run: () => {}, // It's a check.\n },\n // 5. Remove link from image.\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n content: \"Reselect the first image.\",\n trigger: 'iframe .s_three_columns .row > :nth-child(1) div > a > img',\n },\n {\n content: \"Check that link tools appear.\",\n trigger: 'iframe .popover div a:contains(\"http://odoo.com\")',\n run: () => {}, // It's a check.\n },\n {\n content: \"Remove link.\",\n trigger: 'iframe .popover:contains(\"http://odoo.com\") a .fa-chain-broken',\n },\n {\n content: \"Check that image is not within a link anymore.\",\n trigger: 'iframe .s_three_columns .row > :nth-child(1) div > img',\n run: () => {}, // It's a check.\n },\n // 6. Create new a link from a URL-like text.\n {\n content: \"Replace first paragraph, write a URL\",\n trigger: 'iframe #wrap .s_text_image p',\n run: 'text odoo.com'\n },\n {\n content: \"Select text\",\n trigger: 'iframe #wrap .s_text_image p:contains(odoo.com)',\n run() {\n setSelection(...boundariesIn(this.$anchor[0]), false);\n }\n },\n {\n content: \"Open link tools\",\n trigger: \"#toolbar #create-link\",\n },\n clickOnImgStep,\n {\n // URL transformation into link should persist, without the need for\n // input at input[name=url]\n content: \"Check that link was created\",\n trigger: \"iframe .s_text_image p a[href='http://odoo.com']:contains('odoo.com')\",\n run: () => null,\n },\n {\n content: \"Click on link to open the link tools\",\n trigger: \"iframe .s_text_image p a\",\n },\n // 7. Check that http links are not coerced to https and vice-versa.\n {\n content: \"Change URL to https\",\n trigger: \"#o_link_dialog_url_input\",\n run: 'text https://odoo.com',\n },\n {\n content: \"Check that link was updated\",\n trigger: \"iframe .s_text_image p a[href='https://odoo.com']:contains('odoo.com')\",\n run: () => null,\n },\n {\n content: \"Change it back http\",\n trigger: \"#o_link_dialog_url_input\",\n run: 'text http://odoo.com',\n },\n {\n content: \"Check that link was updated\",\n trigger: \"iframe .s_text_image p a[href='http://odoo.com']:contains('odoo.com')\",\n run: () => null,\n },\n // 8. Test conversion between http and mailto links.\n {\n content: \"Change URL into an email address\",\n trigger: \"#o_link_dialog_url_input\",\n run: \"text callme@maybe.com\",\n },\n {\n content: \"Check that link was updated and link content is synced with URL\",\n trigger: \"iframe .s_text_image p a[href='mailto:callme@maybe.com']:contains('callme@maybe.com')\",\n run: () => null,\n },\n {\n content: \"Change URL back into a http one\",\n trigger: \"#o_link_dialog_url_input\",\n run: \"text_blur callmemaybe.com\",\n },\n {\n content: \"Check that link was updated and link content is synced with URL\",\n trigger: \"iframe .s_text_image p a[href='http://callmemaybe.com']:contains('callmemaybe.com')\",\n },\n // 9.Test that UI stays up-to-date.\n {\n content: \"Edit link label\",\n trigger: \"iframe .s_text_image p a\",\n run(actions) {\n // Simulating text input.\n const link = this.$anchor[0];\n actions.text(\"callmemaybe.com/shops\");\n // Trick the editor into keyboardType === 'PHYSICAL' and delete the\n // last character \"s\" and end with \"callmemaybe.com/shop\"\n link.dispatchEvent(new KeyboardEvent(\"keydown\", { key: \"Backspace\", bubbles: true }));\n // Trigger editor's '_onInput' handler, which leads to a history step.\n link.dispatchEvent(new InputEvent('input', {inputType: 'insertText', bubbles: true}));\n },\n },\n {\n content: \"Check that links's href was updated\",\n trigger: \"iframe .s_text_image p a[href='http://callmemaybe.com/shop']:contains('callmemaybe.com/shop')\",\n run: () => null,\n },\n {\n content: \"Check popover content is up-to-date\",\n trigger: \"iframe .popover div a:contains('http://callmemaybe.com/shop')\",\n run: () => null,\n },\n {\n content: \"Check Link tools URL input content is up-to-date\",\n trigger: \"#o_link_dialog_url_input\",\n run() {\n if (this.$anchor[0].value !== 'http://callmemaybe.com/shop') {\n throw new Error(\"Tour step failed\") ;\n }\n }\n },\n ...wTourUtils.clickOnSave(),\n]);\n", "/** @odoo-module */\n\nimport wTourUtils from 'website.tour_utils';\n\nwTourUtils.registerWebsitePreviewTour(\"website_media_dialog_undraw\", {\n test: true,\n url: '/',\n edition: true,\n}, [\nwTourUtils.dragNDrop({\n id: 's_text_image',\n name: 'Text - Image',\n}),\n{\n content: \"Open the media dialog from the snippet\",\n trigger: \"iframe .s_text_image img\",\n run: \"dblclick\",\n}, {\n content: \"Search for 'banner' to call the media library\", // Mocked call\n trigger: \".o_select_media_dialog .o_we_search\",\n run: \"text banner\",\n}, {\n content: \"Check that the media library is available\",\n trigger: '.o_select_media_dialog:has(.o_we_search_select option[value=\"media-library\"])',\n run: () => null, // it's a check\n},\n]);\n\nwTourUtils.registerWebsitePreviewTour(\"website_media_dialog_external_library\", {\n test: true,\n url: \"/\",\n edition: true,\n}, [\n wTourUtils.dragNDrop({\n id: \"s_text_image\",\n name: \"Text - Image\",\n }),\n {\n content: \"Open the media dialog from the snippet\",\n trigger: \"iframe .s_text_image img\",\n run: \"dblclick\",\n }, {\n content: \"Dummy search to call the media library\",\n trigger: \".o_select_media_dialog .o_we_search\",\n run: \"text a\",\n }, {\n content: \"Choose the media library to only show its media\",\n trigger: \".o_select_media_dialog .o_we_search_select\",\n // This is a standard \\\n

<b></b> is an HTML tag & is empty

');\n $('iframe:not(.o_ignore_in_tour)').contents().find(\"#wrap img\").attr(\"title\", \"test translate image title\");\n }\n}, {\n content: \"save\",\n trigger: 'button[data-action=save]',\n extra_trigger: 'iframe #wrap p:first b',\n}, {\n content: \"click language dropdown (3)\",\n trigger: 'iframe .js_language_selector .dropdown-toggle',\n extra_trigger: 'iframe body:not(.editor_enable)',\n}, {\n content: \"click on Parseltongue version\",\n trigger: 'iframe .js_language_selector a[data-url_code=\"pa_GB\"]',\n extra_trigger: 'iframe html[lang*=\"en\"]',\n}, {\n content: \"translate\",\n trigger: '.o_menu_systray .o_translate_website_container > a',\n extra_trigger: 'iframe html:not(:has(#wrap p span))',\n}, {\n content: \"close modal\",\n trigger: '.modal-footer .btn-secondary',\n}, {\n content: \"check if translation is activate\",\n trigger: 'iframe [data-oe-translation-initial-sha]',\n}, {\n content: \"translate text\",\n extra_trigger: '#oe_snippets.o_loaded',\n trigger: 'iframe #wrap p font:first',\n run: function (actionHelper) {\n actionHelper.text('translated Parseltongue text');\n const Wysiwyg = odoo.__DEBUG__.services['web_editor.wysiwyg'];\n Wysiwyg.setRange(this.$anchor.contents()[0], 22);\n this.$anchor.trigger($.Event(\"keyup\", {key: '_', keyCode: 95}));\n this.$anchor.trigger('input');\n },\n}, {\n content: \"translate text with special char\",\n trigger: 'iframe #wrap input + p span:first',\n run: function (actionHelper) {\n actionHelper.click();\n this.$anchor.prepend('<{translated}>');\n const Wysiwyg = odoo.__DEBUG__.services['web_editor.wysiwyg'];\n Wysiwyg.setRange(this.$anchor.contents()[0], 0);\n this.$anchor.trigger($.Event(\"keyup\", {key: '_', keyCode: 95}));\n this.$anchor.trigger('input');\n },\n}, {\n content: \"click on input\",\n trigger: 'iframe #wrap input:first',\n extra_trigger: 'iframe #wrap .o_dirty font:first:contains(translated Parseltongue text)',\n run: 'click',\n}, {\n content: \"translate placeholder\",\n trigger: '.modal-dialog input:first',\n run: 'text test Parseltongue placeholder',\n}, {\n content: \"translate default value\",\n trigger: '.modal-dialog input:last',\n run: 'text test Parseltongue default value',\n}, {\n content: \"close modal\",\n trigger: '.modal-footer .btn-primary',\n extra_trigger: '.modal input:propValue(test Parseltongue placeholder)',\n}, {\n content: \"check: input marked as translated\",\n trigger: 'iframe input[placeholder=\"test Parseltongue placeholder\"].oe_translated',\n run: () => {},\n}, {\n content: \"save translation\",\n trigger: 'button[data-action=save]',\n}, {\n content: \"check: content is translated\",\n trigger: 'iframe #wrap p font:first:contains(translated Parseltongue text)',\n extra_trigger: 'iframe body:not(.editor_enable)',\n run: function () {}, // it's a check\n}, {\n content: \"check: content with special char is translated\",\n trigger: \"iframe #wrap input + p:contains(<{translated}> is an HTML\\xa0tag & )\",\n run: function () {}, // it's a check\n\n}, {\n content: \"check: placeholder translation\",\n trigger: 'iframe input[placeholder=\"test Parseltongue placeholder\"]',\n run: function () {}, // it's a check\n}, {\n content: \"check: default value translation\",\n trigger: 'iframe input[value=\"test Parseltongue default value\"]',\n run: () => {},\n}, {\n content: \"open language selector\",\n trigger: 'iframe .js_language_selector button:first',\n extra_trigger: 'iframe html[lang*=\"pa-GB\"]:not(:has(#wrap p span))',\n}, {\n content: \"return to english version\",\n trigger: 'iframe .js_language_selector a[data-url_code=\"en\"]',\n}, {\n content: \"edit english version\",\n trigger: '.o_edit_website_container > a',\n extra_trigger: 'iframe body:not(:has(#wrap p font:first:containsExact(paragraphs describing)))',\n}, {\n content: \"select text\",\n extra_trigger: '#oe_snippets.o_loaded',\n trigger: 'iframe #wrap p',\n run: function (actionHelper) {\n actionHelper.click();\n var el = this.$anchor[0];\n var mousedown = document.createEvent('MouseEvents');\n mousedown.initMouseEvent('mousedown', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, el);\n el.dispatchEvent(mousedown);\n var mouseup = document.createEvent('MouseEvents');\n const Wysiwyg = odoo.__DEBUG__.services['web_editor.wysiwyg'];\n Wysiwyg.setRange(el.childNodes[2], 6, el.childNodes[2], 13);\n mouseup.initMouseEvent('mouseup', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, el);\n el.dispatchEvent(mouseup);\n },\n// This is disabled for now because it reveals a bug that is fixed in saas-15.1\n// and considered a tradeoff in 15.0. The bug concerns the invalidation of\n// translations when inserting tags with more than one character. Whereas \n// didn't trigger an invalidation, \n// does.\n// }, {\n// content: \"underline\",\n// trigger: '.oe-toolbar #underline',\n}, {\n content: \"save new change\",\n trigger: 'button[data-action=save]',\n // See comment above.\n // extra_trigger: '#wrap.o_dirty p span[style*=\"text-decoration-line: underline;\"]',\n}, {\n content: \"click language dropdown (4)\",\n trigger: 'iframe .js_language_selector .dropdown-toggle',\n extra_trigger: 'iframe body:not(.editor_enable)',\n}, {\n content: \"return in Parseltongue\",\n trigger: 'iframe html[lang=\"en-US\"] .js_language_selector .js_change_lang[data-url_code=\"pa_GB\"]',\n}, {\n content: \"check bis: content is translated\",\n trigger: 'iframe #wrap p font:first:contains(translated Parseltongue text)',\n extra_trigger: 'iframe html[lang*=\"pa-GB\"]',\n}, {\n content: \"check bis: placeholder translation\",\n trigger: 'iframe input[placeholder=\"test Parseltongue placeholder\"]',\n}, {\n content: \"open site menu\",\n trigger: 'button[data-menu-xmlid=\"website.menu_site\"]',\n}, {\n content: \"Open HTML editor\",\n trigger: 'a[data-menu-xmlid=\"website.menu_ace_editor\"]',\n}, {\n content: \"Check that the editor is not showing translated content (1)\",\n trigger: '.ace_text-layer .ace_line:contains(\"an HTML\")',\n run: function (actions) {\n var lineEscapedText = $(this.$anchor.text()).last().text();\n if (lineEscapedText !== \"<b></b> is an HTML tag & is empty\") {\n console.error('The HTML editor should display the correct untranslated content');\n $('iframe:not(.o_ignore_in_tour)').contents().find('body').addClass('rte_translator_error');\n }\n },\n}, {\n content: \"Check that the editor is not showing translated content (2)\",\n trigger: 'iframe body:not(.rte_translator_error)',\n run: function () {},\n}]);\n});\n", "/** @odoo-module */\n\nimport weUtils from 'web_editor.utils';\nimport wTourUtils from 'website.tour_utils';\n\nconst snippets = [\n {\n id: 's_text_image',\n name: 'Text - Image',\n },\n];\nconst gradients = [\n 'linear-gradient(135deg, rgb(203, 94, 238) 0%, rgb(75, 225, 236) 100%)',\n 'linear-gradient(135deg, rgb(255, 222, 202) 0%, rgb(202, 115, 69) 100%)',\n];\n\nfunction typeToName(xType) {\n return xType === 'cc' ? 'color combinations' : xType === 'bg' ? 'background colors' : 'gradients';\n}\n\nfunction switchTo(type, _name) {\n const target = type === 'cc' ? 'color-combinations' : type === 'bg' ? 'custom-colors' : 'gradients';\n const name = _name || typeToName(type);\n return {\n trigger: `.o_we_colorpicker_switch_pane_btn[data-target=\"${target}\"]`,\n content: `Switch to ${name}`,\n };\n}\n\nfunction addCheck(steps, checkX, checkNoX, xType, noSwitch = false) {\n if (!checkX && !checkNoX) {\n return;\n }\n\n const name = typeToName(xType);\n const selectorCheckX = checkX && `.o_we_color_btn[data-color=\"${checkX}\"].selected`;\n const selectorCheckNoX = checkNoX && `.o_we_color_btn[data-color=\"${checkNoX}\"]:not(.selected)`;\n const step = {\n trigger: selectorCheckX || selectorCheckNoX,\n content: `The correct ${name} is marked as selected`,\n position: 'bottom',\n run: () => null,\n };\n if (!selectorCheckX && selectorCheckNoX) {\n step.extra_trigger = selectorCheckNoX;\n }\n\n if (!noSwitch) {\n steps.push(switchTo(xType, name));\n }\n steps.push(step);\n}\n\nfunction checkAndUpdateBackgroundColor({\n checkCC, checkNoCC,\n checkBg, checkNoBg,\n checkGradient, checkNoGradient,\n changeType, change,\n finalSelector, finalRun\n}) {\n const steps = [\n wTourUtils.changeBackgroundColor(),\n ];\n\n addCheck(steps, checkCC, checkNoCC, 'cc', true);\n addCheck(steps, checkBg, checkNoBg, 'bg');\n addCheck(steps, checkGradient, checkNoGradient, 'gradient');\n\n if (changeType) {\n steps.push(switchTo(changeType));\n steps.push(wTourUtils.changeOption('ColoredLevelBackground', `.o_we_color_btn[data-color=\"${change}\"]`, 'background color', 'top', true));\n steps.push({\n trigger: finalSelector,\n content: \"The selected colors have been applied (CC AND (BG or GRADIENT))\",\n position: 'bottom',\n run: finalRun,\n });\n }\n\n return steps;\n}\n\nfunction updateAndCheckCustomGradient({updateStep, checkGradient}) {\n const steps = [updateStep, {\n trigger: `iframe #wrapwrap section.${snippets[0].id}.o_cc1`,\n content: 'Color combination 1 still selected',\n run: () => null,\n }];\n addCheck(steps, checkGradient, checkGradient !== gradients[0] && gradients[0], 'gradient', true);\n return steps;\n}\n\nwTourUtils.registerWebsitePreviewTour('snippet_background_edition', {\n url: '/',\n edition: true,\n test: true,\n},\n[\nwTourUtils.dragNDrop(snippets[0]),\nwTourUtils.clickOnSnippet(snippets[0]),\n\n// Add a color combination\n...checkAndUpdateBackgroundColor({\n changeType: 'cc',\n change: 3,\n finalSelector: `iframe .${snippets[0].id}.o_cc.o_cc3:not([class*=bg-]):not([style*=\"background\"])`,\n}),\n\n// Change the color combination + Check the previous one was marked as selected\n...checkAndUpdateBackgroundColor({\n checkCC: 3,\n changeType: 'cc',\n change: 2,\n finalSelector: `iframe .${snippets[0].id}.o_cc.o_cc2:not(.o_cc3):not([class*=bg-])`,\n}),\n\n// Check the color combination was marked as selected + Edit the bg color\n...checkAndUpdateBackgroundColor({\n checkCC: 2,\n checkNoCC: 3,\n changeType: 'bg',\n change: 'black-50',\n finalSelector: `iframe .${snippets[0].id}.o_cc.o_cc2.bg-black-50`,\n}),\n\n// Check the current color palette selection + Change the bg color\n...checkAndUpdateBackgroundColor({\n checkCC: 2,\n checkBg: 'black-50',\n changeType: 'bg',\n change: '800',\n finalSelector: `iframe .${snippets[0].id}.o_cc.o_cc2.bg-800:not(.bg-black-50)`,\n}),\n\n// Check the current color palette selection + Change the color combination\n// again. It should keep the bg color class.\n...checkAndUpdateBackgroundColor({\n checkCC: 2,\n checkBg: '800',\n checkNoBg: 'black-50',\n changeType: 'cc',\n change: 4,\n finalSelector: `iframe .${snippets[0].id}.o_cc.o_cc4:not(.o_cc2).bg-800`,\n}),\n\n// Check the current color palette status + Replace the bg color by a gradient\n...checkAndUpdateBackgroundColor({\n checkCC: 4,\n checkNoCC: 2,\n checkBg: '800',\n changeType: 'gradient',\n change: gradients[0],\n finalSelector: `iframe .${snippets[0].id}.o_cc.o_cc4:not(.bg-800)[style*=\"background-image: ${gradients[0]}\"]`,\n}),\n\n// Check the current color palette status + Replace the gradient\n...checkAndUpdateBackgroundColor({\n checkCC: 4,\n checkNoBg: '800',\n checkGradient: gradients[0],\n changeType: 'gradient',\n change: gradients[1],\n finalSelector: `iframe .${snippets[0].id}.o_cc.o_cc4[style*=\"background-image: ${gradients[1]}\"]:not([style*=\"background-image: ${gradients[0]}\"])`,\n}),\n\n// Check the current color palette selection + Change the color combination\n// again. It should keep the gradient.\n...checkAndUpdateBackgroundColor({\n checkCC: 4,\n checkGradient: gradients[1],\n checkNoGradient: gradients[0],\n changeType: 'cc',\n change: 1,\n finalSelector: `iframe .${snippets[0].id}.o_cc.o_cc1:not(.o_cc4)[style*=\"background-image: ${gradients[1]}\"]`,\n}),\n\n// Final check of the color status in the color palette\n...checkAndUpdateBackgroundColor({\n checkCC: 1,\n checkNoCC: 4,\n checkGradient: gradients[1],\n}),\n\n// Now, add an image on top of that color combination + gradient\n{\n // Close the palette before selecting a media.\n trigger: '.snippet-option-ColoredLevelBackground we-title',\n content: 'Close palette',\n},\nwTourUtils.changeOption('ColoredLevelBackground', '[data-name=\"bg_image_toggle_opt\"]'),\n{\n trigger: '.o_existing_attachment_cell img',\n content: \"Select an image in the media dialog\",\n},\n{\n trigger: `iframe .${snippets[0].id}.o_cc.o_cc1`,\n run: function () {\n const parts = weUtils.backgroundImageCssToParts(this.$anchor.css('background-image'));\n if (!parts.url || !parts.url.startsWith('url(')) {\n console.error('An image should have been added as background.');\n }\n if (parts.gradient !== gradients[1]) {\n console.error('The gradient should have been kept when adding the background image');\n }\n },\n},\n\n// Replace the gradient while there is a background-image\n...checkAndUpdateBackgroundColor({\n checkCC: 1,\n checkGradient: gradients[1],\n changeType: 'gradient',\n change: gradients[0],\n finalSelector: `iframe .${snippets[0].id}.o_cc.o_cc1:not([style*=\"${gradients[1]}\"])`,\n finalRun: function () {\n const parts = weUtils.backgroundImageCssToParts(this.$anchor.css('background-image'));\n if (!parts.url || !parts.url.startsWith('url(')) {\n console.error('The image should have been kept when changing the gradient');\n }\n if (parts.gradient !== gradients[0]) {\n console.error('The gradient should have been changed');\n }\n },\n}),\n\n// Customize gradient\nwTourUtils.changeBackgroundColor(),\nswitchTo('gradient'),\n// Avoid navigating across tabs to maintain current editor state\n// Step colors\n...updateAndCheckCustomGradient({\n updateStep: {\n trigger: '.colorpicker .o_custom_gradient_scale',\n content: 'Add step',\n run: 'click',\n },\n checkGradient: 'linear-gradient(135deg, rgb(203, 94, 238) 0%, rgb(203, 94, 238) 0%, rgb(75, 225, 236) 100%)',\n}),\n...updateAndCheckCustomGradient({\n updateStep: {\n trigger: '.colorpicker .o_slider_multi input.active',\n content: 'Move step',\n run: () => {\n const slider = $('.colorpicker .o_slider_multi input.active');\n slider.val(45);\n slider.trigger('click');\n },\n },\n checkGradient: 'linear-gradient(135deg, rgb(203, 94, 238) 0%, rgb(203, 94, 238) 45%, rgb(75, 225, 236) 100%)',\n}),\n...updateAndCheckCustomGradient({\n updateStep: {\n trigger: '.colorpicker .o_color_picker_inputs .o_hex_div input',\n content: 'Pick step color',\n run: 'text #FF0000',\n },\n checkGradient: 'linear-gradient(135deg, rgb(203, 94, 238) 0%, rgb(255, 0, 0) 45%, rgb(75, 225, 236) 100%)',\n}),\n...updateAndCheckCustomGradient({\n updateStep: {\n trigger: '.colorpicker .o_remove_color',\n content: 'Delete step',\n run: 'click',\n },\n checkGradient: 'linear-gradient(135deg, rgb(203, 94, 238) 0%, rgb(75, 225, 236) 100%)',\n}),\n// Linear\n...updateAndCheckCustomGradient({\n updateStep: {\n trigger: '.colorpicker input[data-name=\"angle\"]',\n content: 'Change angle',\n run: 'text_blur 50',\n },\n checkGradient: 'linear-gradient(50deg, rgb(203, 94, 238) 0%, rgb(75, 225, 236) 100%)',\n}),\n// Radial\n...updateAndCheckCustomGradient({\n updateStep: {\n trigger: '.colorpicker we-button[data-gradient-type=\"radial-gradient\"]',\n content: 'Switch to Radial',\n run: 'click',\n },\n checkGradient: 'radial-gradient(circle farthest-side at 25% 25%, rgb(203, 94, 238) 0%, rgb(75, 225, 236) 100%)',\n}),\n...updateAndCheckCustomGradient({\n updateStep: {\n trigger: '.colorpicker input[data-name=\"positionX\"]',\n content: 'Change X position',\n run: 'text_blur 33',\n },\n checkGradient: 'radial-gradient(circle farthest-side at 33% 25%, rgb(203, 94, 238) 0%, rgb(75, 225, 236) 100%)',\n}),\n...updateAndCheckCustomGradient({\n updateStep: {\n trigger: '.colorpicker input[data-name=\"positionY\"]',\n content: 'Change Y position',\n run: 'text_blur 75',\n },\n checkGradient: 'radial-gradient(circle farthest-side at 33% 75%, rgb(203, 94, 238) 0%, rgb(75, 225, 236) 100%)',\n}),\n...updateAndCheckCustomGradient({\n updateStep: {\n trigger: '.colorpicker we-button[data-gradient-size=\"closest-side\"]',\n content: 'Change color spread size',\n run: 'click',\n },\n checkGradient: 'radial-gradient(circle closest-side at 33% 75%, rgb(203, 94, 238) 0%, rgb(75, 225, 236) 100%)',\n}),\n// Revert to predefined gradient\n{\n trigger: `.o_we_color_btn[data-color=\"${gradients[0]}\"]`,\n content: `Revert to predefiend gradient ${gradients[0]}`,\n run: 'click',\n},\n\n// Replace the gradient by a bg color\n...checkAndUpdateBackgroundColor({\n checkCC: 1,\n checkGradient: gradients[0],\n checkNoGradient: gradients[1],\n changeType: 'bg',\n change: 'black-75',\n finalSelector: `iframe .${snippets[0].id}.o_cc.o_cc1.bg-black-75[style^=\"background-image: url(\"]:not([style*=\"${gradients[0]}\"])`\n}),\n\n// Re-add a gradient\n...checkAndUpdateBackgroundColor({\n checkCC: 1,\n checkBg: 'black-75',\n checkNoGradient: gradients[0],\n changeType: 'gradient',\n change: gradients[1],\n finalSelector: `iframe .${snippets[0].id}.o_cc.o_cc1:not(.bg-black-75)`,\n finalRun: function () {\n const parts = weUtils.backgroundImageCssToParts(this.$anchor.css('background-image'));\n if (!parts.url || !parts.url.startsWith('url(')) {\n console.error('The image should have been kept when re-adding the gradient');\n }\n if (parts.gradient !== gradients[1]) {\n console.error('The gradient should have been re-added');\n }\n },\n}),\n\n// Final check of color selection and removing the image\n...checkAndUpdateBackgroundColor({\n checkCC: 1,\n checkNoBg: 'black-75',\n checkGradient: gradients[1],\n}),\nwTourUtils.changeOption('ColoredLevelBackground', '[data-name=\"bg_image_toggle_opt\"]', 'image toggle', 'top', true),\n{\n trigger: `iframe .${snippets[0].id}.o_cc.o_cc1[style*=\"background-image: ${gradients[1]}\"]`,\n run: () => null,\n},\n\n// Now removing all colors via the 'None' button (note: colorpicker still opened)\n{\n trigger: '.o_colorpicker_reset',\n content: \"Click on the None button of the color palette\",\n},\n{\n trigger: `iframe .${snippets[0].id}:not(.o_cc):not(.o_cc1):not([style*=\"background-image\"])`,\n content: \"All color classes and properties should have been removed\",\n run: () => null,\n}\n]);\n", "/** @odoo-module **/\n\nimport wTourUtils from 'website.tour_utils';\n\nwTourUtils.registerWebsitePreviewTour('snippet_cache_across_websites', {\n edition: true,\n test: true,\n url: '/@/'\n}, [\n {\n content: \"Check that the custom snippet is displayed\",\n trigger: '#snippet_custom_body span:contains(\"custom_snippet_test\")',\n run: () => null,\n },\n // There's no need to save, but canceling might or might not show a popup...\n ...wTourUtils.clickOnSave(),\n ...wTourUtils.testSwitchWebsite('Test Website'),\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n content: \"Check that the custom snippet is not here\",\n extra_trigger: '#oe_snippets:not(:has(#snippet_custom_body span:contains(\"custom_snippet_test\")))',\n trigger: '#oe_snippets:has(#snippet_custom.d-none)',\n run: () => null,\n },\n]);\n", "/** @odoo-module */\n\nimport wTourUtils from 'website.tour_utils';\n\nwTourUtils.registerWebsitePreviewTour('snippet_countdown', {\n test: true,\n url: '/',\n edition: true,\n}, [\n wTourUtils.dragNDrop({id: 's_countdown', name: 'Countdown'}),\n wTourUtils.clickOnSnippet({id: 's_countdown', name: 'Countdown'}),\n wTourUtils.changeOption('countdown', 'we-select:has([data-end-action]) we-toggler', 'end action'),\n wTourUtils.changeOption('countdown', 'we-button[data-end-action=\"message\"]', 'end action'),\n wTourUtils.changeOption('countdown', 'we-button.toggle-edit-message', 'message preview'),\n // The next two steps check that the end message does not disappear when a\n // widgets_start_request is triggered.\n {\n content: \"Hover an option which has a preview\",\n trigger: '[data-select-class=\"o_half_screen_height\"]',\n run: function (actions) {\n this.$anchor.trigger('mouseover');\n this.$anchor.trigger('mouseenter');\n },\n },\n {\n content: \"Check that the countdown message is still displayed\",\n trigger: 'iframe .s_countdown .s_picture',\n run: () => {\n // Just a visibility check\n\n // Also make sure the mouseout and mouseleave are triggered so that\n // next steps make sense.\n // TODO the next steps are not actually testing anything without\n // it and the mouseout and mouseleave make sense but really it\n // should not be *necessary* to simulate those for the editor flow\n // to make some sense.\n const $previousAnchor = $('[data-select-class=\"o_half_screen_height\"]');\n $previousAnchor.trigger('mouseout');\n $previousAnchor.trigger('mouseleave');\n },\n },\n // Next, we change the end action to message and no countdown while the edit\n // message toggle is still activated. It should hide the countdown\n wTourUtils.changeOption('countdown', 'we-select:has([data-end-action]) we-toggler', 'end action'),\n wTourUtils.changeOption('countdown', 'we-button[data-end-action=\"message_no_countdown\"]', 'end action'),\n {\n content: \"Check that the countdown is not displayed\",\n trigger: 'iframe .s_countdown:has(.s_countdown_canvas_wrapper:not(:visible))',\n run: () => null, // Just a visibility check\n },\n {\n content: \"Check that the message is still displayed\",\n trigger: 'iframe .s_countdown .s_picture',\n run: () => null, // Just a visibility check\n },\n]);\n", "/** @odoo-module */\n\nimport wTourUtils from 'website.tour_utils';\n\nwTourUtils.registerWebsitePreviewTour('snippet_editor_panel_options', {\n test: true,\n url: '/',\n edition: true,\n}, [\nwTourUtils.dragNDrop({\n id: 's_text_image',\n name: 'Text - Image',\n}),\n// Test keeping the text selection when using the width option.\n{\n content: \"Click on the first paragraph.\",\n trigger: 'iframe .s_text_image p',\n}, {\n content: \"The text toolbar should be visible. The paragraph should be selected.\",\n trigger: '#oe_snippets .o_we_customize_panel > #o_we_editor_toolbar_container',\n run() {\n const iframeDocument = document.querySelector('.o_iframe').contentDocument;\n const pText = iframeDocument.querySelector('.s_text_image p').textContent;\n const selection = iframeDocument.getSelection().toString();\n if (pText !== selection) {\n console.error(\"The paragraph was not correctly selected.\");\n }\n },\n}, {\n content: \"Click on the width option.\",\n trigger: '[data-select-class=\"o_container_small\"]',\n}, {\n content: \"The snippet should have the correct class.\",\n trigger: 'iframe .s_text_image > .o_container_small',\n run: () => {}, // It's a check.\n}, {\n content: \"The text toolbar should still be visible, and the text still selected.\",\n trigger: '#oe_snippets .o_we_customize_panel > #o_we_editor_toolbar_container',\n run() {\n const iframeDocument = document.querySelector('.o_iframe').contentDocument;\n const pText = iframeDocument.querySelector('.s_text_image p').textContent;\n const selection = iframeDocument.getSelection().toString();\n if (pText !== selection) {\n console.error(\"The paragraph text selection was lost.\");\n }\n },\n},\n// Test the anchor option.\n{\n content: \"Click on the anchor option\",\n trigger: '#oe_snippets .snippet-option-anchor we-button',\n run() {\n // The clipboard cannot be accessed from a script.\n // https://w3c.github.io/editing/docs/execCommand/#dfn-the-copy-command\n // The execCommand is patched for that step so that ClipboardJS still\n // sends the 'success' event.\n const oldExecCommand = document.execCommand;\n document.execCommand = () => true;\n this.$anchor[0].click();\n document.execCommand = oldExecCommand;\n }\n}, {\n content: \"Check the copied url from the notification toast\",\n trigger: '.o_notification_manager .o_notification_content',\n run() {\n const { textContent } = this.$anchor[0];\n const url = textContent.substring(textContent.indexOf('/'));\n\n // The url should not target the client action\n if (url.startsWith('/@')) {\n console.error('The anchor option should target the frontend');\n }\n\n const iframeDocument = document.querySelector('.o_iframe').contentDocument;\n const snippetId = iframeDocument.querySelector('.s_text_image').id;\n if (!url || url.indexOf(snippetId) < 0) {\n console.error('The anchor option does not target the correct snippet.');\n }\n },\n},\n// Test keeping the text selection when adding columns to a snippet with none.\nwTourUtils.goBackToBlocks(),\nwTourUtils.dragNDrop({\n id: 's_text_block',\n name: 'Text',\n}),\n{\n content: \"Click on the first paragraph.\",\n trigger: 'iframe .s_text_block p',\n}, {\n content: \"The text toolbar should be visible. The paragraph should be selected.\",\n trigger: '#oe_snippets .o_we_customize_panel > #o_we_editor_toolbar_container',\n run() {\n const iframeDocument = document.querySelector('.o_iframe').contentDocument;\n const pText = iframeDocument.querySelector('.s_text_block p').textContent;\n const selection = iframeDocument.getSelection().toString();\n if (pText !== selection) {\n console.error(\"The paragraph was not correctly selected.\");\n }\n },\n}, {\n content: \"Click on the columns option.\",\n trigger: '.snippet-option-layout_column we-select',\n},\n{\n content: \"Change the number of columns.\",\n trigger: '.snippet-option-layout_column [data-select-count=\"3\"]',\n}, {\n content: \"The snippet should have the correct number of columns.\",\n trigger: 'iframe .s_text_block .container > .row',\n run() {\n if (this.$anchor[0].childElementCount !== 3) {\n console.error(\"The snippet does not have the correct number of columns\");\n }\n },\n}, {\n content: \"The text toolbar should still be visible, and the text still selected.\",\n trigger: '#oe_snippets .o_we_customize_panel > #o_we_editor_toolbar_container',\n run() {\n const iframeDocument = document.querySelector('.o_iframe').contentDocument;\n const pText = iframeDocument.querySelector('.s_text_block p').textContent;\n const selection = iframeDocument.getSelection().toString();\n if (pText !== selection) {\n console.error(\"The paragraph text selection was lost.\");\n }\n },\n},\n// Test keeping the text selection when removing all columns of a snippet.\n{\n content: \"Click on the columns option.\",\n trigger: '.snippet-option-layout_column we-select',\n},\n{\n content: \"Change the number of columns.\",\n trigger: '.snippet-option-layout_column [data-select-count=\"0\"]',\n}, {\n content: \"The snippet should have the correct number of columns.\",\n trigger: 'iframe .s_text_block .container:not(:has(.row))',\n}, {\n content: \"The text toolbar should still be visible, and the text still selected.\",\n trigger: '#oe_snippets .o_we_customize_panel > #o_we_editor_toolbar_container',\n run() {\n const iframeDocument = document.querySelector('.o_iframe').contentDocument;\n const pText = iframeDocument.querySelector('.s_text_block p').textContent;\n const selection = iframeDocument.getSelection().toString();\n if (pText !== selection) {\n console.error(\"The paragraph text selection was lost.\");\n }\n },\n},\n// Test keeping the text selection when toggling the grid mode.\nwTourUtils.changeOption(\"layout_column\", 'we-button[data-name=\"grid_mode\"]'),\n{\n content: \"The snippet row should have the grid mode class.\",\n trigger: \"iframe .s_text_block .row.o_grid_mode\",\n run: () => {}, // It's a check.\n}, {\n content: \"The text toolbar should still be visible, and the text still selected.\",\n trigger: \"#oe_snippets .o_we_customize_panel > #o_we_editor_toolbar_container\",\n run() {\n const iframeDocument = document.querySelector(\".o_iframe\").contentDocument;\n const pText = iframeDocument.querySelector(\".s_text_block p\").textContent;\n const selection = iframeDocument.getSelection().toString();\n if (pText !== selection) {\n console.error(\"The paragraph text selection was lost.\");\n }\n },\n},\n// Test keeping the text selection when toggling back the normal mode.\nwTourUtils.changeOption(\"layout_column\", 'we-button[data-name=\"normal_mode\"]'),\n{\n content: \"The snippet row should not have the grid mode class anymore.\",\n trigger: \"iframe .s_text_block .row:not(.o_grid_mode)\",\n run: () => {}, // It's a check.\n}, {\n content: \"The text toolbar should still be visible, and the text still selected.\",\n trigger: \"#oe_snippets .o_we_customize_panel > #o_we_editor_toolbar_container\",\n run() {\n const iframeDocument = document.querySelector(\".o_iframe\").contentDocument;\n const pText = iframeDocument.querySelector(\".s_text_block p\").textContent;\n const selection = iframeDocument.getSelection().toString();\n if (pText !== selection) {\n console.error(\"The paragraph text selection was lost.\");\n }\n },\n},\n// Test close dropdowns if click anywhere outside the dropdown\n{\n content: \"Open text style dropdown.\",\n trigger: \"#style button.dropdown-toggle\",\n}, {\n content: \"Check if dropdown opened correctly.\",\n trigger: \"#style button[data-bs-toggle=dropdown][aria-expanded=true]\",\n run: () => {}, // It's a check.\n}, {\n content: \"Click on the first paragraph again.\",\n trigger: \"iframe .s_text_block p\",\n}, {\n content: \"Check if dropdown closed correctly.\",\n trigger: \"#style button[data-bs-toggle=dropdown][aria-expanded=false]\",\n run: () => {}, // It's a check.\n},\n...wTourUtils.clickOnSave(),\n]);\n", "odoo.define(\"website.tour.snippet_empty_parent_autoremove\", function (require) {\n\"use strict\";\n\nconst wTourUtils = require('website.tour_utils');\n\nfunction removeSelectedBlock() {\n return {\n content: \"Remove selected block\",\n trigger: '#oe_snippets we-customizeblock-options:nth-last-child(3) .oe_snippet_remove',\n };\n}\n\nwTourUtils.registerWebsitePreviewTour('snippet_empty_parent_autoremove', {\n test: true,\n url: '/',\n edition: true,\n}, [\n // Base case: remove both columns from text - image\n wTourUtils.dragNDrop({\n id: 's_text_image',\n name: 'Text - Image',\n }),\n {\n content: \"Click on second column\",\n trigger: 'iframe #wrap .s_text_image .row > :nth-child(2)',\n },\n removeSelectedBlock(),\n {\n content: \"Click on first column\",\n trigger: 'iframe #wrap .s_text_image .row > :first-child',\n },\n removeSelectedBlock(),\n {\n content: \"Check that #wrap is empty\",\n trigger: 'iframe #wrap:empty',\n },\n\n // Banner: test that parallax, bg-filter and shape are not treated as content\n wTourUtils.dragNDrop({\n id: 's_banner',\n name: 'Banner',\n }),\n wTourUtils.clickOnSnippet({\n id: 's_banner',\n name: 'Banner',\n }),\n {\n content: \"Check that parallax is present\",\n trigger: 'iframe #wrap .s_banner .s_parallax_bg',\n run: () => null,\n },\n wTourUtils.changeOption('ColoredLevelBackground', 'Shape'),\n {\n content: \"Check that shape is present\",\n trigger: 'iframe #wrap .s_banner .o_we_shape',\n run: () => null,\n },\n {\n content: \"Click on first column\",\n trigger: 'iframe #wrap .s_banner .row > :first-child',\n },\n removeSelectedBlock(),\n {\n content: \"Check that #wrap is empty\",\n trigger: 'iframe #wrap:empty',\n run: () => null,\n },\n]);\n});\n", "/** @odoo-module */\n\nimport wTourUtils from 'website.tour_utils';\n\nwTourUtils.registerWebsitePreviewTour('snippet_image_gallery', {\n test: true,\n url: '/',\n edition: true,\n}, [\n wTourUtils.dragNDrop({id: 's_image_gallery', name: 'Images Wall'}),\n ...wTourUtils.clickOnSave(),\n {\n content: 'Click on an image of the Image Wall',\n trigger: 'iframe .s_image_gallery img',\n run: 'click',\n },\n {\n content: 'Check that the modal has opened properly',\n trigger: 'iframe .s_gallery_lightbox img',\n run: () => {}, // This is a check.\n },\n]);\n\nwTourUtils.registerWebsitePreviewTour(\"snippet_image_gallery_remove\", {\n test: true,\n url: \"/\",\n edition: true,\n}, [\n wTourUtils.dragNDrop({\n id: \"s_image_gallery\",\n name: \"Image Gallery\",\n}), wTourUtils.clickOnSnippet({\n id: 's_image_gallery',\n name: 'Image Gallery',\n}), {\n content: \"Click on Remove all\",\n trigger: \"we-button:has(div:contains('Remove all'))\",\n}, {\n content: \"Click on Add Images\",\n trigger: \"iframe span:contains('Add Images')\",\n}, {\n content: \"Click on the first new image\",\n trigger: \".o_select_media_dialog img[title='s_default_image.jpg']\",\n}, {\n content: \"Click on the second new image\",\n trigger: \".o_select_media_dialog img[title='s_default_image2.jpg']\",\n},\n wTourUtils.addMedia(),\n {\n content: \"Click on the image of the Image Gallery snippet\",\n trigger: \"iframe .s_image_gallery .carousel-item.active img\",\n}, {\n content: \"Check that the Snippet Editor of the clicked image has been loaded\",\n trigger: \"we-customizeblock-options span:contains('Image'):not(:contains('Image Gallery'))\",\n run: () => null,\n}, {\n content: \"Click on Remove Block\",\n trigger: \".o_we_customize_panel we-title:has(span:contains('Image Gallery')) we-button[title='Remove Block']\",\n}, {\n content: \"Check that the Image Gallery snippet has been removed\",\n trigger: \"iframe #wrap:not(:has(.s_image_gallery))\",\n run: () => null,\n}]);\n\nwTourUtils.registerWebsitePreviewTour(\"snippet_image_gallery_reorder\", {\n test: true,\n url: \"/\",\n edition: true,\n}, [\n wTourUtils.dragNDrop({\n id: \"s_image_gallery\",\n name: \"Image Gallery\",\n }),\n{\n content: \"Click on the first image of the snippet\",\n trigger: \"iframe .s_image_gallery .carousel-item.active img\",\n},\n wTourUtils.changeOption('ImageTools', 'we-select:contains(\"Filter\") we-toggler'),\n wTourUtils.changeOption('ImageTools', '[data-gl-filter=\"blur\"]'),\n{\n content: \"Check that the image has the correct filter\",\n trigger: \".snippet-option-ImageTools we-select:contains('Filter') we-toggler:contains('Blur')\",\n run: () => null, // This is a check.\n}, {\n content: \"Click on move to next\",\n trigger: \".snippet-option-gallery_img we-button[data-position='next']\",\n}, {\n content: \"Check that the image has been moved\",\n trigger: \"iframe .s_image_gallery .carousel-item.active img[data-index='1']\",\n run: () => null, // This is a check.\n}, {\n content: \"Click on the footer to reload the editor panel\",\n trigger: \"iframe #footer\",\n}, {\n content: \"Check that the footer options have been loaded\",\n trigger: \".snippet-option-HideFooter we-button:contains('Page Visibility')\",\n run: () => null, // This is a check.\n}, {\n content: \"Click on the moved image\",\n trigger: \"iframe .s_image_gallery .carousel-item.active img[data-index='1'][data-gl-filter='blur']\",\n}, {\n content: \"Check that the image still has the correct filter\",\n trigger: \".snippet-option-ImageTools we-select:contains('Filter') we-toggler:contains('Blur')\",\n run: () => null, // This is a check.\n}, {\n content: \"Click to access next image\",\n trigger: \"iframe .s_image_gallery .carousel-control-next\",\n}, {\n content: \"Check that the option has changed\",\n trigger: \".snippet-option-ImageTools we-select:contains('Filter') we-toggler:not(:contains('Blur'))\",\n run: () => null, // This is a check.\n}, {\n content: \"Click to access previous image\",\n trigger: \"iframe .s_image_gallery .carousel-control-prev\",\n}, {\n content: \"Check that the option is restored\",\n trigger: \".snippet-option-ImageTools we-select:contains('Filter') we-toggler:contains('Blur')\",\n run: () => null, // This is a check.\n}]);\n\nwTourUtils.registerWebsitePreviewTour(\"snippet_image_gallery_thumbnail_update\", {\n test: true,\n url: \"/\",\n edition: true,\n}, [\n wTourUtils.dragNDrop({\n id: \"s_image_gallery\",\n name: \"Image Gallery\",\n }),\n wTourUtils.clickOnSnippet({\n id: \"s_image_gallery\",\n name: \"Image Gallery\",\n }),\n wTourUtils.changeOption(\"gallery\", \"we-button[data-add-images]\"),\n{\n content: \"Click on the default image\",\n trigger: \".o_select_media_dialog img[title='s_default_image.jpg']\",\n},\n wTourUtils.addMedia(),\n{\n content: \"Check that the new image has been added\",\n trigger: \"iframe .s_image_gallery:has(img[data-index='3'])\",\n run: () => null, // This is a check.\n}, {\n content: \"Check that the thumbnail of the first image has not been changed\",\n trigger: \"iframe .s_image_gallery ul.carousel-indicators li:first-child[style='background-image: url(/web/image/website.library_image_08)']\",\n run: () => null, // This is a check.\n}]);\n", "/** @odoo-module **/\n\nimport wTourUtils from \"website.tour_utils\";\n\nconst selectSignImageStep = {\n content: \"Click on sign image\",\n extra_trigger: \".o_we_customize_panel:not(:has(.snippet-option-gallery_img))\",\n trigger: \"iframe .s_image_gallery img[data-original-src*='sign']\",\n};\n// Without reselecting the image, the tour manages to click on the\n// move button before the active image is updated.\n// We need to first select the footer and then the social media in it to prevent\n// race condition with the potential loader after image wall order option.\nconst reselectSignImageSteps = [{\n content: \"Select footer\",\n extra_trigger: \"iframe .s_image_gallery .o_masonry_col:nth-child(3):has(img[data-index='5'])\",\n trigger: \"iframe footer\",\n}, {\n content: \"Select social media in footer\",\n trigger: \"iframe footer .s_social_media\",\n}, {\n content: \"Check that the social media options are displayed\",\n trigger: \"we-list[data-name='social_media_list']\",\n run: () => {}, // This is a check.\n}, selectSignImageStep];\n\nwTourUtils.registerWebsitePreviewTour(\"snippet_images_wall\", {\n test: true,\n url: \"/\",\n edition: true,\n}, [\n wTourUtils.dragNDrop({\n id: \"s_images_wall\",\n name: \"Images Wall\",\n}), wTourUtils.clickOnSnippet({\n id: \"s_image_gallery\",\n name: \"Images Wall\",\n}),\nselectSignImageStep,\n{\n content: \"Click on add a link\",\n trigger: \".snippet-option-ReplaceMedia we-button[data-set-link]\",\n}, {\n content: \"Change the link of the image\",\n trigger: \".snippet-option-ReplaceMedia [data-set-url] input\",\n run: \"text /contactus\",\n}, {\n content: \"Click on move to previous\",\n trigger: \".snippet-option-gallery_img we-button[data-position='prev']\",\n}, {\n content: \"Check if sign is in second column\",\n trigger: \"iframe .s_image_gallery .o_masonry_col:nth-child(2):has(a[href='/contactus'] img[data-index='1'][data-original-src*='sign'])\",\n run: () => {}, // This is a check.\n},\n...reselectSignImageSteps,\n{\n content: \"Click on move to first\",\n trigger: \".snippet-option-gallery_img we-button[data-position='first']\",\n}, {\n content: \"Check if sign is in first column\",\n trigger: \"iframe .s_image_gallery .o_masonry_col:nth-child(1):has(img[data-index='0'][data-original-src*='sign'])\",\n run: () => {}, // This is a check.\n},\n...reselectSignImageSteps,\n{\n content: \"Click on move to previous\",\n trigger: \".snippet-option-gallery_img we-button[data-position='prev']\",\n}, {\n content: \"Check if sign is in third column\",\n trigger: \"iframe .s_image_gallery .o_masonry_col:nth-child(3):has(img[data-index='5'][data-original-src*='sign'])\",\n run: () => {}, // This is a check.\n},\n...reselectSignImageSteps,\n{\n content: \"Click on move to next\",\n trigger: \".snippet-option-gallery_img we-button[data-position='next']\",\n}, {\n content: \"Check if sign is in first column\",\n trigger: \"iframe .s_image_gallery .o_masonry_col:nth-child(1):has(img[data-index='0'][data-original-src*='sign'])\",\n run: () => {}, // This is a check.\n},\n...reselectSignImageSteps,\n{\n content: \"Click on move to last\",\n trigger: \".snippet-option-gallery_img we-button[data-position='last']\",\n}, {\n content: \"Check layout\",\n trigger: \"iframe .s_image_gallery .o_masonry_col:nth-child(3):has(img[data-index='5'][data-original-src*='sign'])\",\n run: () => {}, // This is a check.\n}]);\n", "/** @odoo-module */\n\nimport wTourUtils from 'website.tour_utils';\n\nwTourUtils.registerWebsitePreviewTour('snippet_popup_add_remove', {\n test: true,\n url: '/',\n edition: true,\n}, [{\n content: 'Drop s_popup snippet',\n trigger: '.oe_snippet:has( > [data-snippet=\"s_popup\"]) .oe_snippet_thumbnail',\n run: \"drag_and_drop iframe #wrap\",\n}, {\n content: 'Edit s_popup snippet',\n in_modal: false,\n trigger: 'iframe #wrap.o_editable [data-snippet=\"s_popup\"] .row > div', // Click deep in the snippet structure\n}, {\n content: 'Check s_popup setting are loaded, wait panel is visible',\n in_modal: false,\n trigger: '.o_we_customize_panel',\n run: () => null,\n},\n...wTourUtils.clickOnSave(),\n...wTourUtils.clickOnEditAndWaitEditMode(),\n{\n content: 'Toggle the visibility of the Popup',\n in_modal: false,\n trigger: '.o_we_invisible_el_panel .o_we_invisible_entry:contains(\"Popup\")',\n}, {\n content: 'Edit s_popup snippet(2)',\n in_modal: false,\n trigger: 'iframe #wrap.o_editable [data-snippet=\"s_popup\"] h2',\n run: function() {\n // Simulating pressing enter.\n const anchor = this.$anchor[0];\n // Trick the editor into keyboardType === 'PHYSICAL' and press enter\n anchor.dispatchEvent(new KeyboardEvent(\"keydown\", { key: \"Enter\", bubbles: true }));\n // Trigger editor's '_onInput' handler, which leads to historyRollback.\n anchor.dispatchEvent(new InputEvent('input', { inputType: 'insertLineBreak', bubbles: true }));\n }\n}, {\n content: 'Check the s_popup was visible',\n in_modal: false,\n trigger: 'iframe #wrap.o_editable [data-snippet=\"s_popup\"]:not(.d-none)',\n run: () => null,\n}, {\n content: `Remove the s_popup snippet`,\n in_modal: false,\n trigger: '.o_we_customize_panel we-customizeblock-options:contains(\"Popup\") we-button.oe_snippet_remove:first',\n}, {\n content: 'Check the s_popup was removed',\n in_modal: false,\n trigger: 'iframe #wrap.o_editable:not(:has([data-snippet=\"s_popup\"]))',\n run: () => null,\n},\n// Test that undoing dropping the snippet removes the invisible elements panel.\n{\n content: \"Drop the snippet again.\",\n trigger: '#oe_snippets .oe_snippet:has(> [data-snippet=\"s_popup\"]) .oe_snippet_thumbnail',\n run: \"drag_and_drop iframe #wrap\",\n}, {\n content: \"The popup should be in the invisible elements panel.\",\n in_modal: false,\n trigger: '.o_we_invisible_el_panel .o_we_invisible_entry',\n run: () => null, // It's a check.\n}, {\n content: \"Click on the 'undo' button.\",\n in_modal: false,\n trigger: '#oe_snippets button[data-action=\"undo\"]',\n}, {\n content: \"Check that the s_popup was removed.\",\n in_modal: false,\n trigger: 'iframe #wrap.o_editable:not(:has([data-snippet=\"s_popup\"]))',\n run: () => null, // It's a check.\n}, {\n content: \"The invisible elements panel should also be removed.\",\n trigger: '#oe_snippets:has(.o_we_invisible_el_panel.d-none)',\n run: () => null, // It's a check.\n}]);\n", "/** @odoo-module */\n\nimport wTourUtils from \"website.tour_utils\";\n\nconst snippets = [\n {\n id: \"s_popup\",\n name: \"Popup\",\n },\n {\n id: \"s_media_list\",\n name: \"Media List\",\n },\n {\n id: \"s_three_columns\",\n name: \"Columns\",\n },\n];\n\nconst setOnScrollAnim = function () {\n return [\n wTourUtils.changeOption(\"WebsiteAnimate\", 'we-select[data-is-animation-type-selection=\"true\"] we-toggler'),\n wTourUtils.changeOption(\"WebsiteAnimate\", 'we-button[data-animation-mode=\"onScroll\"]'),\n wTourUtils.changeOption(\"WebsiteAnimate\", 'we-select[data-name=\"animation_effect_opt\"] we-toggler'),\n wTourUtils.changeOption(\"WebsiteAnimate\", 'we-button[data-name=\"o_anim_slide_in_opt\"]'),\n ];\n};\n\nwTourUtils.registerWebsitePreviewTour(\"snippet_popup_and_animations\", {\n test: true,\n url: \"/\",\n edition: true,\n}, [\n wTourUtils.dragNDrop(snippets[1]), // Media List\n wTourUtils.dragNDrop(snippets[1]), // Media List\n wTourUtils.dragNDrop(snippets[2]), // Columns\n wTourUtils.clickOnElement(\"3rd columns\", \"iframe .s_three_columns .row > :last-child\"),\n ...setOnScrollAnim(),\n {\n content: \"Open the Cookies Bar.\",\n trigger: \".o_we_invisible_el_panel .o_we_invisible_entry\",\n },\n {\n content: \"Scroll to top\",\n trigger: \"iframe #wrapwrap\",\n extra_trigger: \"iframe #website_cookies_bar:not(.d-none)\",\n run: function () {\n const animatedColumnEl = this.$anchor[0].querySelector(\".s_three_columns .row > :last-child\");\n // When the animated element is fully visible, its animation delay\n // should be rounded to -1 in the following condition.\n if (Math.round(parseFloat(animatedColumnEl.style.animationDelay)) !== -1) {\n console.error(\"The scroll animation in the page did not start properly with the cookies bar open.\");\n }\n this.$anchor[0].scrollTo({\n top: 0,\n left: 0,\n behavior: 'smooth'\n });\n },\n },\n {\n content: \"Wait for the page to be scrolled to the top.\",\n trigger: \"iframe .s_three_columns .row > :last-child:not(.o_animating)\",\n extra_trigger: \"iframe header#top:not(.o_header_affixed)\",\n run: function () {\n // If the column has been animated successfully, the animation delay\n // should be set to approximately zero when it is not visible.\n // The main goal of the following condition is to verify if the\n // animation delay is being updated as expected.\n if (Math.round(parseFloat(this.$anchor[0].style.animationDelay)) !== 0) {\n console.error(\"The scroll animation in the page did not end properly with the cookies bar open.\");\n }\n },\n },\n {\n content: \"Close the Cookies Bar.\",\n trigger: \".o_we_invisible_el_panel .o_we_invisible_entry\",\n run: \"click\",\n },\n wTourUtils.dragNDrop(snippets[0]), // Popup\n wTourUtils.dragNDrop(snippets[1]), // Media List\n {\n content: \"Drag the Columns building block and drop it at the bottom of the popup.\",\n trigger: '#oe_snippets .oe_snippet[name=\"Columns\"] .oe_snippet_thumbnail:not(.o_we_already_dragging)',\n extra_trigger: \".o_website_preview.editor_enable.editor_has_snippets\",\n run: \"drag_and_drop iframe #wrapwrap .modal-content .s_media_list .container > .row > :last-child\",\n },\n wTourUtils.clickOnElement(\"3rd columns\", \"iframe .s_popup .s_three_columns .row > :last-child\"),\n ...setOnScrollAnim(),\n {\n content: \"Verify the animation delay of the animated element in the popup at the beginning\",\n trigger: \"iframe .s_popup .modal\",\n run: function () {\n const animatedColumnEl = this.$anchor[0].querySelector(\".s_three_columns .row > :last-child\");\n // When the animated element is fully visible, its animation delay\n // should be rounded to -1 in the following condition.\n if (Math.round(parseFloat(animatedColumnEl.style.animationDelay)) !== -1) {\n console.error(\"The scroll animation in the modal did not start properly.\");\n }\n this.$anchor[0].closest(\".modal\").scrollTo({\n top: 0,\n left: 0,\n behavior: 'smooth'\n });\n },\n },\n {\n content: \"Wait until the column is no longer animated/visible.\",\n trigger: \"iframe .s_popup .s_three_columns .row > :last-child:not(.o_animating)\",\n run: function () {\n // If the column has been animated successfully, the animation delay\n // should be set to approximately zero when it is not visible.\n if (Math.round(parseFloat(this.$anchor[0].style.animationDelay)) !== 0) {\n console.error(\"The scroll animation in the modal did not end properly.\");\n }\n },\n },\n]);\n", "/** @odoo-module */\n\nimport wTourUtils from \"website.tour_utils\";\n\nconst snippets = [\n {\n id: \"s_popup\",\n name: \"Popup\",\n },\n {\n id: \"s_media_list\",\n name: \"Media List\",\n },\n];\n\nconst checkScrollbar = function (hasScrollbar) {\n return {\n content: `Check that the #wrapwrap ${hasScrollbar ? \"has\" : \"does not have\"} a vertical scrollbar.`,\n trigger: `iframe ${hasScrollbar ? \"body:not(.modal-open)\" : \"body.modal-open\"}`,\n run: function () {\n const wrapwrapEl = this.$anchor[0].querySelector(\"#wrapwrap\");\n const wrapwrapStyle = window.getComputedStyle(wrapwrapEl);\n if (!hasScrollbar && (wrapwrapStyle.overflow !== \"hidden\" || parseFloat(wrapwrapStyle.paddingRight) < 1)) {\n console.error(\"error The #wrapwrap vertical scrollbar should be hidden\");\n } else if (hasScrollbar && (wrapwrapStyle.overflow === \"hidden\" || parseFloat(wrapwrapStyle.paddingRight) > 0)) {\n console.error(\"error The #wrapwrap vertical scrollbar should be displayed\");\n }\n },\n };\n};\n\nconst toggleBackdrop = function () {\n return wTourUtils.changeOption('SnippetPopup', 'we-button[data-name=\"popup_backdrop_opt\"] we-checkbox', 'backdrop');\n};\n\nwTourUtils.registerWebsitePreviewTour(\"snippet_popup_and_scrollbar\", {\n test: true,\n url: \"/\",\n edition: true,\n}, [\n wTourUtils.dragNDrop(snippets[1]), // Media List\n wTourUtils.dragNDrop(snippets[0]), // Popup\n checkScrollbar(false),\n wTourUtils.clickOnSnippet(snippets[0]), // Popup\n toggleBackdrop(), // hide Popup backdrop\n checkScrollbar(true),\n wTourUtils.goBackToBlocks(),\n {\n content: \"Drag the Media List block and drop it in the popup.\",\n trigger: \"#oe_snippets .oe_snippet:has(> [data-snippet='s_media_list']) .oe_snippet_thumbnail\",\n run: \"drag_and_drop iframe #wrap .s_popup .modal-content.oe_structure\",\n },\n checkScrollbar(false),\n {\n content: \"Select the Media List snippet in the Popup.\",\n trigger: \"iframe #wrap .s_popup .modal-content .s_media_list\",\n },\n {\n content: \"Remove the Media List snippet in the Popup.\",\n trigger: \"iframe .oe_overlay.oe_active .oe_snippet_remove\",\n },\n checkScrollbar(true),\n toggleBackdrop(), // show Popup backdrop\n checkScrollbar(false),\n {\n content: \"Close the Popup.\",\n trigger: \".o_we_invisible_el_panel .o_we_invisible_entry\",\n },\n checkScrollbar(true),\n {\n content: \"Open the Cookies Bar.\",\n trigger: \".o_we_invisible_el_panel .o_we_invisible_entry:last-child\",\n },\n checkScrollbar(true),\n toggleBackdrop(), // show Cookies Bar backdrop\n checkScrollbar(false),\n toggleBackdrop(), // hide Cookies Bar backdrop\n checkScrollbar(true),\n {\n content: \"Open the Popup.\",\n trigger: \".o_we_invisible_el_panel .o_we_invisible_entry\",\n },\n checkScrollbar(false),\n wTourUtils.goBackToBlocks(),\n {\n content: \"Drag the Media List block and drop it in the popup.\",\n trigger: \"#oe_snippets .oe_snippet:has(> [data-snippet='s_media_list']) .oe_snippet_thumbnail\",\n run: \"drag_and_drop iframe #wrap .s_popup .modal-content.oe_structure\",\n },\n checkScrollbar(false),\n wTourUtils.clickOnSnippet(snippets[0]), // Popup\n {\n content: \"Remove the s_popup snippet\",\n trigger: \".o_we_customize_panel we-customizeblock-options:contains('Popup') we-button.oe_snippet_remove:first\",\n in_modal: false,\n run: \"click\",\n },\n checkScrollbar(true),\n wTourUtils.goBackToBlocks(),\n {\n content: \"Drag a Media List snippet and drop it in the Cookies Bar.\",\n trigger: \"#oe_snippets .oe_snippet:has(> [data-snippet='s_media_list']) .oe_snippet_thumbnail\",\n run: \"drag_and_drop iframe #website_cookies_bar .modal-content.oe_structure\",\n },\n {\n content: \"Select the Media List snippet in the Cookies Bar.\",\n trigger: \"iframe #website_cookies_bar .modal-content .s_media_list\",\n },\n {\n content: \"Duplicate the Media List snippet\",\n trigger: \".o_we_customize_panel we-customizeblock-options:contains('Media List') we-button.oe_snippet_clone:first\",\n in_modal: false,\n run: \"click\",\n },\n checkScrollbar(false),\n {\n content: \"Remove the first Media List snippet in the Cookies Bar.\",\n trigger: \"iframe .oe_overlay.oe_active .oe_snippet_remove\",\n },\n {\n content: \"Remove the second Media List snippet in the Cookies Bar.\",\n trigger: \"iframe .oe_overlay.oe_active .oe_snippet_remove\",\n },\n checkScrollbar(true),\n]);\n", "/** @odoo-module */\n\nimport wTourUtils from \"website.tour_utils\";\n\nwTourUtils.registerWebsitePreviewTour(\"snippet_popup_open_on_top\", {\n test: true,\n url: \"/\",\n edition: true,\n}, [\n wTourUtils.dragNDrop({id: \"s_popup\", name: \"Popup\"}),\n {\n content: \"Change content for long text.\",\n trigger: \"iframe .s_popup p.lead\",\n run: 'text ' + ' hello world'.repeat(300),\n },\n {\n content: \"Set delay to 0 second\",\n trigger: '[data-attribute-name=\"showAfter\"] input',\n run: 'text 0',\n },\n ...wTourUtils.clickOnSave(),\n {\n content: 'Check that the modal is scrolled on top',\n trigger: \"iframe .s_popup .modal:contains('hello world')\",\n run: function () {\n const modalEl = this.$anchor[0];\n if (modalEl.scrollHeight <= modalEl.clientHeight) {\n console.error('There is no scrollbar on the modal');\n }\n const activeEl = modalEl.ownerDocument.activeElement;\n if (activeEl.parentElement.closest('.modal') !== modalEl) {\n // Note: it might not be the best idea to still focus a button\n // that is not in the viewport, but since this is a niche case\n // this is the behavior right now. The important parts are:\n // in the normal case, focus the button; in all cases, the modal\n // should not be scrolled when opened.\n console.error('The focus should be on an element inside the modal');\n }\n if (modalEl.scrollTop !== 0) {\n console.error('The modal scrollbar is not scrolled at the top');\n }\n },\n },\n]);\n", "/** @odoo-module */\n\nimport wTourUtils from 'website.tour_utils';\n\n// TODO: Remove following steps once fix of task-3212519 is done.\n// Those steps are preventing a race condition to happen in the meantime: when\n// the tour was clicking on the toggle to hide facebook in the next step, it\n// would actually \"ignore\" the result of the click on the toggle and would just\n// consider the action of focusing out the input.\nconst socialRaceConditionClass = 'social_media_race_condition';\nconst preventRaceConditionStep = [{\n content: \"Wait a few ms to avoid race condition\",\n // Ensure the class is remove from previous call of those steps\n extra_trigger: `body:not(.${socialRaceConditionClass})`,\n trigger: 'iframe .s_social_media',\n run() {\n setTimeout(() => {\n document.body.classList.add(socialRaceConditionClass);\n }, 500);\n }\n}, {\n content: \"Check the race condition class is added after a few ms\",\n trigger: `body.${socialRaceConditionClass}`,\n run() {\n document.body.classList.remove(socialRaceConditionClass);\n }\n}];\n\nconst replaceIconByImage = function (url) {\n return [{\n content: \"Replace the icon by an image\",\n trigger: `iframe .s_social_media a[href='${url}'] i.fa`,\n run: \"dblclick\",\n },\n {\n content: \"Go to the Images tab in the media dialog\",\n trigger: \".o_select_media_dialog .o_notebook_headers .nav-item a:contains('Images')\",\n },\n {\n content: \"Select the image\",\n trigger: \".o_select_media_dialog img[title='s_banner_default_image.jpg']\",\n },\n ...preventRaceConditionStep,\n ];\n};\n\nconst addNewSocialNetwork = function (optionIndex, linkIndex, url, replaceIcon = false) {\n const replaceIconByImageSteps = replaceIcon ? replaceIconByImage(\"https://www.example.com\") : [];\n return [{\n content: \"Click on Add New Social Network\",\n trigger: 'we-list we-button.o_we_list_add_optional',\n },\n {\n content: \"Ensure new option is found\",\n trigger: `we-list table input:eq(${optionIndex})[data-list-position=${optionIndex}][data-dom-position=${linkIndex}][data-undeletable=false]`,\n run: () => {}, // This is a check.\n },\n {\n content: \"Ensure new link is found\",\n trigger: `iframe .s_social_media:has(a:eq(${linkIndex})[href='https://www.example.com'])`,\n run: () => {}, // This is a check.\n },\n ...replaceIconByImageSteps,\n {\n content: \"Change added Option label\",\n trigger: `we-list table input:eq(${optionIndex})`,\n run: `text ${url}`,\n },\n {\n content: \"Ensure new link is changed\",\n trigger: `iframe .s_social_media:has(a:eq(${linkIndex})[href='${url}'])`,\n run: () => {}, // This is a check.\n },\n ...preventRaceConditionStep,\n ];\n};\n\nwTourUtils.registerWebsitePreviewTour('snippet_social_media', {\n test: true,\n url: '/',\n edition: true,\n}, [\n wTourUtils.dragNDrop({id: 's_social_media', name: 'Social Media'}),\n wTourUtils.clickOnSnippet({id: 's_social_media', name: 'Social Media'}),\n ...addNewSocialNetwork(6, 6, 'https://www.youtu.be/y7TlnAv6cto'),\n {\n content: 'Click on the toggle to hide Facebook',\n trigger: 'we-list table we-button.o_we_user_value_widget',\n run: 'click',\n },\n {\n content: \"Ensure twitter became first\",\n trigger: 'iframe .s_social_media:has(a:eq(0)[href=\"/website/social/twitter\"])',\n run: () => {}, // This is a check.\n },\n {\n content: 'Drag the facebook link at the end of the list',\n trigger: 'we-list table we-button.o_we_drag_handle',\n position: 'bottom',\n run: \"drag_and_drop we-list table tr:last-child\",\n },\n {\n content: 'Check drop completed',\n trigger: 'we-list table input:eq(6)[data-media=\"facebook\"]',\n run: () => {}, // This is a check.\n },\n ...preventRaceConditionStep,\n // Create a Link for which we don't have an icon to propose.\n ...addNewSocialNetwork(7, 6, 'https://whatever.it/1EdSw9X'),\n // Create a custom instagram link.\n ...addNewSocialNetwork(8, 7, 'https://instagr.am/odoo.official/'),\n {\n content: \"Check if the result is correct before removing\",\n trigger: \"iframe .s_social_media\" +\n \":has(a:eq(0)[href='/website/social/twitter'])\" +\n \":has(a:eq(1)[href='/website/social/linkedin'])\" +\n \":has(a:eq(2)[href='/website/social/youtube'])\" +\n \":has(a:eq(3)[href='/website/social/instagram'])\" +\n \":has(a:eq(4)[href='/website/social/github'])\" +\n \":has(a:eq(5)[href='https://www.youtu.be/y7TlnAv6cto']:has(i.fa-youtube))\" +\n \":has(a:eq(6)[href='https://whatever.it/1EdSw9X']:has(i.fa-pencil))\" +\n \":has(a:eq(7)[href='https://instagr.am/odoo.official/']:has(i.fa-instagram))\",\n run: () => {}, // This is a check.\n },\n // Create a custom link, not officially supported, ensure icon is found.\n {\n content: 'Change custom social to unsupported link',\n trigger: 'we-list table input:eq(5)',\n run: 'text https://www.paypal.com/abc',\n },\n {\n content: \"Ensure paypal icon is found\",\n trigger: \"iframe .s_social_media\" +\n \":has(a:eq(5)[href='https://www.paypal.com/abc']:has(i.fa-paypal))\",\n run: () => {}, // This is a check.\n },\n ...preventRaceConditionStep,\n {\n content: 'Delete the custom link',\n trigger: 'we-list we-button.o_we_select_remove_option',\n run: 'click',\n },\n {\n content: \"Ensure custom link was removed\",\n trigger: 'iframe .s_social_media:has(a:eq(5)[href=\"https://whatever.it/1EdSw9X\"]:has(i.fa-pencil))',\n run: () => {}, // This is a check.\n },\n {\n content: 'Click on the toggle to show Facebook',\n trigger: 'we-list table we-button.o_we_user_value_widget:not(.active)',\n run: 'click',\n },\n {\n content: \"Check if the result is correct after removing\",\n trigger: \"iframe .s_social_media\" +\n \":has(a:eq(0)[href='/website/social/twitter'])\" +\n \":has(a:eq(1)[href='/website/social/linkedin'])\" +\n \":has(a:eq(2)[href='/website/social/youtube'])\" +\n \":has(a:eq(3)[href='/website/social/instagram'])\" +\n \":has(a:eq(4)[href='/website/social/github'])\" +\n \":has(a:eq(5)[href='/website/social/facebook'])\" +\n \":has(a:eq(6)[href='https://whatever.it/1EdSw9X']:has(i.fa-pencil))\" +\n \":has(a:eq(7)[href='https://instagr.am/odoo.official/']:has(i.fa-instagram))\",\n run: () => {}, // This is a check.\n },\n {\n content: 'Change url of the DB instagram link',\n trigger: 'we-list table input:eq(3)',\n run: 'text https://instagram.com/odoo.official/',\n },\n {\n content: 'Save',\n trigger: 'button[data-action=save]',\n run: function () {\n // We need this 'setTimeout' to ensure that the 'change' event of\n // the input has enough time to be executed. Without it, the click\n // on the save button takes priority, and the 'change' event is not\n // executed (see the '_onListItemChange' function of the 'we-list'\n // widget).\"\n setTimeout(() => {\n this.$anchor[0].click();\n }, 500);\n },\n },\n {\n content: \"Wait until save's calls are finished\",\n trigger: \"iframe body:not(.editor_enable)\",\n run: function () {}, // it's a check\n },\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n wTourUtils.clickOnSnippet({\n id: 's_social_media',\n name: 'Social Media',\n }),\n {\n content: \"Check if we can still change custom icons\",\n trigger: 'iframe .s_social_media a[href=\"https://whatever.it/1EdSw9X\"] i.fa-pencil',\n run: 'dblclick',\n },\n {\n content: \"Select a new icon\",\n trigger: '.o_select_media_dialog .fa-heart',\n },\n {\n content: \"Check if the result is correct after setting the icon\",\n trigger: \"iframe .s_social_media\" +\n \":has(a:eq(0)[href='/website/social/twitter'])\" +\n \":has(a:eq(1)[href='/website/social/linkedin'])\" +\n \":has(a:eq(2)[href='/website/social/youtube'])\" +\n \":has(a:eq(3)[href='/website/social/instagram'])\" +\n \":has(a:eq(4)[href='/website/social/github'])\" +\n \":has(a:eq(5)[href='/website/social/facebook'])\" +\n \":has(a:eq(6)[href='https://whatever.it/1EdSw9X']:has(i.fa-heart))\" +\n \":has(a:eq(7)[href='https://instagr.am/odoo.official/']:has(i.fa-instagram))\",\n run: () => {}, // This is a check.\n },\n // Create a social network but replace its icon by an image before setting\n // the link (`replaceIcon` parameter set to `true`).\n ...addNewSocialNetwork(8, 8, \"https://google.com\", true),\n // Create a social network after replacing the first icon by an image.\n ...replaceIconByImage(\"/website/social/twitter\"),\n ...addNewSocialNetwork(9, 9, \"https://facebook.com\"),\n {\n content: \"Check if the result is correct after adding images\",\n trigger: \"iframe .s_social_media\" +\n \":has(a:eq(0)[href='/website/social/twitter']:has(img))\" +\n \":has(a:eq(8)[href='https://google.com']:has(img))\" +\n \":has(a:eq(9)[href='https://facebook.com']:has(img))\",\n run: () => {}, // This is a check.\n },\n ...wTourUtils.clickOnSave(),\n]);\n", "/** @odoo-module */\n\nimport wTourUtils from 'website.tour_utils';\n\nconst scrollToHeading = function (position) {\n return {\n content: `Scroll to h1 number ${position}`,\n trigger: `iframe h1:eq(${position})`,\n run: function () {\n this.$anchor[0].scrollIntoView(true);\n },\n };\n};\nconst checkTOCNavBar = function (tocPosition, activeHeaderPosition) {\n return {\n content: `Check that the header ${activeHeaderPosition} is active for TOC ${tocPosition}`,\n trigger: `iframe .s_table_of_content:eq(${tocPosition}) .table_of_content_link:eq(${activeHeaderPosition}).active `,\n run: () => {}, // This is a check.\n };\n};\n\nwTourUtils.registerWebsitePreviewTour('snippet_table_of_content', {\n test: true,\n url: '/',\n edition: true,\n}, [\n wTourUtils.dragNDrop({id: 's_table_of_content', name: 'Table of Content'}),\n wTourUtils.dragNDrop({id: 's_table_of_content', name: 'Table of Content'}),\n // To make sure that the public widgets of the two previous ones started.\n wTourUtils.dragNDrop({id: 's_banner', name: 'Banner'}),\n ...wTourUtils.clickOnSave(),\n checkTOCNavBar(0, 0),\n checkTOCNavBar(1, 0),\n scrollToHeading(1),\n checkTOCNavBar(0, 1),\n checkTOCNavBar(1, 0),\n scrollToHeading(2),\n checkTOCNavBar(1, 0),\n scrollToHeading(3),\n checkTOCNavBar(1, 1),\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n content: \"Click on the first TOC's title\",\n trigger: 'iframe .s_table_of_content:eq(0) h1',\n },\n {\n content: \"Hide the first TOC on mobile\",\n trigger: '[data-toggle-device-visibility=\"no_mobile\"]',\n },\n {\n content: \"Click on the second TOC's title\",\n trigger: 'iframe .s_table_of_content:eq(1) h1',\n },\n {\n content: \"Hide the second TOC on desktop\",\n trigger: '[data-toggle-device-visibility=\"no_desktop\"]',\n },\n ...wTourUtils.clickOnSave(),\n {\n content: \"Check that we have the good TOC on desktop\",\n trigger: 'iframe .s_table_of_content.o_snippet_mobile_invisible',\n run: () => {\n if ($(document.querySelector('iframe .s_table_of_content.o_snippet_desktop_invisible'))\n .is(':visible')) {\n console.error('The mobile TOC should not be visible on desktop');\n }\n },\n },\n {\n content: \"Toggle the mobile view\",\n trigger: '.o_mobile_preview',\n },\n {\n content: \"Check that we have the good TOC on mobile\",\n trigger: 'iframe .s_table_of_content.o_snippet_desktop_invisible',\n run: () => {\n if ($(document.querySelector('iframe .s_table_of_content.o_snippet_mobile_invisible'))\n .is(':visible')) {\n console.error('The desktop TOC should not be visible on mobile');\n }\n },\n },\n]);\n", "/** @odoo-module **/\n\nimport wTourUtils from 'website.tour_utils';\n\nwTourUtils.registerWebsitePreviewTour('snippet_translation', {\n url: '/',\n edition: true,\n test: true,\n}, [\n wTourUtils.dragNDrop({name: 'Cover'}),\n {\n content: \"Check that contact us contain Parseltongue\",\n trigger: 'iframe .s_cover .btn-primary:contains(\"Contact us in Parseltongue\")',\n run: () => null, // it's a check\n },\n {\n content: \"Check that the save button contains 'in fu_GB'\",\n trigger: '.btn[data-action=\"save\"]:contains(\"Save in fu_GB\")',\n run: () => null, // it's a check\n },\n]);\nwTourUtils.registerWebsitePreviewTour('snippet_translation_changing_lang', {\n url: '/',\n test: true,\n}, [\n {\n content: \"Change language to Parseltongue\",\n trigger: 'iframe .js_language_selector .btn',\n },\n {\n content: \"Change the language to English\",\n trigger: 'iframe .js_language_selector .js_change_lang[data-url_code=\"en\"]',\n },\n {\n content: \"Enable translation\",\n trigger: '.o_translate_website_container a',\n },\n {\n content: \"Close the dialog\",\n trigger: '.modal-footer .btn-primary',\n },\n ...wTourUtils.clickOnSave(),\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n wTourUtils.dragNDrop({name: 'Cover'}),\n {\n content: \"Check that contact us contain Parseltongue\",\n trigger: 'iframe .s_cover .btn-primary:contains(\"Contact us in Parseltongue\")',\n run: () => null, // it's a check\n },\n]);\n", "odoo.define(\"website.tour.snippet_version\", function (require) {\n\"use strict\";\n\nconst wTourUtils = require('website.tour_utils');\n\nwTourUtils.registerWebsitePreviewTour(\"snippet_version\", {\n edition: true,\n url: \"/\",\n test: true,\n}, [{\n content: \"Drop s_test_snip snippet\",\n trigger: '#oe_snippets .oe_snippet:has(.s_test_snip) .oe_snippet_thumbnail',\n run: \"drag_and_drop iframe #wrap\",\n}, {\n content: \"Drop s_text_image snippet\",\n trigger: '#oe_snippets .oe_snippet:has(.s_text_image) .oe_snippet_thumbnail:not(.o_we_already_dragging)',\n run: \"drag_and_drop iframe #wrap\",\n}, {\n content: \"Test t-snippet and t-snippet-call: snippets have data-snippet set\",\n trigger: '#oe_snippets .o_panel_body > .oe_snippet.ui-draggable',\n run: function () {\n // Tests done here as all these are not visible on the page\n const draggableSnippets = document.querySelectorAll('#oe_snippets .o_panel_body > .oe_snippet.ui-draggable > :nth-child(2)');\n if (![...draggableSnippets].every(el => el.dataset.snippet)) {\n console.error(\"error Some t-snippet are missing their template name\");\n }\n if (!document.querySelector('#oe_snippets [data-snippet=\"s_test_snip\"] [data-snippet=\"s_share\"]')) {\n console.error(\"error s_share t-called inside s_test_snip is missing template name\");\n }\n if (!document.querySelector('iframe:not(.o_ignore_in_tour)').contentDocument.querySelector('#wrap [data-snippet=\"s_test_snip\"] [data-snippet=\"s_share\"]')) {\n console.error(\"error Dropped a s_test_snip snippet but missing s_share template name in it\");\n }\n },\n},\n ...wTourUtils.clickOnSave(),\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n{\n content: \"Modify the version of snippets\",\n trigger: '#oe_snippets .o_panel_body > .oe_snippet',\n run: function () {\n document.querySelector('#oe_snippets .oe_snippet > [data-snippet=\"s_test_snip\"]').dataset.vjs = '999';\n document.querySelector('#oe_snippets .oe_snippet > [data-snippet=\"s_share\"]').dataset.vcss = '999';\n document.querySelector('#oe_snippets .oe_snippet > [data-snippet=\"s_text_image\"]').dataset.vxml = '999';\n },\n}, {\n content: \"Edit s_test_snip\",\n trigger: 'iframe #wrap.o_editable .s_test_snip',\n}, {\n content: \"Edit text_image\",\n extra_trigger: 'we-customizeblock-options:contains(Test snip) .snippet-option-VersionControl > we-alert',\n trigger: 'iframe #wrap.o_editable .s_text_image',\n}, {\n content: \"Edit s_share\",\n extra_trigger: 'we-customizeblock-options:contains(Text - Image) .snippet-option-VersionControl > we-alert',\n trigger: 'iframe #wrap.o_editable .s_share',\n}, {\n content: \"s_share is outdated\",\n extra_trigger: 'we-customizeblock-options:contains(Share) .snippet-option-VersionControl > we-alert',\n trigger: 'iframe body',\n}]);\n});\n", "odoo.define(\"website.tour.snippets_all_drag_and_drop\", async function (require) {\n\"use strict\";\n\nconst { WysiwygAdapterComponent } = require('@website/components/wysiwyg_adapter/wysiwyg_adapter');\nconst websiteTourUtils = require('website.tour_utils');\nconst { patch, unpatch } = require('web.utils');\n\nconst patchWysiwygAdapter = () => patch(WysiwygAdapterComponent.prototype, 'snippets_all_drag_and_drop.wysiwyg_adapter', {\n _trigger_up(ev) {\n this._super(...arguments);\n if (ev.name === 'snippet_removed') {\n $('body').attr('test-dd-snippet-removed', true);\n }\n }\n});\n\nconst unpatchWysiwygAdapter = () => unpatch(WysiwygAdapterComponent.prototype, 'snippets_all_drag_and_drop.wysiwyg_adapter');\n\nconst tour = require(\"web_tour.tour\");\n\nlet snippetsNames = (new URL(document.location.href)).searchParams.get('snippets_names') || '';\n// When this test is loaded in the backend, the search params aren't as easy to\n// read as before. Little trickery to make this test run.\nconst searchParams = new URLSearchParams(window.location.href.split('#')[1]).get('path');\nif (searchParams) {\n snippetsNames = new URLSearchParams(searchParams.split('/')[1]).get('snippets_names') || '';\n snippetsNames = snippetsNames.split(',');\n}\nlet steps = [];\nlet n = 0;\nfor (const snippet of snippetsNames) {\n n++;\n const snippetSteps = [{\n content: `Drop ${snippet} snippet [${n}/${snippetsNames.length}]`,\n trigger: `#oe_snippets .oe_snippet:has( > [data-snippet='${snippet}']) .oe_snippet_thumbnail`,\n run: \"drag_and_drop iframe #wrap\",\n }, {\n content: `Edit ${snippet} snippet`,\n trigger: `iframe #wrap.o_editable [data-snippet='${snippet}']`,\n }, {\n content: `check ${snippet} setting are loaded, wait panel is visible`,\n trigger: \".o_we_customize_panel\",\n run: function () {}, // it's a check\n }, {\n content: `Remove the ${snippet} snippet`, // Avoid bad perf if many snippets\n trigger: \"we-button.oe_snippet_remove:last\"\n }, {\n content: `click on 'BLOCKS' tab (${snippet})`,\n extra_trigger: 'body[test-dd-snippet-removed]',\n trigger: \".o_we_add_snippet_btn\",\n run: function (actions) {\n $('body').removeAttr('test-dd-snippet-removed');\n actions.auto();\n },\n }];\n\n if (snippet === 's_google_map') {\n snippetSteps.splice(1, 3, {\n content: 'Close API Key popup',\n trigger: \"iframe .modal-footer .btn-secondary\",\n });\n } else if (['s_popup', 's_newsletter_subscribe_popup'].includes(snippet)) {\n snippetSteps[2]['in_modal'] = false;\n snippetSteps.splice(3, 2, {\n content: `Hide the ${snippet} popup`,\n trigger: \"iframe .s_popup_close\",\n }, {\n content: `Make sure ${snippet} is hidden`,\n trigger: \"iframe body:not(.modal-open)\",\n });\n }\n steps = steps.concat(snippetSteps);\n}\n\ntour.register(\"snippets_all_drag_and_drop\", {\n test: true,\n // To run the tour locally, you need to insert the URL sent by the python\n // tour here. There is currently an issue with tours which don't have an URL\n // url: '/?enable_editor=1&snippets_names=s_showcase,s_numbers,s_...',\n}, [\n ...websiteTourUtils.clickOnEditAndWaitEditMode(),\n {\n content: \"Ensure snippets are actually passed at the test.\",\n trigger: \"body\",\n run: function () {\n // safety check, otherwise the test might \"break\" one day and\n // receive no steps. The test would then not test anything anymore\n // without us noticing it.\n if (steps.length < 220) {\n console.error(`This test is not behaving as it should, got only ${steps.length} steps.`);\n }\n patchWysiwygAdapter();\n },\n },\n // This first step is needed as it will be used later for inner snippets\n // Without this, it will dropped inside the footer and will need an extra\n // selector.\n {\n content: \"Drop s_text_image snippet\",\n trigger: \"#oe_snippets .oe_snippet:has( > [data-snippet='s_text_image']) .oe_snippet_thumbnail\",\n run: \"drag_and_drop iframe #wrap\"\n },\n {\n content: \"Edit s_text_image snippet\",\n trigger: \"iframe #wrap.o_editable [data-snippet='s_text_image']\"\n },\n {\n content: \"check setting are loaded, wait panel is visible\",\n trigger: \".o_we_customize_panel\"\n },\n // We hide the header before starting to drop snippets. This prevents\n // situations where the header's drop zones overlap with those of the #wrap,\n // ensuring that a snippet is dropped in the #wrap as expected instead of\n // the header.\n websiteTourUtils.clickOnSnippet({id: \"o_header_standard\", name: \"Header\"}),\n websiteTourUtils.changeOption(\"TopMenuVisibility\", \"we-select:has([data-visibility]) we-toggler\"),\n websiteTourUtils.changeOption(\"TopMenuVisibility\", 'we-button[data-visibility=\"hidden\"]'),\n {\n content: \"click on 'BLOCKS' tab\",\n trigger: \".o_we_add_snippet_btn\"\n },\n].concat(steps).concat([\n {\n content: \"Remove wysiwyg patch\",\n trigger: \"body\",\n run: () => unpatchWysiwygAdapter(),\n }\n]),\n);\n});\n", "odoo.define('website.tour.specific_website_editor', function (require) {\n'use strict';\n\nconst tour = require('web_tour.tour');\nconst wTourUtils = require(\"website.tour_utils\");\n\nwTourUtils.registerWebsitePreviewTour(\"generic_website_editor\", {\n test: true,\n edition: true,\n}, [{\n trigger: 'iframe body:not([data-hello=\"world\"])',\n content: 'Check that the editor DOM matches its website-generic features',\n run: function () {}, // Simple check\n}]);\n\n// Good practice would have been to use `wTourUtils.registerWebsitePreviewTour`\n// for this tour with `edition: true` and remove the first step to enter edit\n// mode. Unfortunately this breaks the page and therefore the test fails for\n// unknown reason.\ntour.register('specific_website_editor', {\n test: true,\n}, [\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n trigger: 'iframe body[data-hello=\"world\"]',\n content: 'Check that the editor DOM matches its website-specific features',\n run: function () {}, // Simple check\n}]);\n});\n", "/** @odoo-module **/\n\nimport wTourUtils from 'website.tour_utils';\n\nconst countdownSnippet = {\n name: 'Countdown',\n id: 's_countdown',\n};\nconst dragNDropOutOfFooter = wTourUtils.dragNDrop(countdownSnippet);\ndragNDropOutOfFooter.run = 'drag_and_drop iframe #wrapwrap #wrap';\n\nwTourUtils.registerWebsitePreviewTour('website_start_cloned_snippet', {\n edition: true,\n test: true,\n url: '/',\n}, [\n dragNDropOutOfFooter,\n wTourUtils.clickOnSnippet(countdownSnippet),\n {\n content: 'Click on clone snippet',\n trigger: '.oe_snippet_clone',\n },\n {\n content: 'Check that the cloned snippet has a canvas and that something has been drawn inside of it',\n trigger: 'iframe .s_countdown:eq(1) canvas',\n run: function () {\n // Check that at least one bit has been drawn in the canvas\n if (!this.$anchor[0].getContext('2d').getImageData(0, 0, 1000, 1000).data.includes(1)) {\n console.error('The cloned snippet should have been started');\n }\n },\n },\n]);\n", "/** @odoo-module */\n\nimport wTourUtils from \"website.tour_utils\";\n\nwTourUtils.registerWebsitePreviewTour(\"text_animations\", {\n test: true,\n url: \"/\",\n edition: true,\n}, [\n wTourUtils.dragNDrop({\n id: \"s_cover\",\n name: \"Cover\",\n }),\n {\n content: \"Click on the snippet title\",\n trigger: \"iframe .s_cover h1 > font\",\n run: \"dblclick\", // Make sure the title is selected.\n },\n {\n content: \"Click on the 'Animate Text' button to activate the option\",\n trigger: \"div.o_we_animate_text\",\n },\n {\n content: \"Check that the animation was applied\",\n trigger: \"iframe .s_cover h1 span.o_animated_text\",\n run: () => null, // it's a check\n },\n {\n content: \"Click on the 'Animate Text' button\",\n trigger: \"div.o_we_animate_text\",\n },\n {\n content: \"Check that the animation was disabled for the title\",\n trigger: \"iframe .s_cover:not(:has(.o_animated_text))\",\n run: () => null, // it's a check\n },\n {\n content: \"Try to apply the text animation again\",\n trigger: \"div.o_we_animate_text\",\n },\n {\n content: \"Check that the animation was applied\",\n trigger: \"iframe .s_cover:has(span.o_animated_text)\",\n run: () => null, // it's a check\n },\n]);\n", "/** @odoo-module **/\n\nimport wTourUtils from 'website.tour_utils';\n\nwTourUtils.registerWebsitePreviewTour('translate_menu_name', {\n url: '/pa_GB',\n test: true,\n edition: false,\n}, [\n {\n content: \"activate translate mode\",\n trigger: '.o_translate_website_container a',\n },\n {\n content: \"Close the dialog\",\n trigger: '.modal-footer .btn-primary',\n },\n wTourUtils.clickOnExtraMenuItem({}, true),\n {\n content: \"translate the menu entry\",\n trigger: 'iframe a[href=\"/englishURL\"] span',\n run: 'text value pa-GB',\n },\n ...wTourUtils.clickOnSave(),\n]);\n", "odoo.define(\"website.tour.unsplash_beacon\", function (require) {\n\"use strict\";\n\nconst tour = require(\"web_tour.tour\");\n\ntour.register(\"test_unsplash_beacon\", {\n test: true,\n url: \"/\",\n}, [{\n content: \"Verify whether beacon was sent.\",\n trigger: 'img[data-beacon=\"sent\"]',\n run: () => {}, // This is a check.\n}]);\n});\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\n\ntour.register('website_backend_menus_redirect', {\n test: true,\n url: '/',\n},\n[{\n content: 'Need at least a step so the tour is not failing in enterprise',\n trigger: 'body',\n edition: 'enterprise',\n}, {\n content: 'Make frontend to backend menus appears',\n trigger: 'body:has(#wrap)',\n run: function () {\n // The dropdown is hidden behind an SVG on hover animation.\n this.$anchor.find('.o_frontend_to_backend_apps_menu').addClass('show');\n },\n edition: 'community',\n}, {\n content: 'Click on Test Root backend menu',\n trigger: '.o_frontend_to_backend_apps_menu a:contains(\"Test Root\")',\n edition: 'community',\n}, {\n content: 'Check that we landed on the apps page (Apps), and not the Home Action page (Settings)',\n trigger: '.oe_module_vignette',\n edition: 'community',\n},\n]);\n", "/** @odoo-module **/\n\nimport wTourUtils from 'website.tour_utils';\n\nconst cover = {\n id: 's_cover',\n name: 'Cover',\n};\n\nwTourUtils.registerWebsitePreviewTour('website_click_tour', {\n test: true,\n url: '/',\n}, [\n {\n content: \"trigger a page navigation\",\n trigger: 'iframe a[href=\"/contactus\"]',\n },\n {\n content: \"wait for the page to be loaded\",\n trigger: '.o_website_preview[data-view-xmlid=\"website.contactus\"]',\n run: () => null, // it's a check\n },\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n content: \"click on a link that would trigger navigation\",\n trigger: 'iframe a[href=\"/\"]',\n },\n wTourUtils.goBackToBlocks(),\n wTourUtils.dragNDrop(cover),\n wTourUtils.clickOnSnippet(cover),\n ...wTourUtils.clickOnSave(),\n {\n content: \"wait for the iframe to be ready\",\n trigger: 'iframe body:not(.editor_enable)',\n run: () => null, // it's a check\n }\n]);\n", "odoo.define('website.tour.form_editor', function (require) {\n 'use strict';\n\n const wTourUtils = require(\"website.tour_utils\");\n\n // Visibility possible values:\n const VISIBLE = 'Always Visible';\n const HIDDEN = 'Hidden';\n const CONDITIONALVISIBILITY = 'Visible only if';\n\n const NB_NON_ESSENTIAL_REQUIRED_FIELDS_IN_DEFAULT_FORM = 2;\n const ESSENTIAL_FIELDS_VALID_DATA_FOR_DEFAULT_FORM = [\n {\n name: 'email_from',\n value: 'admin@odoo.com',\n },\n {\n name: 'subject',\n value: 'Hello, world!',\n }\n ];\n const essentialFieldsForDefaultFormFillInSteps = [];\n for (const data of ESSENTIAL_FIELDS_VALID_DATA_FOR_DEFAULT_FORM) {\n essentialFieldsForDefaultFormFillInSteps.push({\n content: \"Enter data in model-required field\",\n trigger: `iframe .s_website_form_model_required .s_website_form_input[name=\"${data.name}\"]`,\n run: `text ${data.value}`,\n });\n }\n\n // TODO: in master only keep the conversion of the double quotes character.\n // Replace all `\"` character by `"`, all `'` character by `'` and\n // all \"`\" character by `‘`.\n const getQuotesEncodedName = function (name) {\n return name.replaceAll(/\"/g, character => `"`)\n .replaceAll(/'/g, character => `'`)\n .replaceAll(/`/g, character => `‘`)\n .replaceAll(\"\\\\\", character => `\`);\n };\n\n const triggerFieldByLabel = (label) => {\n return `.s_website_form_field.s_website_form_custom:has(label:contains(\"${label}\"))`;\n };\n const selectFieldByLabel = (label) => {\n return [{\n content: `Select field \"${label}\"`,\n trigger: \"iframe \" + triggerFieldByLabel(label),\n }];\n };\n const selectButtonByText = function (text) {\n return [{\n content: \"Open the select\",\n trigger: `we-select:has(we-button:contains(\"${text}\")) we-toggler`,\n },\n {\n content: \"Click on the option\",\n trigger: `we-select we-button:contains(\"${text}\")`,\n }];\n };\n const selectButtonByData = function (data) {\n return [{\n content: \"Open the select\",\n trigger: `we-select:has(we-button[${data}]) we-toggler`,\n }, {\n content: \"Click on the option\",\n trigger: `we-select we-button[${data}]`,\n }];\n };\n const addField = function (name, type, label, required, isCustom,\n display = {visibility: VISIBLE, condition: \"\"}) {\n const data = isCustom ? `data-custom-field=\"${name}\"` : `data-existing-field=\"${name}\"`;\n const ret = [{\n content: \"Select form\",\n extra_trigger: 'iframe .s_website_form_field',\n trigger: 'iframe section.s_website_form',\n }, {\n content: \"Add field\",\n trigger: 'we-button[data-add-field]',\n },\n ...selectButtonByData(data),\n {\n content: \"Wait for field to load\",\n trigger: `iframe .s_website_form_field[data-type=\"${name}\"], .s_website_form_input[name=\"${name}\"]`, //custom or existing field\n run: function () {},\n },\n ...selectButtonByText(display.visibility),\n ];\n let testText = 'iframe .s_website_form_field';\n if (display.condition) {\n ret.push({\n content: \"Set the visibility condition\",\n trigger: 'we-input[data-attribute-name=\"visibilityCondition\"] input',\n run: `text ${display.condition}`,\n });\n }\n if (required) {\n testText += '.s_website_form_required';\n ret.push({\n content: \"Mark the field as required\",\n trigger: 'we-button[data-name=\"required_opt\"] we-checkbox',\n });\n }\n if (label) {\n testText += `:has(label:contains(${label}))`;\n ret.push({\n content: \"Change the label text\",\n trigger: 'we-input[data-set-label-text] input',\n run: `text ${label}`,\n });\n }\n if (type !== 'checkbox' && type !== 'radio' && type !== 'select') {\n let inputType = type === 'textarea' ? type : `input[type=\"${type}\"]`;\n const nameAttribute = isCustom && label ? getQuotesEncodedName(label) : name;\n testText += `:has(${inputType}[name=\"${nameAttribute}\"]${required ? \"[required]\" : \"\"})`;\n // Because 'testText' will be used as selector to verify the content\n // of the label, the `\\` character needs to be escaped.\n testText = testText.replaceAll(\"\\\\\", \"\\\\\\\\\");\n }\n ret.push({\n content: \"Check the resulting field\",\n trigger: testText,\n run: function () {},\n });\n return ret;\n };\n const addCustomField = function (name, type, label, required, display) {\n return addField(name, type, label, required, true, display);\n };\n const addExistingField = function (name, type, label, required, display) {\n return addField(name, type, label, required, false, display);\n };\n\n wTourUtils.registerWebsitePreviewTour(\"website_form_editor_tour\", {\n url: '/',\n edition: true,\n test: true,\n }, [\n // Drop a form builder snippet and configure it\n {\n content: \"Drop the form snippet\",\n trigger: '#oe_snippets .oe_snippet:has(.s_website_form) .oe_snippet_thumbnail',\n run: 'drag_and_drop iframe #wrap',\n }, {\n content: \"Check if the form snippet is dropped\",\n trigger: \"iframe .s_website_form_field\",\n run: () => null,\n },\n // Check if fields in two form snippet have unique IDs\n {\n content: \"Drop another form snippet\",\n trigger: '#oe_snippets .oe_snippet:has(.s_website_form) .oe_snippet_thumbnail',\n run: 'drag_and_drop iframe #wrap',\n }, {\n content: \"Check if there are two form snippets on the page\",\n trigger: \"iframe .s_website_form:nth-of-type(2) .s_website_form_field\",\n run: () => null,\n }, {\n content: \"Check that the first field of both the form snippets have different IDs\",\n trigger: \"iframe .s_website_form:nth-of-type(1) input[name='name']\",\n run: function() {\n const firstFieldForm1El = this.$anchor[0];\n const firstFieldForm2El = firstFieldForm1El.ownerDocument.querySelector(\n \".s_website_form:nth-of-type(2) input[name='name']\"\n );\n if (firstFieldForm1El.id === firstFieldForm2El.id) {\n console.error(\"The first fields of two different form snippet have the same ID\");\n }\n },\n }, {\n content: \"Click on the second form\",\n trigger: \"iframe .s_website_form:nth-of-type(2)\",\n }, {\n content: \"Remove the second snippet\",\n trigger: \"iframe .oe_overlay.oe_active .oe_snippet_remove\"\n }, {\n content: \"Select form by clicking on an input field\",\n extra_trigger: 'iframe .s_website_form_field',\n trigger: 'iframe section.s_website_form input',\n }, {\n content: \"Verify that the form editor appeared\",\n trigger: '.o_we_customize_panel .snippet-option-WebsiteFormEditor',\n run: () => null,\n }, {\n content: \"Go back to blocks to unselect form\",\n trigger: '.o_we_add_snippet_btn',\n }, {\n content: \"Select form by clicking on a text area\",\n extra_trigger: 'iframe .s_website_form_field',\n trigger: 'iframe section.s_website_form textarea',\n }, {\n content: \"Verify that the form editor appeared\",\n trigger: '.o_we_customize_panel .snippet-option-WebsiteFormEditor',\n run: () => null,\n }, {\n content: \"Rename the field label\",\n trigger: 'we-input[data-set-label-text] input',\n run: \"text Renamed\",\n }, {\n content: \"Leave the rename options\",\n trigger: 'we-input[data-set-label-text] input',\n run: \"text_blur\",\n }, {\n content: \"Go back to blocks to unselect form\",\n trigger: '.o_we_add_snippet_btn',\n }, {\n content: \"Select form itself (not a specific field)\",\n extra_trigger: 'iframe .s_website_form_field',\n trigger: 'iframe section.s_website_form',\n },\n ...selectButtonByText('Send an E-mail'),\n {\n content: \"Form has a model name\",\n trigger: 'iframe section.s_website_form form[data-model_name=\"mail.mail\"]',\n }, {\n content: 'Edit the Phone Number field',\n trigger: 'iframe input[name=\"phone\"]',\n }, {\n content: 'Change the label position of the phone field',\n trigger: 'we-button[data-select-label-position=\"right\"]',\n },\n ...addCustomField(\"char\", \"text\", \"Conditional Visibility Check 1\", false),\n ...addCustomField(\"char\", \"text\", \"Conditional Visibility Check 2\", false),\n ...selectButtonByData(\"data-set-visibility='conditional'\"),\n ...selectButtonByData(\"data-set-visibility-dependency='Conditional Visibility Check 1'\"),\n ...addCustomField(\"char\", \"text\", \"Conditional Visibility Check 2\", false),\n ...selectFieldByLabel(\"Conditional Visibility Check 1\"),\n ...selectButtonByData(\"data-set-visibility='conditional'\"),\n {\n content: \"Check that 'Conditional Visibility Check 2' is not in the list of the visibility selector of Conditional Visibility Check 1\",\n trigger: \"we-select[data-name='hidden_condition_opt']:not(:has(we-button[data-set-visibility-dependency='Conditional Visibility Check 2']))\",\n run: () => null,\n },\n ...addCustomField(\"char\", \"text\", \"Conditional Visibility Check 3\", false),\n ...addCustomField(\"char\", \"text\", \"Conditional Visibility Check 4\", false),\n ...selectButtonByData(\"data-set-visibility='conditional'\"),\n ...selectButtonByData(\"data-set-visibility-dependency='Conditional Visibility Check 3'\"),\n {\n content: \"Change the label of 'Conditional Visibility Check 4' and change it to 'Conditional Visibility Check 3'\",\n trigger: 'we-input[data-set-label-text] input',\n run: \"text Conditional Visibility Check 3\",\n },\n {\n content: \"Check that the conditional visibility of the renamed field is removed\",\n trigger: \"we-customizeblock-option.snippet-option-WebsiteFieldEditor we-select:contains('Visibility'):has(we-toggler:contains('Always Visible'))\",\n run: () => null,\n },\n ...addCustomField(\"char\", \"text\", \"Conditional Visibility Check 5\", false),\n ...addCustomField(\"char\", \"text\", \"Conditional Visibility Check 6\", false),\n ...selectButtonByData(\"data-set-visibility='conditional'\"),\n {\n content: \"Change the label of 'Conditional Visibility Check 6' and change it to 'Conditional Visibility Check 5'\",\n trigger: 'we-input[data-set-label-text] input',\n run: \"text Conditional Visibility Check 5\",\n },\n {\n content: \"Check that 'Conditional Visibility Check 5' is not in the list of the renamed field\",\n trigger: \"we-customizeblock-option.snippet-option-WebsiteFieldEditor we-select[data-name='hidden_condition_opt']:not(:has(we-button:contains('Conditional Visibility Check 5')))\",\n run: () => null,\n },\n ...addExistingField('email_cc', 'text', 'Test conditional visibility', false, {visibility: CONDITIONALVISIBILITY, condition: 'odoo'}),\n // Check that visibility condition is deleted on dependency type change.\n ...addCustomField(\"char\", \"text\", \"dependent\", false, {visibility: CONDITIONALVISIBILITY}),\n ...addCustomField(\"selection\", \"radio\", \"dependency\", false),\n ...selectFieldByLabel(\"dependent\"),\n ...selectButtonByData('data-set-visibility-dependency=\"dependency\"'),\n ...selectFieldByLabel(\"dependency\"),\n ...selectButtonByData('data-custom-field=\"char\"'),\n ...selectFieldByLabel(\"dependent\"),\n {\n content: \"Open the select\",\n trigger: 'we-select:has(we-button[data-set-visibility=\"visible\"]) we-toggler',\n },\n {\n content: \"Check that the field no longer has conditional visibility\",\n trigger: \"we-select we-button[data-set-visibility='visible'].active\",\n run: () => null,\n },\n\n ...addExistingField('date', 'text', 'Test Date', true),\n\n ...addExistingField('record_name', 'text', 'Awesome Label', false, {visibility: HIDDEN}),\n\n ...addExistingField('body_html', 'textarea', 'Your Message', true),\n\n ...addExistingField('recipient_ids', 'checkbox'),\n\n ...addCustomField('one2many', 'checkbox', 'Products', true),\n {\n content: \"Change Option 1 label\",\n trigger: 'we-list table input:eq(0)',\n run: 'text Iphone',\n }, {\n content: \"Change Option 2 label\",\n trigger: 'we-list table input:eq(1)',\n run: 'text Galaxy S',\n }, {\n content: \"Change first Option 3 label\",\n trigger: 'we-list table input:eq(2)',\n run: 'text Xperia',\n }, {\n content: \"Click on Add new Checkbox\",\n trigger: 'we-list we-button.o_we_list_add_optional',\n }, {\n content: \"Change added Option label\",\n trigger: 'we-list table input:eq(3)',\n run: 'text Wiko Stairway',\n }, {\n content: \"Check the resulting field\",\n trigger: \"iframe .s_website_form_field.s_website_form_custom.s_website_form_required\" +\n \":has(.s_website_form_multiple[data-display='horizontal'])\" +\n \":has(.checkbox:has(label:contains('Iphone')):has(input[type='checkbox'][required]))\" +\n \":has(.checkbox:has(label:contains('Galaxy S')):has(input[type='checkbox'][required]))\" +\n \":has(.checkbox:has(label:contains('Xperia')):has(input[type='checkbox'][required]))\" +\n \":has(.checkbox:has(label:contains('Wiko Stairway')):has(input[type='checkbox'][required]))\",\n run: function () {},\n },\n ...selectButtonByData('data-multi-checkbox-display=\"vertical\"'),\n {\n content: \"Check the resulting field\",\n trigger: \"iframe .s_website_form_field.s_website_form_custom.s_website_form_required\" +\n \":has(.s_website_form_multiple[data-display='vertical'])\" +\n \":has(.checkbox:has(label:contains('Iphone')):has(input[type='checkbox'][required]))\" +\n \":has(.checkbox:has(label:contains('Galaxy S')):has(input[type='checkbox'][required]))\" +\n \":has(.checkbox:has(label:contains('Xperia')):has(input[type='checkbox'][required]))\" +\n \":has(.checkbox:has(label:contains('Wiko Stairway')):has(input[type='checkbox'][required]))\",\n run: function () {},\n },\n // Check conditional visibility for the relational fields\n ...selectButtonByData(\"data-set-visibility='conditional'\"),\n ...selectButtonByData(\"data-set-visibility-dependency='recipient_ids'\"),\n ...selectButtonByText(\"Is not equal to\"),\n ...selectButtonByText(\"Mitchell Admin\"),\n ...wTourUtils.clickOnSave(),\n {\n content: \"Check 'products' field is visible.\",\n trigger: `iframe .s_website_form:has(${triggerFieldByLabel(\"Products\")}:visible)`,\n run: () => {}, // it's a check\n }, {\n content: \"choose the option 'Mitchell Admin' of partner.\",\n trigger: \"iframe .checkbox:has(label:contains('Mitchell Admin')) input[type='checkbox']\",\n }, {\n content: \"Check 'products' field is not visible.\",\n trigger: \"iframe .s_website_form\" +`:has(${triggerFieldByLabel(\"Products\")}:not(:visible))`,\n run: () => {}, // it's a check\n },\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n\n ...addCustomField('selection', 'radio', 'Service', true),\n {\n content: \"Change Option 1 label\",\n trigger: 'we-list table input:eq(0)',\n run: 'text After-sales Service',\n }, {\n content: \"Change Option 2 label\",\n trigger: 'we-list table input:eq(1)',\n run: 'text Invoicing Service',\n }, {\n content: \"Change first Option 3 label\",\n trigger: 'we-list table input:eq(2)',\n run: 'text Development Service',\n }, {\n content: \"Click on Add new Checkbox\",\n trigger: 'we-list we-button.o_we_list_add_optional',\n }, {\n content: \"Change last Option label\",\n trigger: 'we-list table input:eq(3)',\n run: 'text Management Service',\n }, {\n content: \"Mark the field as not required\",\n extra_trigger: \"we-list table input:eq(3)[name='Management Service']\",\n trigger: 'we-button[data-name=\"required_opt\"] we-checkbox',\n }, {\n content: \"Check the resulting field\",\n trigger: \"iframe .s_website_form_field.s_website_form_custom:not(.s_website_form_required)\" +\n \":has(.radio:has(label:contains('After-sales Service')):has(input[type='radio']:not([required])))\" +\n \":has(.radio:has(label:contains('Invoicing Service')):has(input[type='radio']:not([required])))\" +\n \":has(.radio:has(label:contains('Development Service')):has(input[type='radio']:not([required])))\" +\n \":has(.radio:has(label:contains('Management Service')):has(input[type='radio']:not([required])))\",\n run: function () {},\n },\n\n ...addCustomField('many2one', 'select', 'State', true),\n\n // Customize custom selection field\n {\n content: \"Change Option 1 Label\",\n trigger: 'we-list table input:eq(0)',\n run: 'text Germany',\n }, {\n content: \"Check that the label has been changed on the snippet\",\n trigger: \"iframe .s_website_form_field.s_website_form_custom.s_website_form_required\" +\n \":has(.s_website_form_select_item:contains('Germany'))\",\n run: function () {},\n }, {\n content: \"Change Option 2 Label\",\n trigger: 'we-list table input:eq(1)',\n run: 'text Belgium',\n }, {\n content: \"Check that the label has been changed on the snippet\",\n trigger: \"iframe .s_website_form_field.s_website_form_custom.s_website_form_required\" +\n \":has(.s_website_form_select_item:contains('Belgium'))\",\n run: function () {},\n }, {\n content: \"Change first Option 3 label\",\n trigger: 'we-list table input:eq(2)',\n run: 'text France',\n }, {\n content: \"Check that the label has been changed on the snippet\",\n trigger: \"iframe .s_website_form_field.s_website_form_custom.s_website_form_required\" +\n \":has(.s_website_form_select_item:contains('France'))\",\n run: function () {},\n }, {\n content: \"Click on Add new Checkbox\",\n trigger: 'we-list we-button.o_we_list_add_optional',\n }, {\n content: \"Change last Option label\",\n trigger: \"we-list table input:eq(3)[name='Item']\",\n run: 'text Canada',\n }, {\n content: \"Check that the label has been changed on the snippet\",\n trigger: \"iframe .s_website_form_field.s_website_form_custom.s_website_form_required\" +\n \":has(.s_website_form_select_item:contains('Canada'))\",\n run: function () {},\n }, {\n content: \"Remove Germany Option\",\n trigger: '.o_we_select_remove_option:eq(0)',\n }, {\n content: \"Check that the Germany option was removed\",\n trigger: \"iframe .s_website_form_field.s_website_form_custom.s_website_form_required\" +\n \":has(label:contains('State'))\" +\n \":not(:has(.s_website_form_select_item:contains('Germany')))\",\n run: function () {},\n }, {\n content: \"Click on Add new Checkbox\",\n trigger: 'we-list we-button.o_we_list_add_optional',\n }, {\n content: \"Change last option label with a number\",\n trigger: \"we-list table input:eq(3)[name='Item']\",\n run: 'text 44 - UK',\n }, {\n content: \"Check that the input value is the full option value\",\n trigger: \"we-list table input:eq(3)[name='44 - UK']\",\n run: () => {\n const addedOptionEl = document.querySelector('iframe.o_iframe').contentDocument.querySelector('.s_website_form_field select option[value=\"44 - UK\"]');\n if (!addedOptionEl) {\n console.error('The number option was not correctly added');\n }\n },\n }, {\n content: \"Check the resulting snippet\",\n trigger: \"iframe .s_website_form_field.s_website_form_custom.s_website_form_required\" +\n \":has(label:contains('State'))\" +\n \":has(select[required]:hidden)\" +\n \":has(.s_website_form_select_item:contains('Belgium'))\" +\n \":has(.s_website_form_select_item:contains('France'))\" +\n \":has(.s_website_form_select_item:contains('Canada'))\" +\n \":has(.s_website_form_select_item:contains('44 - UK'))\" +\n \":not(:has(.s_website_form_select_item:contains('Germany')))\",\n run: function () {},\n },\n\n ...addExistingField('attachment_ids', 'file', 'Invoice Scan'),\n\n {\n content: \"Insure the history step of the editor is not checking for unbreakable\",\n trigger: 'iframe #wrapwrap',\n run: () => {\n const wysiwyg = $('iframe:not(.o_ignore_in_tour)').contents().find('#wrapwrap').data('wysiwyg');\n wysiwyg.odooEditor.historyStep(true);\n },\n },\n // Edit the submit button using linkDialog.\n {\n content: \"Click submit button to show edit popover\",\n trigger: 'iframe .s_website_form_send',\n }, {\n content: \"Click on Edit Link in Popover\",\n trigger: 'iframe .o_edit_menu_popover .o_we_edit_link',\n }, {\n content: \"Check that no URL field is suggested\",\n trigger: '#toolbar:has(#url_row:hidden)',\n run: () => null,\n }, {\n content: \"Change button's style\",\n trigger: '.dropdown-toggle[data-bs-original-title=\"Link Style\"]',\n run: () => {\n $('.dropdown-toggle[data-bs-original-title=\"Link Style\"]').click();\n $('[data-value=\"secondary\"]').click();\n $('[data-bs-original-title=\"Link Shape\"]').click();\n $('[data-value=\"rounded-circle\"]').click();\n $('[data-bs-original-title=\"Link Size\"]').click();\n $('[data-value=\"sm\"]').click();\n },\n }, {\n content: \"Check the resulting button\",\n trigger: 'iframe .s_website_form_send.btn.btn-sm.btn-secondary.rounded-circle',\n run: () => null,\n },\n // Add a default value to a auto-fillable field.\n {\n content: 'Select the name field',\n trigger: 'iframe .s_website_form_field:eq(0)',\n }, {\n content: 'Set a default value to the name field',\n trigger: 'we-input[data-attribute-name=\"value\"] input',\n run: 'text John Smith',\n },\n\n // Add two fields: the 1st one's visibility is tied to the 2nd one\n // being set, and the 2nd one is autopopulated. As a result, both\n // should be visible by default.\n ...addCustomField(\"char\", \"text\", \"field A\", false, {visibility: CONDITIONALVISIBILITY}),\n ...addCustomField(\"char\", \"text\", \"field B\", false),\n ...selectFieldByLabel(\"field A\"),\n ...selectButtonByData('data-set-visibility-dependency=\"field B\"'),\n ...selectButtonByData('data-select-data-attribute=\"set\"'),\n ...selectFieldByLabel(\"field B\"),\n {\n content: \"Insert default value\",\n trigger: 'we-input[data-attribute-name=\"value\"] input',\n run: \"text prefilled\",\n },\n {\n content: \"Save the page\",\n trigger: \"button[data-action=save]\",\n },\n {\n content: 'Verify value attribute and property',\n extra_trigger: 'iframe body:not(.editor_enable)',\n trigger: 'iframe .s_website_form_field:eq(0) input[value=\"John Smith\"]:propValue(\"Mitchell Admin\")',\n },\n {\n content: 'Verify that phone field is still auto-fillable',\n trigger: 'iframe .s_website_form_field input[data-fill-with=\"phone\"]:propValue(\"+1 555-555-5555\")',\n },\n // Check that the resulting form behavior is correct.\n {\n content: \"Check that field B prefill text is set\",\n trigger: `iframe ${triggerFieldByLabel(\"field B\")}:has(input[value=\"prefilled\"])`,\n run: () => null, // it's a check\n }, {\n content: \"Check that field A is visible\",\n trigger: `iframe .s_website_form:has(${triggerFieldByLabel(\"field A\")}:visible)`,\n run: () => null, // it's a check\n },\n // A) Check that if we edit again and save again the default value is\n // not deleted.\n // B) Add a 3rd field. Field A's visibility is tied to field B being set,\n // field B is autopopulated and its visibility is tied to field C being\n // set, and field C is empty.\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n content: 'Edit the form',\n trigger: 'iframe .s_website_form_field:eq(0) input',\n run: 'click',\n },\n ...addCustomField(\"char\", \"text\", \"field C\", false),\n ...selectFieldByLabel(\"field B\"),\n ...selectButtonByText(CONDITIONALVISIBILITY),\n ...selectButtonByText(CONDITIONALVISIBILITY),\n {\n content: \"Check that there is a comparator after two clicks on 'Visible only if'\",\n trigger: \"[data-attribute-name='visibilityComparator']\",\n run: function () {\n if (!this.$anchor[0].querySelector(\"we-button.active\")) {\n console.error(\"A default comparator should be set\");\n }\n },\n },\n ...selectButtonByData('data-set-visibility-dependency=\"field C\"'),\n ...selectButtonByData('data-select-data-attribute=\"set\"'),\n {\n content: 'Save the page',\n trigger: 'button[data-action=save]',\n run: 'click',\n },\n\n // Check that the resulting form behavior is correct.\n {\n content: 'Verify that the value has not been deleted',\n extra_trigger: 'iframe body:not(.editor_enable)',\n trigger: 'iframe .s_website_form_field:eq(0) input[value=\"John Smith\"]',\n }, {\n content: \"Check that fields A and B are not visible and that field B's prefill text is still set\",\n trigger: \"iframe .s_website_form\" +\n `:has(${triggerFieldByLabel(\"field A\")}:not(:visible))` +\n `:has(${triggerFieldByLabel(\"field B\")}` +\n `:has(input[value=\"prefilled\"]):not(:visible))`,\n run: () => null, // it's a check\n }, {\n content: \"Type something in field C\",\n trigger: `iframe ${triggerFieldByLabel(\"field C\")} input`,\n run: \"text Sesame\",\n }, {\n content: \"Check that fields A and B are visible\",\n trigger: `iframe .s_website_form:has(${triggerFieldByLabel(\"field B\")}:visible)` +\n `:has(${triggerFieldByLabel(\"field A\")}:visible)`,\n run: () => null, // it's a check\n },\n\n // Have field A's visibility tied to field B containing something,\n // while field B's visibility is also tied to another field.\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n ...selectFieldByLabel(\"field A\"),\n {\n content: \"Verify that the form editor appeared\",\n trigger: \".o_we_customize_panel .snippet-option-WebsiteFormEditor\",\n run: () => null,\n },\n ...selectButtonByData('data-select-data-attribute=\"contains\"'),\n {\n content: \"Tie the visibility of field A to field B containing 'peek-a-boo'\",\n trigger: \"we-input[data-name=hidden_condition_additional_text] input\",\n run: \"text peek-a-boo\",\n },\n ...wTourUtils.clickOnSave(),\n\n // Check that the resulting form works and does not raise an error.\n {\n content: \"Write anything in C\",\n trigger: `iframe ${triggerFieldByLabel(\"field C\")} input`,\n run: \"text Mellon\",\n }, {\n content: \"Check that field B is visible, but field A is not\",\n trigger: `iframe .s_website_form:has(${triggerFieldByLabel(\"field B\")}:visible)` +\n `:has(${triggerFieldByLabel(\"field A\")}:not(:visible))`,\n run: () => null, // it's a check\n }, {\n content: \"Insert 'peek-a-boo' in field B\",\n trigger: `iframe ${triggerFieldByLabel(\"field B\")} input`,\n run: \"text peek-a-boo\",\n }, {\n content: \"Check that field A is visible\",\n trigger: `iframe .s_website_form:has(${triggerFieldByLabel(\"field A\")}:visible)`,\n run: () => null, // it's a check\n },\n\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n ...addCustomField(\"char\", \"text\", \"field D\", false),\n {\n content: \"Select the 'Subject' field\",\n trigger: 'iframe .s_website_form_field.s_website_form_model_required:has(label:contains(\"Subject\"))',\n },\n ...selectButtonByText(CONDITIONALVISIBILITY),\n ...selectButtonByData('data-set-visibility-dependency=\"field D\"'),\n ...selectButtonByData('data-select-data-attribute=\"set\"'),\n {\n content: \"Set a default value to the 'Subject' field\",\n trigger: 'we-input[data-attribute-name=\"value\"] input',\n run: 'text Default Subject',\n },\n {\n content: \"Select the 'Your Message' field\",\n trigger: 'iframe .s_website_form_field.s_website_form_required:has(label:contains(\"Your Message\"))',\n },\n ...selectButtonByText(CONDITIONALVISIBILITY),\n ...selectButtonByData('data-set-visibility-dependency=\"field D\"'),\n ...selectButtonByData('data-select-data-attribute=\"set\"'),\n\n ...wTourUtils.clickOnSave(),\n // Ensure that a field required for a model is not disabled when\n // conditionally hidden.\n {\n content: \"Check that the 'Subject' field is not disabled\",\n trigger: `iframe .s_website_form:has(.s_website_form_model_required ` +\n `.s_website_form_input[value=\"Default Subject\"]:not([disabled]):not(:visible))`,\n run: () => null, // it's a check\n },\n // Ensure that a required field (but not for a model) is disabled when\n // conditionally hidden.\n {\n content: \"Check that the 'Your Message' field is disabled\",\n trigger: `iframe .s_website_form:has(.s_website_form_required ` +\n `.s_website_form_input[name=\"body_html\"][required][disabled]:not(:visible))`,\n run: () => null, // it's a check\n },\n\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n content: \"Select the 'Subject' field\",\n trigger: 'iframe .s_website_form_field.s_website_form_model_required:has(label:contains(\"Subject\"))',\n },\n ...selectButtonByData(\"data-set-visibility='visible'\"),\n {\n content: \"Empty the default value of the 'Subject' field\",\n trigger: 'we-input[data-attribute-name=\"value\"] input',\n run: \"remove_text\",\n },\n {\n content: \"Select the 'Your Message' field\",\n trigger: 'iframe .s_website_form_field.s_website_form_required:has(label:contains(\"Your Message\"))',\n },\n ...selectButtonByData(\"data-set-visibility='visible'\"),\n {\n content: 'Click on the submit button',\n trigger: 'iframe .s_website_form_send',\n run: 'click',\n },\n {\n content: 'Change the Recipient Email',\n trigger: '[data-field-name=\"email_to\"] input',\n run: 'text test@test.test',\n },\n // The next four calls to \"addCustomField\" are there to ensure such\n // characters do not make the form editor crash.\n ...addCustomField(\"char\", \"text\", \"''\", false),\n ...addCustomField(\"char\", \"text\", '\"\"', false),\n ...addCustomField(\"char\", \"text\", \"``\", false),\n ...addCustomField(\"char\", \"text\", \"\\\\\", false),\n {\n content: 'Save the page',\n trigger: 'button[data-action=save]',\n run: 'click',\n },\n {\n content: 'Verify that the recipient email has been saved',\n trigger: 'iframe body:not(.editor_enable)',\n // We have to this that way because the input type = hidden.\n extra_trigger: 'iframe form:has(input[name=\"email_to\"][value=\"test@test.test\"])',\n },\n ]);\n\n function editContactUs(steps) {\n return [\n {\n content: \"Select the contact us form by clicking on an input field\",\n trigger: 'iframe .s_website_form input',\n extra_trigger: '#oe_snippets .oe_snippet_thumbnail',\n run: 'click',\n },\n ...steps,\n {\n content: 'Save the page',\n trigger: 'button[data-action=save]',\n },\n {\n content: 'Wait for reload',\n trigger: 'body:not(.editor_enable)',\n },\n ];\n }\n\n wTourUtils.registerWebsitePreviewTour('website_form_contactus_edition_with_email', {\n url: '/contactus',\n edition: true,\n test: true,\n }, editContactUs([\n {\n content: 'Change the Recipient Email',\n trigger: '[data-field-name=\"email_to\"] input',\n run: 'text test@test.test',\n },\n ]));\n wTourUtils.registerWebsitePreviewTour('website_form_contactus_edition_no_email', {\n url: '/contactus',\n edition: true,\n test: true,\n }, editContactUs([\n {\n content: \"Change a random option\",\n trigger: '[data-set-mark] input',\n run: 'text_blur **',\n }, {\n content: \"Check that the recipient email is correct\",\n trigger: 'we-input[data-field-name=\"email_to\"] input:propValue(\"website_form_contactus_edition_no_email@mail.com\")',\n run: () => null, // it's a check.\n },\n ]));\n\n wTourUtils.registerWebsitePreviewTour('website_form_conditional_required_checkboxes', {\n test: true,\n url: '/',\n edition: true,\n }, [\n // Create a form with two checkboxes: the second one required but\n // invisible when the first one is checked. Basically this should allow\n // to have: both checkboxes are visible by default but the form can\n // only be sent if one of the checkbox is checked.\n {\n content: \"Add the form snippet\",\n trigger: '#oe_snippets .oe_snippet:has(.s_website_form) .oe_snippet_thumbnail',\n run: 'drag_and_drop iframe #wrap',\n }, {\n content: \"Select the form by clicking on an input field\",\n extra_trigger: 'iframe .s_website_form_field',\n trigger: 'iframe section.s_website_form input',\n run: function (actions) {\n actions.auto();\n\n // The next steps will be about removing non essential required\n // fields. For the robustness of the test, check that amount\n // of field stays the same.\n const requiredFields = this.$anchor.closest('[data-snippet]').find('.s_website_form_required');\n if (requiredFields.length !== NB_NON_ESSENTIAL_REQUIRED_FIELDS_IN_DEFAULT_FORM) {\n console.error('The amount of required fields seems to have changed');\n }\n },\n },\n ...((function () {\n const steps = [];\n for (let i = 0; i < NB_NON_ESSENTIAL_REQUIRED_FIELDS_IN_DEFAULT_FORM; i++) {\n steps.push({\n content: \"Select required field to remove\",\n trigger: 'iframe .s_website_form_required .s_website_form_input',\n });\n steps.push({\n content: \"Remove required field\",\n trigger: 'iframe .oe_overlay .oe_snippet_remove',\n });\n }\n return steps;\n })()),\n ...addCustomField('boolean', 'checkbox', 'Checkbox 1', false),\n ...addCustomField('boolean', 'checkbox', 'Checkbox 2', true, {visibility: CONDITIONALVISIBILITY}),\n {\n content: \"Open condition item select\",\n trigger: 'we-select[data-name=\"hidden_condition_opt\"] we-toggler',\n }, {\n content: \"Choose first checkbox as condition item\",\n trigger: 'we-button[data-set-visibility-dependency=\"Checkbox 1\"]',\n }, {\n content: \"Open condition comparator select\",\n trigger: 'we-select[data-attribute-name=\"visibilityComparator\"] we-toggler',\n }, {\n content: \"Choose 'not equal to' comparator\",\n trigger: 'we-button[data-select-data-attribute=\"!selected\"]',\n }, {\n content: 'Save the page',\n trigger: 'button[data-action=save]',\n },\n\n // Check that the resulting form behavior is correct\n {\n content: \"Wait for page reload\",\n trigger: 'iframe body:not(.editor_enable) [data-snippet=\"s_website_form\"]',\n run: function (actions) {\n // The next steps will be about removing non essential required\n // fields. For the robustness of the test, check that amount\n // of field stays the same.\n const essentialFields = this.$anchor.find('.s_website_form_model_required');\n if (essentialFields.length !== ESSENTIAL_FIELDS_VALID_DATA_FOR_DEFAULT_FORM.length) {\n console.error('The amount of model-required fields seems to have changed');\n }\n },\n },\n ...essentialFieldsForDefaultFormFillInSteps,\n {\n content: 'Try sending empty form',\n trigger: 'iframe .s_website_form_send',\n }, {\n content: 'Check the form could not be sent',\n trigger: 'iframe #s_website_form_result.text-danger',\n run: () => null,\n }, {\n content: 'Check the first checkbox',\n trigger: 'iframe input[type=\"checkbox\"][name=\"Checkbox 1\"]',\n }, {\n content: 'Check the second checkbox is now hidden',\n trigger: 'iframe .s_website_form:has(input[type=\"checkbox\"][name=\"Checkbox 2\"]:not(:visible))',\n run: () => null,\n }, {\n content: 'Try sending the form',\n trigger: 'iframe .s_website_form_send',\n }, {\n content: \"Check the form was sent (success page without form)\",\n trigger: 'iframe body:not(:has([data-snippet=\"s_website_form\"])) .fa-check-circle',\n run: () => null,\n }, {\n content: \"Go back to the form\",\n trigger: 'iframe a.navbar-brand.logo',\n },\n ...essentialFieldsForDefaultFormFillInSteps,\n {\n content: 'Check the second checkbox',\n trigger: 'iframe input[type=\"checkbox\"][name=\"Checkbox 2\"]',\n }, {\n content: 'Try sending the form again',\n trigger: 'iframe .s_website_form_send',\n }, {\n content: \"Check the form was again sent (success page without form)\",\n trigger: 'iframe body:not(:has([data-snippet=\"s_website_form\"])) .fa-check-circle',\n run: () => null,\n }\n ]);\n\n wTourUtils.registerWebsitePreviewTour('website_form_contactus_change_random_option', {\n test: true,\n url: '/contactus',\n edition: true,\n }, editContactUs([\n {\n content: \"Change a random option\",\n trigger: '[data-set-mark] input',\n run: 'text_blur **',\n },\n ]));\n\n // Check that the editable form content is actually editable.\n wTourUtils.registerWebsitePreviewTour(\"website_form_editable_content\", {\n test: true,\n url: \"/\",\n edition: true,\n }, [\n {\n ...wTourUtils.dragNDrop({id: \"s_website_form\", name: \"Form\"}),\n run: \"drag_and_drop iframe #wrap\",\n },\n {\n content: \"Check that a form field is not editable\",\n extra_trigger: \"iframe .s_website_form_field\",\n trigger: \"iframe section.s_website_form input\",\n run: function () {\n if (this.$anchor[0].isContentEditable) {\n console.error(\"A form field should not be editable.\");\n }\n },\n },\n {\n content: \"Go back to blocks\",\n trigger: \".o_we_add_snippet_btn\",\n },\n wTourUtils.dragNDrop({id: \"s_three_columns\", name: \"Columns\"}),\n {\n content: \"Select the first column\",\n trigger: \"iframe .s_three_columns .row > :nth-child(1)\",\n },\n {\n content: \"Drag and drop the selected column inside the form\",\n trigger: \"iframe .o_overlay_move_options .ui-draggable-handle\",\n run: \"drag_and_drop iframe section.s_website_form\",\n },\n {\n content: \"Click on the text inside the dropped form column\",\n trigger: \"iframe section.s_website_form h3.card-title\",\n run: \"dblclick\",\n },\n { // Simulate a user interaction with the editable content.\n content: \"Update the text inside the form column\",\n trigger: \"iframe section.s_website_form h3.card-title\",\n run: \"keydown 65 66 67\",\n },\n {\n content: \"Check that the new text value was correctly set\",\n trigger: \"iframe section.s_website_form h3:containsExact(ABC)\",\n run: () => null, // it's a check\n },\n { content: \"Remove the dropped column\",\n trigger: \"iframe .oe_overlay.oe_active .oe_snippet_remove\",\n run: \"click\",\n },\n ...wTourUtils.clickOnSave(),\n ]);\n\n wTourUtils.registerWebsitePreviewTour(\"website_form_special_characters\", {\n test: true,\n url: \"/\",\n edition: true,\n }, [\n {\n ...wTourUtils.dragNDrop({id: \"s_website_form\", name: \"Form\"}),\n run: \"drag_and_drop iframe #wrap\",\n },\n {\n content: \"Select form by clicking on an input field\",\n extra_trigger: \"iframe .s_website_form_field\",\n trigger: \"iframe section.s_website_form input\",\n },\n ...addCustomField(\"char\", \"text\", `Test1\"'`, false),\n ...addCustomField(\"char\", \"text\", 'Test2`\\\\', false),\n ...wTourUtils.clickOnSave(),\n ...essentialFieldsForDefaultFormFillInSteps,\n {\n content: \"Complete 'Your Question' field\",\n trigger: \"iframe textarea[name='description']\",\n run: \"text test\",\n }, {\n content: \"Complete the first added field\",\n trigger: \"iframe input[name='Test1"'']\",\n run: \"text test1\",\n }, {\n content: \"Complete the second added field\",\n trigger: \"iframe input[name='Test2‘\']\",\n run: \"text test2\",\n }, {\n content: \"Click on 'Submit'\",\n trigger: \"iframe a.s_website_form_send\",\n }, {\n content: \"Check the form was again sent (success page without form)\",\n trigger: \"iframe body:not(:has([data-snippet='s_website_form'])) .fa-check-circle\",\n run: () => null,\n },\n ]);\n\n return {};\n});\n", "/** @odoo-module **/\nimport tour from 'web_tour.tour';\nimport rpc from 'web.rpc';\n\ntour.register(\"website_form_editor_tour_submit\", {\n test: true,\n},[\n {\n content: \"Try to send the form with some required fields not filled in\",\n extra_trigger: \"form[data-model_name='mail.mail']\" +\n \"[data-success-page='/contactus-thank-you']\" +\n \":has(.s_website_form_field:has(label:contains('Your Name')):has(input[type='text'][name='name'][required]))\" +\n \":has(.s_website_form_field:has(label:contains('Your Email')):has(input[type='email'][name='email_from'][required]))\" +\n \":has(.s_website_form_field:has(label:contains('Your Question')):has(textarea[name='description'][required]))\" +\n \":has(.s_website_form_field:has(label:contains('Subject')):has(input[type='text'][name='subject'][required]))\" +\n \":has(.s_website_form_field:has(label:contains('Test Date')):has(input[type='text'][name='date'][required]))\" +\n \":has(.s_website_form_field:has(label:contains('Awesome Label')):hidden)\" +\n \":has(.s_website_form_field:has(label:contains('Your Message')):has(textarea[name='body_html'][required]))\" +\n \":has(.s_website_form_field:has(label:contains('Products')):has(input[type='checkbox'][name='Products'][value='Iphone'][required]))\" +\n \":has(.s_website_form_field:has(label:contains('Products')):has(input[type='checkbox'][name='Products'][value='Galaxy S'][required]))\" +\n \":has(.s_website_form_field:has(label:contains('Products')):has(input[type='checkbox'][name='Products'][value='Xperia'][required]))\" +\n \":has(.s_website_form_field:has(label:contains('Products')):has(input[type='checkbox'][name='Products'][value='Wiko Stairway'][required]))\" +\n \":has(.s_website_form_field:has(label:contains('Service')):has(input[type='radio'][name='Service'][value='After-sales Service']:not([required])))\" +\n \":has(.s_website_form_field:has(label:contains('Service')):has(input[type='radio'][name='Service'][value='Invoicing Service']:not([required])))\" +\n \":has(.s_website_form_field:has(label:contains('Service')):has(input[type='radio'][name='Service'][value='Development Service']:not([required])))\" +\n \":has(.s_website_form_field:has(label:contains('Service')):has(input[type='radio'][name='Service'][value='Management Service']:not([required])))\" +\n \":has(.s_website_form_field:has(label:contains('State')):has(select[name='State'][required]:has(option[value='Belgium'])))\" +\n \":has(.s_website_form_field.s_website_form_required:has(label:contains('State')):has(select[name='State'][required]:has(option[value='France'])))\" +\n \":has(.s_website_form_field:has(label:contains('State')):has(select[name='State'][required]:has(option[value='Canada'])))\" +\n \":has(.s_website_form_field:has(label:contains('Invoice Scan')))\" +\n \":has(.s_website_form_field:has(input[name='email_to'][value='test@test.test']))\" +\n \":has(.s_website_form_field:has(input[name='website_form_signature']))\",\n trigger: \".s_website_form_send\"\n },\n {\n content: \"Check if required fields were detected and complete the Subject field\",\n extra_trigger: \"form:has(#s_website_form_result.text-danger)\" +\n \":has(.s_website_form_field:has(label:contains('Your Name')):not(.o_has_error))\" +\n \":has(.s_website_form_field:has(label:contains('Email')).o_has_error)\" +\n \":has(.s_website_form_field:has(label:contains('Your Question')).o_has_error)\" +\n \":has(.s_website_form_field:has(label:contains('Subject')).o_has_error)\" +\n \":has(.s_website_form_field:has(label:contains('Test Date')).o_has_error)\" +\n \":has(.s_website_form_field:has(label:contains('Your Message')).o_has_error)\" +\n \":has(.s_website_form_field:has(label:contains('Products')).o_has_error)\" +\n \":has(.s_website_form_field:has(label:contains('Service')):not(.o_has_error))\" +\n \":has(.s_website_form_field:has(label:contains('State')).o_has_error)\" +\n \":has(.s_website_form_field:has(label:contains('Invoice Scan')):not(.o_has_error))\",\n trigger: \"input[name=subject]\",\n run: \"text Jane Smith\"\n },\n {\n content: \"Update required field status by trying to Send again\",\n trigger: \".s_website_form_send\"\n },\n {\n content: \"Check if required fields were detected and complete the Message field\",\n extra_trigger: \"form:has(#s_website_form_result.text-danger)\" +\n \":has(.s_website_form_field:has(label:contains('Your Name')):not(.o_has_error))\" +\n \":has(.s_website_form_field:has(label:contains('Email')).o_has_error)\" +\n \":has(.s_website_form_field:has(label:contains('Your Question')).o_has_error)\" +\n \":has(.s_website_form_field:has(label:contains('Subject')):not(.o_has_error))\" +\n \":has(.s_website_form_field:has(label:contains('Test Date')).o_has_error)\" +\n \":has(.s_website_form_field:has(label:contains('Your Message')).o_has_error)\" +\n \":has(.s_website_form_field:has(label:contains('Products')).o_has_error)\" +\n \":has(.s_website_form_field:has(label:contains('Service')):not(.o_has_error))\" +\n \":has(.s_website_form_field:has(label:contains('State')).o_has_error)\" +\n \":has(.s_website_form_field:has(label:contains('Invoice Scan')):not(.o_has_error))\",\n trigger: \"textarea[name=body_html]\",\n run: \"text A useless message\"\n },\n {\n content: \"Update required field status by trying to Send again\",\n trigger: \".s_website_form_send\"\n },\n {\n content: \"Check if required fields was detected and check a product. If this fails, you probably broke the cleanForSave.\",\n extra_trigger: \"form:has(#s_website_form_result.text-danger)\" +\n \":has(.s_website_form_field:has(label:contains('Your Name')):not(.o_has_error))\" +\n \":has(.s_website_form_field:has(label:contains('Email')).o_has_error)\" +\n \":has(.s_website_form_field:has(label:contains('Your Question')).o_has_error)\" +\n \":has(.s_website_form_field:has(label:contains('Subject')):not(.o_has_error))\" +\n \":has(.s_website_form_field:has(label:contains('Test Date')).o_has_error)\" +\n \":has(.s_website_form_field:has(label:contains('Your Message')):not(.o_has_error))\" +\n \":has(.s_website_form_field:has(label:contains('Products')).o_has_error)\" +\n \":has(.s_website_form_field:has(label:contains('Service')):not(.o_has_error))\" +\n \":has(.s_website_form_field:has(label:contains('State')).o_has_error)\" +\n \":has(.s_website_form_field:has(label:contains('Invoice Scan')):not(.o_has_error))\",\n trigger: \"input[name=Products][value='Wiko Stairway']\"\n },\n {\n content: \"Complete Date field\",\n trigger: \".s_website_form_datetime [data-toggle='datetimepicker']\",\n },\n {\n content: \"Check another product\",\n trigger: \"input[name='Products'][value='Xperia']\"\n },\n {\n content: \"Check a service\",\n trigger: \"input[name='Service'][value='Development Service']\"\n },\n {\n content: \"Select a State\",\n trigger: \"select[name='State']\",\n run: \"text Canada\",\n },\n {\n content: \"Complete Your Name field\",\n trigger: \"input[name='name']\",\n run: \"text chhagan\"\n },\n {\n content: \"Complete Email field\",\n trigger: \"input[name=email_from]\",\n run: \"text test@mail.com\"\n },\n {\n content: \"Complete Subject field\",\n trigger: 'input[name=\"subject\"]',\n run: 'text subject',\n },\n {\n content: \"Complete Your Question field\",\n trigger: \"textarea[name='description']\",\n run: \"text magan\"\n },\n {\n content: \"Check if conditional field is visible, it shouldn't.\",\n trigger: \"body\",\n run: function () {\n const style = window.getComputedStyle(this.$anchor[0].getElementsByClassName('s_website_form_field_hidden_if')[0]);\n if (style.display !== 'none') {\n console.error('error This field should be invisible when the name is not odoo');\n }\n }\n },\n {\n content: \"Change name input\",\n trigger: \"input[name='name']\",\n run: \"text odoo\",\n },\n {\n content: \"Check if conditional field is visible, it should.\",\n trigger: \"input[name='email_cc']\",\n },\n {\n content: \"Select state option\",\n trigger: \"select[name='State']\",\n run: 'text 44 - UK',\n },\n {\n content: \"Send the form\",\n trigger: \".s_website_form_send\"\n },\n {\n content: \"Check form is submitted without errors\",\n trigger: \"#wrap:has(h1:contains('Thank You!'))\"\n }\n]);\n\ntour.register(\"website_form_editor_tour_results\", {\n test: true,\n}, [\n {\n content: \"Check mail.mail records have been created\",\n trigger: \"body\",\n run: function () {\n var mailDef = rpc.query({\n model: 'mail.mail',\n method: 'search_count',\n args: [[\n ['email_to', '=', 'test@test.test'],\n ['body_html', 'like', 'A useless message'],\n ['body_html', 'like', 'Service : Development Service'],\n ['body_html', 'like', 'State : 44 - UK'],\n ['body_html', 'like', 'Products : Xperia,Wiko Stairway']\n ]],\n });\n var success = function(model, count) {\n if (count > 0) {\n $('body').append('
');\n }\n };\n mailDef.then(_.bind(success, this, 'mail_mail'));\n }\n },\n {\n content: \"Check mail.mail records have been created\",\n trigger: \"#website_form_editor_success_test_tour_mail_mail\"\n }\n]);\ntour.register('website_form_contactus_submit', {\n test: true,\n url: '/contactus',\n}, [\n // As the demo portal user, only two inputs needs to be filled to send\n // the email\n {\n content: \"Fill in the subject\",\n trigger: 'input[name=\"subject\"]',\n },\n {\n content: 'Fill in the message',\n trigger: 'textarea[name=\"description\"]',\n },\n {\n content: 'Send the form',\n trigger: '.s_website_form_send',\n },\n {\n content: 'Check form is submitted without errors',\n trigger: '#wrap:has(h1:contains(\"Thank You!\"))',\n },\n]);\ntour.register('website_form_contactus_check_changed_email', {\n test: true,\n url: '/contactus',\n}, [\n {\n content: \"Check that the recipient email is updated\",\n trigger: 'form:has(input[name=\"email_to\"][value=\"after.change@mail.com\"])',\n run: () => null, // it's a check.\n },\n]);\n", "odoo.define(\"website.tour.website_navbar_menu\", function (require) {\n\"use strict\";\n\nvar tour = require(\"web_tour.tour\");\n\ntour.register(\"website_navbar_menu\", {\n test: true,\n url: \"/\",\n}, [\n {\n content: \"Ensure menus are in DOM\",\n trigger: '#top_menu .nav-item a:contains(\"Test Tour Menu\")',\n run: function () {}, // it's a check\n }, {\n content: \"Ensure menus loading is done (so they are actually visible)\",\n trigger: 'body:not(:has(.o_menu_loading))',\n run: function () {}, // it's a check\n }\n]);\n});\n", "/** @odoo-module **/\n\nimport wTourUtils from 'website.tour_utils';\n\nconst makeSteps = (steps = []) => [\n wTourUtils.dragNDrop({\n id: \"s_text_image\",\n name: \"Text - Image\",\n }), {\n content: \"Click on Discard\",\n trigger: '.o_we_website_top_actions [data-action=\"cancel\"]',\n }, {\n content: \"Check that discarding actually warns when there are dirty changes, and cancel\",\n trigger: \".modal-footer .btn-secondary\",\n },\n ...wTourUtils.clickOnSave(),\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n // This makes sure the last step about leaving edit mode at the end of\n // this tour makes sense.\n content: \"Confirm we are in edit mode\",\n trigger: 'body.editor_has_snippets',\n run: () => null,\n },\n ...steps,\n {\n // Makes sure the dirty flag does not happen after a setTimeout or\n // something like that.\n content: \"Click elsewhere and wait for a few ms\",\n trigger: 'iframe #wrap',\n run: function (actions) {\n actions.auto();\n setTimeout(() => document.body.classList.add('o_test_delay'), 999);\n },\n }, {\n content: \"Click on Discard\",\n trigger: '.o_we_website_top_actions [data-action=\"cancel\"]',\n extra_trigger: 'body.o_test_delay',\n }, {\n content: \"Confirm we are not in edit mode anymore\",\n trigger: 'body:not(.editor_has_snippets)',\n run: () => null,\n },\n];\n\nwTourUtils.registerWebsitePreviewTour('website_no_action_no_dirty_page', {\n test: true,\n url: '/',\n edition: true,\n}, makeSteps());\n\nwTourUtils.registerWebsitePreviewTour('website_no_dirty_page', {\n test: true,\n url: '/',\n edition: true,\n}, makeSteps([\n {\n content: \"Click on default paragraph\",\n trigger: 'iframe .s_text_image h2 + p.o_default_snippet_text',\n }, {\n // TODO this should be done in a dedicated test which would be testing\n // all default snippet texts behaviors. Will be done in master where a\n // task will review this feature.\n // TODO also test that applying an editor command removes that class.\n content: \"Make sure the paragraph still acts as a default paragraph\",\n trigger: 'iframe .s_text_image h2 + p.o_default_snippet_text',\n run: () => null,\n }, {\n content: \"Click on button\",\n trigger: 'iframe .s_text_image .btn',\n run: function (actions) {\n actions.click();\n const el = this.$anchor[0];\n const sel = el.ownerDocument.getSelection();\n sel.collapse(el, 0);\n el.focus();\n },\n },\n]));\n", "/** @odoo-module **/\n\nimport wTourUtils from 'website.tour_utils';\n\nwTourUtils.registerWebsitePreviewTour('website_page_options', {\n test: true,\n url: '/',\n edition: true,\n}, [\n wTourUtils.clickOnSnippet({id: 'o_header_standard', name: 'Header'}),\n wTourUtils.changeOption('TopMenuVisibility', 'we-select:has([data-visibility]) we-toggler'),\n wTourUtils.changeOption('TopMenuVisibility', 'we-button[data-visibility=\"transparent\"]'),\n // It's important to test saving right after changing that option only as\n // this is why this test was made in the first place: the page was not\n // marked as dirty when that option was the only one that was changed.\n ...wTourUtils.clickOnSave(),\n {\n content: \"Check that the header is transparent\",\n trigger: 'iframe #wrapwrap.o_header_overlay',\n run: () => null, // it's a check\n },\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n wTourUtils.clickOnSnippet({id: 'o_header_standard', name: 'Header'}),\n wTourUtils.changeOption('topMenuColor', 'we-select.o_we_so_color_palette'),\n wTourUtils.changeOption('topMenuColor', 'button[data-color=\"black-50\"]', 'background color', 'bottom', true),\n ...wTourUtils.clickOnSave(),\n {\n content: \"Check that the header is in black-50\",\n trigger: 'iframe header#top.bg-black-50',\n run: () => null, // it's a check\n },\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n wTourUtils.clickOnSnippet({id: 'o_header_standard', name: 'Header'}),\n wTourUtils.changeOption('TopMenuVisibility', 'we-select:has([data-visibility]) we-toggler'),\n wTourUtils.changeOption('TopMenuVisibility', 'we-button[data-visibility=\"hidden\"]'),\n ...wTourUtils.clickOnSave(),\n {\n content: \"Check that the header is hidden\",\n trigger: 'iframe #wrapwrap:has(header#top.d-none.o_snippet_invisible)',\n run: () => null, // it's a check\n },\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n content: \"Click on 'header' in the invisible elements list\",\n trigger: '.o_we_invisible_el_panel .o_we_invisible_entry',\n },\n wTourUtils.clickOnSnippet({id: 'o_footer', name: 'Footer'}),\n wTourUtils.changeOption('HideFooter', 'we-button[data-name=\"hide_footer_page_opt\"] we-checkbox'),\n ...wTourUtils.clickOnSave(),\n {\n content: \"Check that the footer is hidden and the header is visible\",\n trigger: 'iframe #wrapwrap:has(.o_footer.d-none.o_snippet_invisible)',\n extra_trigger: 'iframe #wrapwrap header#top:not(.d-none)',\n run: () => null, // it's a check\n },\n]);\n", "/** @odoo-module **/\n\nimport wTourUtils from 'website.tour_utils';\n\nwTourUtils.registerWebsitePreviewTour(\"website_snippets_menu_tabs\", {\n test: true,\n url: \"/\",\n edition: true,\n}, [\n wTourUtils.goToTheme(),\n {\n content: \"Click on the empty 'DRAG BUILDING BLOCKS HERE' area.\",\n extra_trigger: 'we-customizeblock-option.snippet-option-ThemeColors',\n trigger: 'iframe main > .oe_structure.oe_empty',\n run: 'click',\n },\n wTourUtils.goToTheme(),\n {\n content: \"Verify that the customize panel is not empty.\",\n trigger: '.o_we_customize_panel > we-customizeblock-options',\n run: () => null, // it's a check\n },\n {\n content: \"Click on the style tab.\",\n trigger: '#snippets_menu .o_we_customize_snippet_btn',\n },\n wTourUtils.goToTheme(),\n {\n content: \"Verify that the customize panel is not empty.\",\n trigger: '.o_we_customize_panel > we-customizeblock-options',\n run: () => null, // it's a check\n },\n]);\n", "odoo.define(\"website.tour.website_style_edition\", function (require) {\n\"use strict\";\n\nconst wTourUtils = require(\"website.tour_utils\");\n\nconst TARGET_FONT_SIZE = 30;\n\nconst checkFontSize = function (actions) {\n const style = actions.tip_widget.el.ownerDocument.defaultView.getComputedStyle(this.$anchor[0]);\n if (style.fontSize !== `${TARGET_FONT_SIZE}px`) {\n console.error(`Expected the font-size to be equal to ${TARGET_FONT_SIZE}px but found ${style.fontSize} instead`);\n }\n};\n\nwTourUtils.registerWebsitePreviewTour(\"website_style_edition\", {\n test: true,\n url: '/',\n edition: true,\n}, [{\n content: \"Go to theme options\",\n trigger: '.o_we_customize_theme_btn',\n}, {\n content: \"Change font size\",\n trigger: '[data-variable=\"font-size-base\"] input',\n run: `text_blur ${TARGET_FONT_SIZE}`,\n}, {\n content: \"Check the font size was properly adapted\",\n trigger: 'iframe #wrapwrap',\n // Waiting the CSS to be reloaded: the code adds a new assets bundle with\n // a #t=... at the end then removes the old one.\n extra_trigger: 'iframe html:not(:has(link[href$=\"web.assets_frontend.min.css\"]))',\n run: checkFontSize,\n},\n...wTourUtils.clickOnSave(),\n{\n content: \"Check the font size is still ok outside of edit mode\",\n trigger: 'iframe body:not(.editor_enable) #wrapwrap',\n run: checkFontSize,\n},\n...wTourUtils.clickOnEditAndWaitEditMode(),\nwTourUtils.goToTheme(),\n{\n content: \"Click on the Background Image selection\",\n trigger: '[data-customize-body-bg-type=\"\\'image\\'\"]:not(.active)',\n extra_trigger: '[data-customize-body-bg-type=\"NONE\"].active',\n}, {\n content: \"The media dialog should open\",\n trigger: '.o_select_media_dialog',\n run: () => {}, // It's a check.\n}]);\n});\n", "/** @odoo-module **/\n\nimport wTourUtils from 'website.tour_utils';\n\nconst WEBSITE_MAIN_COLOR = '#ABCDEF';\n\nwTourUtils.registerWebsitePreviewTour('website_text_edition', {\n test: true,\n url: '/',\n edition: true,\n}, [\n {\n content: \"Go to theme options\",\n trigger: '.o_we_customize_theme_btn',\n },\n {\n content: \"Open colorpicker to change website main color\",\n trigger: 'we-select[data-color=\"o-color-1\"] .o_we_color_preview',\n },\n {\n content: \"Input the value for the new website main color (also make sure it is independent from the backend)\",\n trigger: '.o_hex_input',\n run: `text_blur ${WEBSITE_MAIN_COLOR}`,\n },\n {\n content: \"Go to the blocks tab\",\n trigger: '.o_we_add_snippet_btn',\n },\n wTourUtils.dragNDrop({id: 's_text_block', name: 'Text'}),\n {\n content: \"Click on the text block first paragraph (to auto select)\",\n trigger: 'iframe .s_text_block p',\n },\n {\n content: \"Open the foreground colorpicker\",\n trigger: '#oe-text-color',\n },\n {\n content: \"Go to the 'solid' tab\",\n trigger: '.o_we_colorpicker_switch_pane_btn[data-target=\"custom-colors\"]',\n },\n {\n content: \"Input the website main color explicitly\",\n trigger: '.o_hex_input',\n run: `text_blur ${WEBSITE_MAIN_COLOR}`,\n },\n {\n content: \"Check that paragraph now uses the main color *class*\",\n trigger: 'iframe .s_text_block p',\n run: function (actions) {\n const fontEl = this.$anchor[0].querySelector('font');\n if (!fontEl) {\n console.error(\"A background color should have been applied\");\n return;\n }\n if (fontEl.style.backgroundColor) {\n console.error(\"The paragraph should not have an inline style background color\");\n return;\n }\n if (!fontEl.classList.contains('text-o-color-1')) {\n console.error(\"The paragraph should have the right background color class\");\n return;\n }\n },\n }\n]);\n", "/** @odoo-module **/\n\nimport wTourUtils from \"website.tour_utils\";\n\n// Note: cannot import @website/../tests/tour_utils/widget_lifecycle_dep_widget\n// here because that module requires web.public.widget which is not available\n// in the backend, where this tour definition is loaded. Easier to duplicate\n// that key for now rather than create a whole file to handle this localStorage\n// key only.\nconst localStorageKey = 'widgetAndWysiwygLifecycle';\n\nwTourUtils.registerWebsitePreviewTour(\"widget_lifecycle\", {\n test: true,\n url: \"/\",\n edition: true,\n}, [\n wTourUtils.dragNDrop({\n id: \"s_countdown\",\n name: \"Countdown\",\n }),\n {\n content: \"Wait for the widget to be started and empty the widgetAndWysiwygLifecycle list\",\n trigger: \"iframe .s_countdown.public_widget_started\",\n run: () => {\n // Start recording the calls to the \"start\" and \"destroy\" method of\n // the widget and the wysiwyg.\n window.localStorage.setItem(localStorageKey, '[]');\n },\n },\n ...wTourUtils.clickOnSave(),\n {\n content: \"Wait for the widget to be started\",\n trigger: \"iframe .s_countdown.public_widget_started\",\n run: () => {}, // It's a check\n },\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n content: \"Wait for the widget to be started and check the order of the lifecycle method call of the widget and the wysiwyg\",\n trigger: \"iframe .s_countdown.public_widget_started\",\n run: () => {\n const result = JSON.parse(window.localStorage.widgetAndWysiwygLifecycle);\n const expected = [\"widgetStop\", \"wysiwygStop\", \"wysiwygStop\", \"widgetStart\",\n \"widgetStop\", \"wysiwygStart\", \"wysiwygStarted\", \"widgetStart\",\n ];\n const alternative = [\"widgetStop\", \"wysiwygStop\", \"widgetStart\", \"wysiwygStop\",\n \"widgetStop\", \"wysiwygStart\", \"wysiwygStarted\", \"widgetStart\",\n ];\n const resultIsEqualTo = (arr) => {\n return arr.length === result.length\n && arr.every((item, i) => item === result[i]);\n };\n if (!(resultIsEqualTo(expected) || resultIsEqualTo(alternative))) {\n // The \"destroy\" method of the wysiwyg is called two times when\n // leaving the edit mode: the first one comes explicitly from\n // the \"leaveEditMode\" of the \"wysiwyg_adapter\". The second\n // comes from the OWL mechanism as the wysiwyg is not present in\n // the DOM when the page is reloaded. Because it is not\n // guaranteed that this last call happens before the start of\n // the widget at the page reload, two sequences are acceptable\n // as a result.\n console.error(`\n Expected: ${expected.toString()}\n Or: ${alternative.toString()}\n Result: ${result.toString()}\n `);\n }\n },\n },\n]);\n", "/** @odoo-module **/\nimport { patch } from 'web.utils';\nimport { WebsitePreview } from '@website/client_actions/website_preview/website_preview';\n\npatch(WebsitePreview.prototype, 'website_preview_test_mode', {\n /**\n * @override\n */\n get testMode() {\n return true;\n }\n});\n", "odoo.define('sale.tour_sale_signature', function (require) {\n'use strict';\n\nvar tour = require('web_tour.tour');\n\n// This tour relies on data created on the Python test.\ntour.register('sale_signature', {\n test: true,\n url: '/my/quotes',\n},\n[\n {\n content: \"open the test SO\",\n trigger: 'a:containsExact(\"test SO\")',\n },\n {\n content: \"click sign\",\n trigger: 'a:contains(\"Sign\")',\n },\n {\n content: \"check submit is enabled\",\n trigger: '.o_portal_sign_submit:enabled',\n run: function () {},\n },\n {\n content: \"click select style\",\n trigger: '.o_web_sign_auto_select_style a',\n },\n {\n content: \"click style 4\",\n trigger: '.o_web_sign_auto_font_selection a:eq(3)',\n },\n {\n content: \"click submit\",\n trigger: '.o_portal_sign_submit:enabled',\n },\n {\n content: \"check it's confirmed\",\n trigger: '#quote_content:contains(\"Thank You\")',\n }, {\n trigger: '#quote_content',\n run: function () {\n window.location.href = window.location.origin + '/web';\n }, // Avoid race condition at the end of the tour by returning to the home page.\n },\n {\n trigger: 'nav',\n run: function() {},\n }\n]);\n});\n", "/** @odoo-module **/\nimport tour from 'web_tour.tour';\n\nconst todayDate = function() {\n let now = new Date();\n let year = now.getFullYear();\n let month = String(now.getMonth() + 1).padStart(2, '0');\n let day = String(now.getDate()).padStart(2, '0');\n\n return `${month}/${day}/${year} 10:00:00`;\n};\n\ntour.register('calendar_appointments_hour_tour', {\n url: '/web',\n test: true,\n}, [\n tour.stepUtils.showAppsMenuItem(),\n {\n trigger: '.o_app[data-menu-xmlid=\"calendar.mail_menu_calendar\"]',\n content: 'Open Calendar',\n run: 'click',\n },\n {\n trigger: '.o-calendar-button-new',\n content: 'Create a new event',\n run: 'click',\n },\n {\n trigger: '#name',\n content: 'Give a name to the new event',\n run: 'text TEST EVENT',\n },\n {\n trigger: '#start',\n content: 'Give a date to the new event',\n run: `text ${todayDate()}`,\n },\n {\n trigger: '.fa-cloud-upload',\n content: 'Save the new event',\n run: 'click',\n },\n {\n trigger: '.dropdown-item:contains(\"Calendar\")',\n content: 'Go back to Calendar view',\n run: 'click',\n },\n {\n trigger: '.dropdown-toggle:contains(\"Week\")',\n content: 'Click to change calendar view',\n run: 'click',\n },\n {\n trigger: '.dropdown-item:contains(\"Month\")',\n content: 'Change the calendar view to Month',\n run: 'click',\n },\n {\n trigger: '.fc-day-header:contains(\"Monday\")',\n content: 'Change the calendar view to week',\n },\n {\n trigger: '.fc-time:contains(\"10:00\")',\n content: 'Check the time is properly displayed',\n },\n {\n trigger: '.o_event_title:contains(\"TEST EVENT\")',\n content: 'Check the event title',\n },\n]);\n\ntour.register('test_calendar_delete_tour', {\n test: true,\n},\n[\n {\n content: 'Select filter (everybody)',\n trigger: 'div[data-value=\"all\"] input',\n },\n {\n content: 'Click on the event (focus + waiting)',\n trigger: 'a .fc-content:contains(\"Test Event\")',\n async run() {\n $('a .fc-content:contains(\"Test Event\")').click();\n await new Promise((r) => setTimeout(r, 1000));\n $('a .fc-content:contains(\"Test Event\")').click();\n }\n },\n {\n content: 'Delete the event',\n trigger: '.o_cw_popover_delete',\n },\n {\n content: 'Validate the deletion',\n trigger:'button:contains(\"Ok\")',\n async run() {\n $('button:contains(\"Ok\")').click();\n await new Promise((r) => setTimeout(r, 1000));\n }\n },\n]);\n\ntour.register('test_calendar_decline_tour', {\n test: true,\n},\n[\n {\n content: 'Click on the event (focus + waiting)',\n trigger: 'a .fc-content:contains(\"Test Event\")',\n async run() {\n $('a .fc-content:contains(\"Test Event\")').click();\n await new Promise((r) => setTimeout(r, 1000));\n $('a .fc-content:contains(\"Test Event\")').click();\n }\n },\n {\n content: 'Delete the event',\n trigger: '.o_cw_popover_delete',\n },\n {\n content: 'Wait declined status',\n trigger: '.o_attendee_status_declined',\n },\n]);\n\ntour.register('test_calendar_decline_with_everybody_filter_tour', {\n test: true,\n},\n[\n {\n content: 'Select filter (everybody)',\n trigger: 'div[data-value=\"all\"] input',\n },\n {\n content: 'Click on the event (focus + waiting)',\n trigger: 'a .fc-content:contains(\"Test Event\")',\n async run() {\n $('a .fc-content:contains(\"Test Event\")').click();\n await new Promise((r) => setTimeout(r, 1000));\n $('a .fc-content:contains(\"Test Event\")').click();\n }\n },\n {\n content: 'Delete the event',\n trigger: '.o_cw_popover_delete',\n },\n {\n content: 'Select filter (everybody)',\n trigger: 'div[data-value=\"all\"] input',\n },\n {\n content: 'Wait declined status',\n trigger: '.o_attendee_status_declined',\n },\n]);\n", "odoo.define('debug_menu_set_defaults.tour', function (require) {\n \"use strict\";\n\n var tour = require('web_tour.tour');\n\n tour.register('debug_menu_set_defaults', {\n test: true,\n url: '/web?debug=1',\n },\n [\n ...tour.stepUtils.goToAppSteps('contacts.menu_contacts', \"Open the contacts menu\"),\n {\n content: \"Create a new contact\",\n trigger: '.o-kanban-button-new',\n },\n {\n content: \"Check that Company is checked by default, and not Individual\",\n trigger: '.o_field_widget[name=\"company_type\"] input[data-value=\"company\"]:checked',\n run: function () {},\n },\n {\n content: \"Select the individual radio button\",\n trigger: '.o_field_widget[name=\"company_type\"] input[data-value=\"person\"]',\n },\n {\n content: \"Open the debug menu\",\n trigger: '.o_debug_manager button',\n },\n {\n content: \"Click the Set Defaults menu\",\n trigger: '.o_debug_manager .dropdown-item:contains(Set Defaults)',\n },\n {\n content: \"Choose Company Type = Individual\",\n trigger: '#formview_default_fields',\n run: function () {\n const element_field = document.querySelector('select#formview_default_fields');\n element_field.value = 'company_type';\n element_field.dispatchEvent(new Event(\"change\"));\n },\n },\n {\n content: \"Check that there are conditions\",\n trigger: '#formview_default_conditions',\n },\n {\n content: \"Save the new default\",\n trigger: 'footer button:contains(Save default)',\n },\n {\n content: \"Discard the contact creation\",\n trigger: 'button.o_form_button_cancel',\n },\n {\n trigger: '.o-kanban-button-new',\n },\n {\n content: \"Check that Individual is checked instead of Company\",\n trigger: '.o_field_widget[name=\"company_type\"] input[data-value=\"person\"]:checked',\n run: function () {},\n },\n {\n content: \"Discard the contact creation\",\n trigger: 'button.o_form_button_cancel',\n },\n {\n content: \"Wait for discard\",\n trigger: '.o_control_panel .o-kanban-button-new',\n run() {},\n },\n ]\n );\n\n});\n", "/** @odoo-module */\n\nimport tour from 'web_tour.tour';\n\ntour.register('create_crm_team_tour', {\n url: \"/web\",\n test: true,\n}, [\n ...tour.stepUtils.goToAppSteps('crm.crm_menu_root'),\n{\n trigger: 'button[data-menu-xmlid=\"crm.crm_menu_config\"]',\n}, {\n trigger: 'a[data-menu-xmlid=\"crm.crm_team_config\"]',\n}, {\n trigger: 'button.o_list_button_add',\n}, {\n trigger: 'input[id=\"name\"]',\n run: 'text My CRM Team',\n}, {\n trigger: 'button.o-kanban-button-new',\n}, {\n trigger: 'div.modal-dialog tr:contains(\"Test Salesman\") input.form-check-input',\n run: 'click',\n}, {\n trigger: 'div.modal-dialog tr:contains(\"Test Sales Manager\") input.form-check-input',\n run: 'click',\n}, {\n trigger: 'div.modal-dialog tr:contains(\"Test Sales Manager\") input.form-check-input:checked',\n run: () => {},\n}, {\n trigger: 'button.o_select_button',\n}, \n ...tour.stepUtils.saveForm()\n]);\n", "odoo.define('crm.crm_email_and_phone_propagation', function (require) {\n 'use strict';\n\n const tour = require('web_tour.tour');\n\n tour.register('crm_email_and_phone_propagation_edit_save', {\n test: true,\n url: '/web',\n }, [\n tour.stepUtils.showAppsMenuItem(),\n {\n trigger: '.o_app[data-menu-xmlid=\"crm.crm_menu_root\"]',\n content: 'open crm app',\n }, {\n trigger: '.o_kanban_record .o_kanban_record_title span:contains(Test Lead Propagation)',\n content: 'Open the first lead',\n run: 'click',\n }, {\n trigger: '.o_form_button_save',\n extra_trigger: '.o_form_editable .o_field_widget[name=email_from] input',\n content: 'Save the lead',\n run: 'click',\n },\n ]);\n\n tour.register('crm_email_and_phone_propagation_remove_email_and_phone', {\n test: true,\n url: '/web',\n }, [\n tour.stepUtils.showAppsMenuItem(),\n {\n trigger: '.o_app[data-menu-xmlid=\"crm.crm_menu_root\"]',\n content: 'open crm app',\n }, {\n trigger: '.o_kanban_record .o_kanban_record_title span:contains(Test Lead Propagation)',\n content: 'Open the first lead',\n run: 'click',\n }, {\n trigger: '.o_form_editable .o_field_widget[name=email_from] input',\n extra_trigger: '.o_form_editable .o_field_widget[name=phone] input',\n content: 'Remove the email and the phone',\n run: function (action) {\n action.remove_text(\"\", \".o_form_editable .o_field_widget[name=email_from] input\");\n action.remove_text(\"\", \".o_form_editable .o_field_widget[name=phone] input\");\n },\n }, {\n trigger: '.o_back_button',\n // wait the warning message to be visible\n extra_trigger: '.o_form_sheet_bg .fa-exclamation-triangle:not(.o_invisible_modifier)',\n content: 'Save the lead and exit to kanban',\n run: 'click',\n },{\n trigger: '.o_kanban_renderer',\n }\n ]);\n});\n", "/** @odoo-module */\nimport tour from 'web_tour.tour';\nconst today = moment();\n\ntour.register('crm_forecast', {\n test: true,\n url: \"/web\",\n}, [\n tour.stepUtils.showAppsMenuItem(),\n {\n trigger: \".o_app[data-menu-xmlid='crm.crm_menu_root']\",\n content: \"open crm app\",\n }, {\n trigger: '.dropdown-toggle[data-menu-xmlid=\"crm.crm_menu_report\"]',\n content: 'Open Reporting menu',\n run: 'click',\n }, {\n trigger: '.dropdown-item[data-menu-xmlid=\"crm.crm_menu_forecast\"]',\n content: 'Open Forecast menu',\n run: 'click',\n }, {\n trigger: '.o_column_quick_create:contains(Add next month)',\n content: 'Wait page loading'\n }, {\n trigger: \".o-kanban-button-new\",\n content: \"click create\",\n run: 'click',\n }, {\n trigger: \".o_field_widget[name=name] input\",\n content: \"complete name\",\n run: \"text Test Opportunity 1\",\n }, {\n trigger: \".o_field_widget[name=expected_revenue] input\",\n content: \"complete expected revenue\",\n run: \"text 999999\",\n }, {\n trigger: \"button.o_kanban_edit\",\n content: \"edit lead\",\n }, {\n trigger: \"div[name=date_deadline] input\",\n content: \"complete expected closing\",\n run: `text ${today.format(\"MM/DD/YYYY\")}`,\n }, {\n trigger: \"div[name=date_deadline] input\",\n content: \"click to make the datepicker disappear\",\n run: \"click\"\n }, {\n trigger: \"body:not(:has(div.bootstrap-datetimepicker-widget))\",\n content: \"wait for date_picker to disappear\",\n run: function () {},\n }, {\n trigger: '.o_back_button',\n content: 'navigate back to the kanban view',\n position: \"bottom\",\n run: \"click\"\n }, {\n trigger: \".o_kanban_record .o_kanban_record_title:contains('Test Opportunity 1')\",\n content: \"move to the next month\",\n run: function (actions) {\n const undefined_groups = $('.o_column_title:contains(\"None\")').length;\n actions.drag_and_drop_native(`.o_opportunity_kanban .o_kanban_group:eq(${1 + undefined_groups})`, this.$anchor);\n },\n }, {\n trigger: \".o_kanban_record .o_kanban_record_title:contains('Test Opportunity 1')\",\n content: \"edit lead\",\n run: \"click\"\n }, {\n trigger: \".o_field_widget[name=date_deadline] input\",\n content: \"complete expected closing\",\n run: function (actions) {\n actions.text(`text ${moment(today).add(5, 'months').startOf('month').subtract(1, 'days').format(\"MM/DD/YYYY\")}`, this.$anchor);\n this.$anchor[0].dispatchEvent(new KeyboardEvent(\"keydown\", { bubbles: true, key: \"Escape\" }));\n },\n }, {\n trigger: \"body:not(:has(div.bootstrap-datetimepicker-widget))\",\n content: \"wait for date_picker to disappear\",\n run: function () {},\n }, {\n trigger: \".o_field_widget[name=probability] input\",\n content: \"max out probability\",\n run: \"text 100\"\n }, {\n trigger: '.o_back_button',\n content: 'navigate back to the kanban view',\n position: \"bottom\",\n run: \"click\"\n }, {\n trigger: '.o_kanban_add_column',\n content: \"add next month\",\n run: \"click\"\n }, {\n trigger: \".o_kanban_record:contains('Test Opportunity 1'):contains('Won')\",\n content: \"assert that the opportunity has the Won banner\",\n run: function () {},\n }\n]);\n", "/** @odoo-module **/\n\n import tour from 'web_tour.tour';\n\n tour.register('crm_rainbowman', {\n test: true,\n url: \"/web\",\n }, [\n tour.stepUtils.showAppsMenuItem(),\n {\n trigger: \".o_app[data-menu-xmlid='crm.crm_menu_root']\",\n content: \"open crm app\",\n }, {\n trigger: \".o-kanban-button-new\",\n content: \"click create\",\n }, {\n trigger: \".o_field_widget[name=name] input\",\n content: \"complete name\",\n run: \"text Test Lead 1\",\n }, {\n trigger: \".o_field_widget[name=expected_revenue] input\",\n content: \"complete expected revenue\",\n run: \"text 999999997\",\n }, {\n trigger: \"button.o_kanban_add\",\n content: \"create lead\",\n }, {\n trigger: \".o_kanban_record .o_kanban_record_title:contains('Test Lead 1')\",\n content: \"move to won stage\",\n run: \"drag_and_drop_native .o_opportunity_kanban .o_kanban_group:has(.o_column_title:contains('Won')) \"\n }, {\n trigger: \".o_reward_rainbow\",\n extra_trigger: \".o_reward_rainbow\",\n run: function () {} // check rainbowman is properly displayed\n }, {\n // This step and the following simulates the fact that after drag and drop,\n // from the previous steps, a click event is triggered on the window element,\n // which closes the currently shown .o_kanban_quick_create.\n trigger: \".o_kanban_renderer\",\n }, {\n trigger: \".o_kanban_renderer:not(:has(.o_kanban_quick_create))\",\n run() {},\n }, {\n trigger: \".o-kanban-button-new\",\n content: \"create second lead\",\n }, {\n trigger: \".o_field_widget[name=name] input\",\n content: \"complete name\",\n run: \"text Test Lead 2\",\n }, {\n trigger: \".o_field_widget[name=expected_revenue] input\",\n content: \"complete expected revenue\",\n run: \"text 999999998\",\n }, {\n trigger: \"button.o_kanban_add\",\n content: \"create lead\",\n }, {\n trigger: \".o_kanban_record .o_kanban_record_title:contains('Test Lead 2')\",\n run: function () {} // wait for the record to be properly created\n }, {\n // move first test back to new stage to be able to test rainbowman a second time\n trigger: \".o_kanban_record .o_kanban_record_title:contains('Test Lead 1')\",\n content: \"move back to new stage\",\n run: \"drag_and_drop .o_opportunity_kanban .o_kanban_group:eq(0) \"\n }, {\n trigger: \".o_kanban_record .o_kanban_record_title:contains('Test Lead 2')\",\n content: \"click on second lead\",\n }, {\n trigger: \".o_statusbar_status button[data-value='4']\",\n content: \"move lead to won stage\",\n },\n ...tour.stepUtils.saveForm(),\n {\n trigger: \".o_statusbar_status button[data-value='1']\",\n extra_trigger: \".o_reward_rainbow\",\n content: \"move lead to previous stage & rainbowman appears\",\n }, {\n trigger: \"button[name=action_set_won_rainbowman]\",\n content: \"click button mark won\",\n },\n ...tour.stepUtils.saveForm(),\n {\n trigger: \".o_menu_brand\",\n extra_trigger: \".o_reward_rainbow\",\n content: \"last rainbowman appears\",\n }\n ]);\n", "/** @odoo-module */\n\nimport tour from 'web_tour.tour';\n\ntour.register('personal_stage_tour', {\n test: true,\n url: '/web',\n},\n[tour.stepUtils.showAppsMenuItem(), {\n trigger: '.o_app[data-menu-xmlid=\"project.menu_main_pm\"]',\n}, {\n content: \"Open Pig Project\",\n trigger: '.o_kanban_record:contains(\"Pig\")',\n}, {\n // Default is grouped by stage, user should not be able to create/edit a column\n content: \"Check that there is no create column\",\n trigger: \"body:not(.o_column_quick_create)\",\n run: function () {},\n}, {\n content: \"Check that there is no create column\",\n trigger: \"body:not(.o_column_edit)\",\n run: function () {},\n}, {\n content: \"Check that there is no create column\",\n trigger: \"body:not(.o_column_delete)\",\n run: function () {},\n}, {\n content: \"Go to my tasks\", // My tasks is grouped by personal stage by default\n trigger: 'a[data-menu-xmlid=\"project.menu_project_management\"]',\n}, {\n content: \"Check that we can create a new stage\",\n trigger: '.o_column_quick_create .o_quick_create_folded'\n}, {\n content: \"Create a new personal stage\",\n trigger: 'input.form-control.o_input',\n run: 'text Never',\n}, {\n content: \"Confirm create\",\n trigger: '.o_kanban_add',\n}, {\n content: \"Check that column exists\",\n trigger: '.o_kanban_header:contains(\"Never\")',\n run: function () {},\n}, {\n content: 'Open column edit dropdown',\n trigger: '.o_kanban_header:eq(0)',\n run: function () {\n document.querySelector('.o_kanban_config.dropdown .dropdown-toggle').dispatchEvent(new Event('click'));\n },\n}, {\n content: \"Try editing inbox\",\n trigger: \".dropdown-item.o_column_edit\",\n}, {\n content: \"Change title\",\n trigger: 'div.o_field_char[name=\"name\"] input',\n run: 'text (Todo)',\n}, {\n content: \"Save changes\",\n trigger: '.btn-primary:contains(\"Save\")',\n}, {\n content: \"Check that column was updated\",\n trigger: '.o_kanban_header:contains(\"Todo\")',\n}]);\n", "/** @odoo-module */\n\nimport tour from 'web_tour.tour';\n\ntour.register('burndown_chart_tour', {\n test: true,\n url: '/web',\n},\n[tour.stepUtils.showAppsMenuItem(), {\n trigger: '.o_app[data-menu-xmlid=\"project.menu_main_pm\"]',\n}, {\n content: 'Open \"Burndown Chart Test\" project menu',\n trigger: '.o_kanban_record:contains(\"Burndown Chart Test\") .o_kanban_manage_toggle_button',\n}, {\n content: `Open \"Burndown Chart Test\" project's \"Burndown Chart\" view`,\n trigger: '.o_kanban_record:contains(\"Burndown Chart Test\") .o_kanban_manage_reporting div[role=\"menuitem\"] a:contains(\"Burndown Chart\")',\n}, {\n content: 'The sort buttons are not rendered',\n trigger: '.o_cp_bottom_left:not(:has(.btn-group[role=toolbar][aria-label=\"Sort graph\"]))',\n extra_trigger: '.o_graph_renderer',\n}, {\n content: 'Remove the project search \"Burndown Chart Test\"',\n trigger: '.o_searchview_facet:contains(\"Burndown Chart Test\") .o_facet_remove',\n}, {\n content: 'Search Burndown Chart',\n trigger: 'input.o_searchview_input',\n run: `text Burndown`,\n}, {\n content: 'Validate search',\n trigger: '.o_searchview_autocomplete .o_menu_item:contains(\"Project\")',\n}, {\n content: 'Remove the group by \"Date: Month > Stage\"',\n trigger: '.o_searchview_facet:contains(\"Date: Month\") .o_facet_remove',\n}, {\n content: 'A \"The Burndown Chart must be grouped by Date and Stage\" notification is shown when trying to remove the group by \"Date: Month > Stage\"',\n trigger: '.o_notification_manager .o_notification:contains(\"The Burndown Chart must be grouped by Date and Stage\") button.o_notification_close',\n}, {\n content: 'Open the group by menu',\n trigger: '.o_group_by_menu button',\n}, {\n content: 'The Stage group menu item is invisible',\n trigger: '.o_group_by_menu:not(:has(.o_menu_item:contains(\"Stage\")))',\n}, {\n content: 'Open the Date group by sub menu',\n trigger: '.o_group_by_menu button.o_menu_item:contains(\"Date\")',\n run: function () {\n this.$anchor[0].dispatchEvent(new Event('mouseenter'));\n },\n}, {\n content: 'Click on the selected Date sub menu',\n trigger: '.o_group_by_menu button.o_menu_item:contains(\"Date\") + * .dropdown-item.selected',\n run: function () {\n this.$anchor[0].dispatchEvent(new Event('click'));\n },\n}, {\n content: 'A \"The Burndown Chart must be grouped by Date\" notification is shown when trying to remove the group by \"Date: Month > Stage\"',\n trigger: '.o_notification_manager .o_notification:contains(\"The Burndown Chart must be grouped by Date\") button.o_notification_close',\n}, {\n content: 'Open the filter menu',\n trigger: '.o_filter_menu button',\n}, {\n content: 'Open the Date filter sub menu',\n trigger: '.o_filter_menu button.o_menu_item:contains(\"Date\")',\n run: function () {\n this.$anchor[0].dispatchEvent(new Event('mouseenter'));\n },\n}, {\n content: 'Click on the first Date filter sub menu',\n trigger: '.o_filter_menu .o_menu_item:contains(\"Date\") + * .dropdown-item:first-child',\n run: function () {\n this.$anchor[0].dispatchEvent(new Event('click'));\n },\n}, {\n content: 'Close the Date filter menu',\n trigger: '.o_graph_renderer',\n}, {\n content: 'The comparison menu is not rendered',\n trigger: '.o_search_options:not(:has(.o_comparison_menu))',\n}]);\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\n\nconst projectSharingSteps = [...tour.stepUtils.goToAppSteps(\"project.menu_main_pm\", 'Go to the Project App.'), {\n trigger: '.oe_kanban_global_click :contains(\"Project Sharing\") button.o_dropdown_kanban',\n content: 'Open the project dropdown.'\n}, {\n trigger: '.o_kanban_record:contains(\"Project Sharing\") .dropdown-menu a:contains(\"Share\")',\n content: 'Start editing the project.',\n}, {\n trigger: 'div.o_field_radio[name=\"access_mode\"] div.o_radio_item > input[data-value=\"edit\"]',\n content: 'Select \"Edit\" as Access mode in the \"Share Project\" wizard.',\n}, {\n trigger: '.o_field_many2many_tags_email[name=partner_ids]',\n content: 'Select the user portal as collaborator to the \"Project Sharing\" project.',\n run: function (actions) {\n actions.text('Georges', this.$anchor.find('input'));\n },\n}, {\n trigger: '.ui-autocomplete a.dropdown-item:contains(\"Georges\")',\n in_modal: false,\n}, {\n trigger: 'footer > button[name=\"action_send_mail\"]',\n content: 'Confirm the project sharing with this portal user.',\n}, {\n trigger: '.o_web_client',\n content: 'Go to project portal view to select the \"Project Sharing\" project',\n run: function () {\n window.location.href = window.location.origin + '/my/projects';\n },\n}, {\n id: 'project_sharing_feature',\n trigger: 'table > tbody > tr a:has(span:contains(Project Sharing))',\n content: 'Select \"Project Sharing\" project to go to project sharing feature for this project.',\n}, {\n trigger: '.o_project_sharing',\n content: 'Wait the project sharing feature be loaded',\n run: function () {},\n}, {\n trigger: 'button.o-kanban-button-new',\n content: 'Click \"Create\" button',\n run: 'click',\n}, {\n trigger: '.o_kanban_quick_create .o_field_widget[name=\"name\"] input',\n content: 'Create Task',\n run: 'text Test Create Task',\n}, {\n trigger: '.o_kanban_quick_create .o_kanban_edit',\n content: 'Go to the form view of this new task',\n}, {\n trigger: 'div[name=\"stage_id\"] div.o_statusbar_status button[aria-checked=\"false\"]:contains(Done)',\n content: 'Change the stage of the task.',\n}, {\n trigger: '.o_portal_chatter_composer_input .o_portal_chatter_composer_body textarea',\n content: 'Write a message in the chatter of the task',\n run: 'text I create a new task for testing purpose.',\n}, {\n trigger: '.o_portal_chatter_composer_input .o_portal_chatter_composer_body button[name=\"send_message\"]',\n content: 'Send the message',\n}, {\n trigger: 'ol.breadcrumb > li.o_back_button > a:contains(Project Sharing)',\n content: 'Go back to the kanban view',\n}, {\n trigger: '.o_filter_menu > button',\n content: 'click on filter menu in the search view',\n}, {\n trigger: '.o_filter_menu > .dropdown-menu > .dropdown-item:first-child',\n content: 'click on the first item in the filter menu',\n}, {\n trigger: '.o_group_by_menu > button',\n content: 'click on group by menu in the search view',\n}, {\n trigger: '.o_group_by_menu > .dropdown-menu > .dropdown-item:first-child',\n content: 'click on the first item in the group by menu',\n}, {\n trigger: '.o_favorite_menu > button',\n content: 'click on the favorite menu in the search view',\n}, {\n trigger: '.o_favorite_menu .o_add_favorite > button',\n content: 'click to \"save current search\" button in favorite menu',\n}, {\n trigger: '.o_filter_menu > button',\n content: 'click on filter menu in the search view',\n}, {\n trigger: '.o_filter_menu > .dropdown-menu > .dropdown-item:first-child',\n content: 'click on the first item in the filter menu',\n}, {\n trigger: '.o_group_by_menu > button',\n content: 'click on group by menu in the search view',\n}, {\n trigger: '.o_group_by_menu > .dropdown-menu > .dropdown-item:first-child',\n content: 'click on the first item in the group by menu',\n}, {\n trigger: '.o_favorite_menu > button',\n content: 'click on the favorite menu in the search view',\n}, {\n trigger: '.o_favorite_menu .o_add_favorite > button',\n content: 'click to \"save current search\" button in favorite menu',\n}, {\n trigger: 'button.o_switch_view.o_list',\n content: 'Go to the list view',\n}];\n\ntour.register('project_sharing_tour', {\n test: true,\n url: '/web',\n}, projectSharingSteps);\n\n// The begining of the project sharing feature\nconst projectSharingStepIndex = projectSharingSteps.findIndex(s => s.id && s.id === 'project_sharing_feature');\ntour.register('portal_project_sharing_tour', {\n test: true,\n url: '/my/projects',\n}, projectSharingSteps.slice(projectSharingStepIndex, projectSharingSteps.length));\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\n\nfunction changeFilter(filterName) {\n return [\n {\n trigger: '.o_favorite_menu button:has(i.fa-star)',\n content: 'click on the favorite menu',\n },\n {\n trigger: `.o_favorite_menu .dropdown-item span:contains(\"${filterName}\")`,\n },\n {\n trigger: '.o_group_by_menu button:has(i.oi-group)',\n content: 'click on the groupby menu',\n run: function (actions) {\n this.$anchor[0].dispatchEvent(new Event('mouseenter'));\n },\n },\n {\n trigger: '.o_group_by_menu span:contains(\"Stage\")',\n content: 'click on the stage gb',\n },\n ];\n}\n\ntour.register('project_tags_filter_tour',\n {\n test: true,\n url: '/web',\n },\n [\n tour.stepUtils.showAppsMenuItem(),\n {\n trigger: '.o_app[data-menu-xmlid=\"project.menu_main_pm\"]',\n }, ...changeFilter(\"Corkscrew tail tag filter\"),\n {\n trigger: '.o_kanban_group:has(.o_kanban_header:has(span:contains(\"pig\"))) .o_kanban_record:has(span:contains(\"Pigs\"))',\n extra_trigger: '.o_kanban_group:has(.o_kanban_header:has(span:contains(\"goat\"))):not(:has(.o_kanban_record))',\n content: 'check that the corkscrew tail filter has taken effect',\n run: () => {},\n }, ...changeFilter(\"horned tag filter\"),\n {\n trigger: '.o_kanban_group:has(.o_kanban_header:has(span:contains(\"goat\"))) .o_kanban_record:has(span:contains(\"Goats\"))',\n extra_trigger: '.o_kanban_group:has(.o_kanban_header:has(span:contains(\"pig\"))):not(:has(.o_kanban_record))',\n content: 'check that the horned filter has taken effect',\n run: () => {},\n }, ...changeFilter(\"4 Legged tag filter\"),\n {\n trigger: '.o_kanban_group:has(.o_kanban_header:has(span:contains(\"goat\"))) .o_kanban_record:has(span:contains(\"Goats\"))',\n extra_trigger: '.o_kanban_group:has(.o_kanban_header:has(span:contains(\"pig\"))) .o_kanban_record:has(span:contains(\"Pigs\"))',\n content: 'check that the 4 legged filter has taken effect',\n run: () => {},\n },\n]);\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\n\nfunction openProjectUpdateAndReturnToTasks(view, viewClass) {\n const legacyViewClass = viewClass.replace(\"o_\", \"o_legacy_\");\n return [{\n trigger: '.o_project_updates_breadcrumb',\n content: 'Open Project Update from view : ' + view,\n extra_trigger: `.${viewClass}, .${legacyViewClass}`,\n }, {\n trigger: \".o-kanban-button-new\",\n content: \"Create a new update from project task view : \" + view,\n extra_trigger: '.o_pupdate_kanban',\n }, {\n trigger: \"button.o_form_button_cancel\",\n content: \"Discard project update from project task view : \" + view,\n }, {\n trigger: \".o_switch_view.o_list\",\n content: \"Go to list of project update from view \" + view,\n }, {\n trigger: '.o_back_button',\n content: 'Go back to the task view : ' + view,\n // extra_trigger: '.o_list_view, .o_legacy_list_view', // FIXME: [XBO] uncomment it when the sample data will be displayed after discarding the creation of a project update record.\n },\n ];\n}\n\ntour.register('project_update_tour', {\n test: true,\n url: '/web',\n},\n[tour.stepUtils.showAppsMenuItem(), {\n trigger: '.o_app[data-menu-xmlid=\"project.menu_main_pm\"]',\n}, {\n trigger: '.o-kanban-button-new',\n extra_trigger: '.o_project_kanban',\n width: 200,\n}, {\n trigger: '.o_project_name input',\n run: 'text New Project'\n}, {\n trigger: '.o_open_tasks',\n run: function (actions) {\n actions.auto('.modal:visible .btn.btn-primary');\n },\n}, {\n trigger: \".o_kanban_project_tasks .o_column_quick_create .input-group\",\n run: function (actions) {\n actions.text(\"New\", this.$anchor.find(\"input\"));\n },\n}, {\n trigger: \".o_kanban_project_tasks .o_column_quick_create .o_kanban_add\",\n auto: true,\n}, {\n trigger: \".o_kanban_project_tasks .o_column_quick_create .input-group\",\n extra_trigger: '.o_kanban_group',\n run: function (actions) {\n actions.text(\"Done\", this.$anchor.find(\"input\"));\n },\n}, {\n trigger: \".o_kanban_project_tasks .o_column_quick_create .o_kanban_add\",\n auto: true,\n}, {\n trigger: '.o-kanban-button-new',\n extra_trigger: '.o_kanban_group:eq(0)'\n}, {\n trigger: '.o_kanban_quick_create div.o_field_char[name=name] input',\n extra_trigger: '.o_kanban_project_tasks',\n run: 'text New task'\n}, {\n trigger: '.o_kanban_quick_create .o_kanban_add',\n extra_trigger: '.o_kanban_project_tasks'\n}, {\n trigger: '.o-kanban-button-new',\n extra_trigger: '.o_kanban_group:eq(0)'\n}, {\n trigger: '.o_kanban_quick_create div.o_field_char[name=name] input',\n extra_trigger: '.o_kanban_project_tasks',\n run: 'text Second task'\n}, {\n trigger: '.o_kanban_quick_create .o_kanban_add',\n extra_trigger: '.o_kanban_project_tasks'\n}, {\n trigger: '.o_kanban_group:nth-child(2) .o_kanban_header',\n run: function () {\n document.querySelector('.o_kanban_group:nth-child(2) .o_kanban_config.dropdown .dropdown-toggle').dispatchEvent(new Event('click'));\n }\n}, {\n trigger: \".dropdown-item.o_column_edit\",\n}, {\n trigger: \".o_field_widget[name=fold] input\",\n}, {\n trigger: \".modal-footer button\",\n}, {\n trigger: \".o_kanban_record .oe_kanban_content\",\n extra_trigger: '.o_kanban_project_tasks',\n run: \"drag_and_drop .o_kanban_group:eq(1) \",\n}, {\n trigger: \".o_project_updates_breadcrumb\",\n content: 'Open Updates'\n}, {\n trigger: \".o_add_milestone a\",\n content: \"Add a first milestone\"\n}, {\n trigger: \"div.o_field_widget[name=name] input\",\n run: 'text New milestone'\n}, {\n trigger: \"div[name=deadline] .datetimepicker-input\",\n run: 'text 12/12/2099'\n}, {\n trigger: \".modal-footer .o_form_button_save\"\n}, {\n trigger: \".o_add_milestone a\",\n}, {\n trigger: \"div.o_field_widget[name=name] input\",\n run: 'text Second milestone'\n}, {\n trigger: \"div[name=deadline] .datetimepicker-input\",\n run: 'text 12/12/2022'\n}, {\n trigger: \".modal-footer .o_form_button_save\"\n}, {\n trigger: \".o_rightpanel_milestone:eq(1) .o_milestone_detail\",\n}, {\n trigger: \"div[name=deadline] .datetimepicker-input\",\n run: 'text 12/12/2100'\n}, {\n trigger: \".modal-footer .o_form_button_save\"\n}, {\n trigger: \".o-kanban-button-new\",\n content: \"Create a new update\"\n}, {\n trigger: \"div.o_field_widget[name=name] input\",\n run: 'text New update'\n}, {\n trigger: \".o_form_button_save\"\n}, {\n trigger: \".o_field_widget[name='description'] h1:contains('Activities')\",\n run: function () {},\n}, {\n trigger: \".o_field_widget[name='description'] h3:contains('Milestones')\",\n run: function () {},\n}, {\n trigger: \".o_field_widget[name='description'] div[name='milestone'] ul li:contains('(12/12/2099 => 12/12/2100)')\",\n run: function () {},\n}, {\n trigger: \".o_field_widget[name='description'] div[name='milestone'] ul li:contains('(due 12/12/2022)')\",\n run: function () {},\n}, {\n trigger: \".o_field_widget[name='description'] div[name='milestone'] ul li:contains('(due 12/12/2100)')\",\n run: function () {},\n}, {\n trigger: '.o_back_button',\n content: 'Go back to the kanban view the project',\n}, {\n trigger: '.o_switch_view.o_list',\n content: 'Open List View of Project Updates',\n}, {\n trigger: '.o_back_button',\n content: 'Go back to the kanban view the project',\n extra_trigger: '.o_list_view, .o_legacy_list_view',\n}, {\n trigger: '.o_switch_view.o_graph',\n content: 'Open Graph View of Tasks',\n}, ...openProjectUpdateAndReturnToTasks(\"Graph\", \"o_graph_view\"), {\n trigger: '.o_switch_view.o_list',\n content: 'Open List View of Tasks',\n extra_trigger: '.o_graph_view',\n}, ...openProjectUpdateAndReturnToTasks(\"List\", \"o_list_view\"), {\n trigger: '.o_switch_view.o_pivot',\n content: 'Open Pivot View of Tasks',\n}, ...openProjectUpdateAndReturnToTasks(\"Pivot\", \"o_pivot_view\"), {\n trigger: '.o_switch_view.o_calendar',\n content: 'Open Calendar View of Tasks',\n}, ...openProjectUpdateAndReturnToTasks(\"Calendar\", \"o_calendar_view\"), {\n trigger: '.o_switch_view.o_activity',\n content: 'Open Activity View of Tasks',\n}, ...openProjectUpdateAndReturnToTasks(\"Activity\", \"o_activity_view\"),\n]);\n", "odoo.define(\"website_payment.tour.donation_snippet_edition\", function (require) {\n \"use strict\";\n\n const tour = require(\"web_tour.tour\");\n const wTourUtils = require(\"website.tour_utils\");\n\n tour.register(\"donation_snippet_edition\", {\n test: true,\n url: \"/?enable_editor=1\",\n }, [\n wTourUtils.dragNDrop({\n id: \"s_donation\",\n name: \"Donation\"\n }),\n ...wTourUtils.clickOnSave(),\n // -- Testing the minimum amount --\n {\n content: \"Enter a negative custom amount, testing the minimum amount\",\n trigger: \"iframe #s_donation_amount_input\",\n run: \"text -5\",\n },\n {\n content: \"Donate with custom amount set\",\n trigger: \"iframe .s_donation_donate_btn\",\n },\n {\n content: \"Check if alert-danger element exists\",\n trigger: \"iframe p.alert-danger\",\n },\n // -- End of testing the minimum amount --\n {\n content: \"Enter a custom amount\",\n trigger: \"iframe #s_donation_amount_input\",\n run: \"text 55\",\n },\n {\n content: \"Donate with custom amount set\",\n trigger: \"iframe .s_donation_donate_btn\",\n },\n {\n content: \"Check if custom amount radio input is selected\",\n trigger: \"iframe input#other_amount:checked\",\n run: () => {}, // This is a check\n },\n {\n content: \"Check if custom amount radio input has value 55\",\n trigger: 'iframe input#other_amount[value=\"55.0\"]',\n run: () => {}, // This is a check\n },\n {\n content: \"Select the amount of 25\",\n trigger: \"iframe input#amount_1\",\n },\n {\n content: \"Verify that amount_1 is checked\",\n trigger: \"iframe input#amount_1:checked\",\n run: () => {}, // This is a check\n },\n {\n content: \"Verify that other_amount is not checked\",\n trigger: \"iframe input#other_amount:not(:checked)\",\n run: () => {}, // This is a check\n },\n {\n content: \"Change custom amount to 67\",\n trigger: \"iframe input#other_amount_value\",\n run: \"text 67\",\n },\n {\n content: \"Select the custom amount radio button\",\n trigger: \"iframe input#other_amount\",\n },\n {\n content: \"Submit the donation form\",\n trigger: \"iframe button[name='o_payment_submit_button']\",\n },\n {\n content: \"Verify that the amount displayed is 67\",\n trigger: 'iframe span.oe_currency_value:contains(\"67.00\")',\n run: () => {}, // This is a check\n timeout: 10000 // Make sure the payment process animation is finished\n },\n ]);\n});\n", "/** @odoo-module **/\n\nimport wsTourUtils from 'website_sale.tour_utils';\nimport wTourUtils from 'website.tour_utils';\n\nfunction editAddToCartSnippet() {\n return [\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n wTourUtils.clickOnSnippet({id: 's_add_to_cart'})\n ]\n}\n\nwTourUtils.registerWebsitePreviewTour('add_to_cart_snippet_tour', {\n url: '/',\n edition: true,\n test: true,\n },\n [\n wTourUtils.dragNDrop({name: 'Add to Cart Button'}),\n\n // Basic product with no variants\n wTourUtils.clickOnSnippet({id: 's_add_to_cart'}),\n ...wTourUtils.selectElementInWeSelectWidget('product_template_picker_opt', 'Product No Variant', true),\n ...wTourUtils.clickOnSave(),\n wTourUtils.clickOnElement('add to cart button', 'iframe .s_add_to_cart_btn'),\n {\n trigger: \"iframe nav li.o_wsale_my_cart sup:contains(1)\",\n run: () => null,\n },\n // Product with 2 variants with visitor choice (will open modal)\n ...editAddToCartSnippet(),\n ...wTourUtils.selectElementInWeSelectWidget('product_template_picker_opt', 'Product Yes Variant 1', true),\n ...wTourUtils.clickOnSave(),\n wTourUtils.clickOnElement('add to cart button', 'iframe .s_add_to_cart_btn'),\n wTourUtils.clickOnElement('continue shopping', 'iframe span:contains(Continue Shopping)'),\n {\n trigger: \"body:not(:has(.modal))\",\n run: () => null,\n },\n {\n trigger: \"iframe nav li.o_wsale_my_cart sup:contains(2)\",\n run: () => null,\n },\n\n // Product with 2 variants with a variant selected\n ...editAddToCartSnippet(),\n ...wTourUtils.selectElementInWeSelectWidget('product_template_picker_opt', 'Product Yes Variant 2', true),\n {\n run: () => null,\n trigger:\n `we-select[data-name=product_variant_picker_opt] we-toggler:contains(\"Visitor's Choice\")`,\n },\n ...wTourUtils.selectElementInWeSelectWidget('product_variant_picker_opt', 'Product Yes Variant 2 (Pink)'),\n ...wTourUtils.clickOnSave(),\n wTourUtils.clickOnElement('add to cart button', 'iframe .s_add_to_cart_btn'),\n {\n trigger: \"iframe nav li.o_wsale_my_cart sup:contains(3)\",\n run: () => null,\n },\n\n // Basic product with no variants and action=buy now\n ...editAddToCartSnippet(),\n ...wTourUtils.selectElementInWeSelectWidget('product_template_picker_opt', 'Product No Variant', true),\n {\n run: () => null,\n trigger:\n `we-select[data-name=action_picker_opt] we-toggler:contains(\"Add to Cart\")`,\n },\n ...wTourUtils.selectElementInWeSelectWidget('action_picker_opt', 'Buy Now'),\n ...wTourUtils.clickOnSave(),\n wTourUtils.clickOnElement('add to cart button', 'iframe .s_add_to_cart_btn'),\n {\n // wait for the page to load, as the next check was sometimes too fast\n content: \"Wait for the redirection to the payment page\",\n trigger: \"div#oe_structure_website_sale_payment_1\",\n run: () => null,\n },\n wTourUtils.assertPathName('/shop/payment', 'button[name=o_payment_submit_button]'),\n\n wsTourUtils.goToCart({quantity: 4, backend: false}),\n wsTourUtils.assertCartContains({productName: 'Product No Variant'}),\n wsTourUtils.assertCartContains({productName: 'Product Yes Variant 1 (Red)'}),\n wsTourUtils.assertCartContains({productName: 'Product Yes Variant 2 (Pink)'}),\n ],\n);\n", "odoo.define('website_sale.tour', function (require) {\n'use strict';\n\nvar tour = require(\"web_tour.tour\");\nconst tourUtils = require('website_sale.tour_utils');\n\ntour.register('shop_buy_product', {\n test: true,\n url: '/shop',\n},\n [\n {\n content: \"search conference chair\",\n trigger: 'form input[name=\"search\"]',\n run: \"text conference chair\",\n },\n {\n content: \"search conference chair\",\n trigger: 'form:has(input[name=\"search\"]) .oe_search_button',\n },\n {\n content: \"select conference chair\",\n trigger: '.oe_product_cart:first a:contains(\"Conference Chair\")',\n },\n {\n content: \"select Conference Chair Aluminium\",\n extra_trigger: '#product_detail',\n trigger: 'label:contains(Aluminium) input',\n },\n {\n content: \"select Conference Chair Steel\",\n extra_trigger: '#product_detail',\n trigger: 'label:contains(Steel) input',\n },\n {\n id: 'add_cart_step',\n content: \"click on add to cart\",\n extra_trigger: 'label:contains(Steel) input:propChecked',\n trigger: '#product_detail form[action^=\"/shop/cart/update\"] #add_to_cart',\n },\n tourUtils.goToCart(),\n {\n content: \"add suggested\",\n extra_trigger: '#wrap:not(:has(#cart_products:contains(\"Storage Box\")))',\n trigger: '.oe_cart:has(tr:contains(\"Storage Box\")) a:contains(\"Add to Cart\")',\n },\n {\n content: \"add one more\",\n extra_trigger: '#cart_products tr:contains(\"Storage Box\")',\n trigger: '#cart_products tr:contains(\"Steel\") a.js_add_cart_json:eq(1)',\n },\n {\n content: \"remove Storage Box\",\n extra_trigger: '#cart_products tr:contains(\"Steel\") input.js_quantity:propValue(2)',\n trigger: '#cart_products tr:contains(\"Storage Box\") a.js_add_cart_json:first',\n },\n {\n content: \"set one\",\n extra_trigger: '#wrap:not(:has(#cart_products tr:contains(\"Storage Box\")))',\n trigger: '#cart_products input.js_quantity',\n run: 'text 1',\n },\n {\n content: \"go to checkout\",\n extra_trigger: '#cart_products input.js_quantity:propValue(1)',\n trigger: 'a[href*=\"/shop/checkout\"]',\n },\n {\n content: \"select payment\",\n trigger: '#payment_method label:contains(\"Wire Transfer\")',\n },\n {\n content: \"Pay Now\",\n //Either there are multiple payment methods, and one is checked, either there is only one, and therefore there are no radio inputs\n extra_trigger: '#payment_method label:contains(\"Wire Transfer\") input:checked,#payment_method:not(:has(\"input:radio:visible\"))',\n trigger: 'button[name=\"o_payment_submit_button\"]:visible:not(:disabled)',\n },\n {\n content: \"finish\",\n trigger: '.oe_website_sale:contains(\"Please use the following transfer details\")',\n // Leave /shop/confirmation to prevent RPC loop to /shop/payment/get_status.\n // The RPC could be handled in python while the tour is killed (and the session), leading to crashes\n run: function () {\n window.location.href = '/contactus'; // Redirect in JS to avoid the RPC loop (20x1sec)\n },\n timeout: 30000,\n },\n {\n content: \"wait page loaded\",\n trigger: 'h1:contains(\"Contact us\")',\n run: function () {}, // it's a check\n },\n ]\n);\n\n});\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\nimport wTourUtils from 'website.tour_utils';\n\ntour.register('website_sale_cart_popover_tour', {\n test: true,\n url: '/shop',\n },\n [\n {\n content: \"Search for the product\",\n trigger: 'form input[name=\"search\"]',\n run: 'text website_sale_cart_popover_tour_product'\n },\n wTourUtils.clickOnElement('Search', 'form:has(input[name=\"search\"]) .oe_search_button'),\n wTourUtils.clickOnElement('website_sale_cart_popover_tour_product', 'a:contains(website_sale_cart_popover_tour_product)'),\n wTourUtils.clickOnElement('Add to Cart', '#product_detail form[action^=\"/shop/cart/update\"] #add_to_cart'),\n {\n content: \"hover on cart popover\",\n trigger: '#top_menu a[href$=\"/shop/cart\"]',\n run: () => {\n $('#top_menu a[href$=\"/shop/cart\"]').mouseenter();\n },\n },\n {\n content: \"check that popover is visible\",\n trigger: '.mycart-popover:visible',\n run: () => {},\n },\n // Trigger mouseleave to close the popover\n {\n content: \"hover on cart popover\",\n trigger: '#top_menu a[href$=\"/shop/cart\"]',\n run: () => {\n $('#top_menu a[href$=\"/shop/cart\"]').mouseleave();\n },\n },\n // Check that popover is not visible\n {\n content: \"check that popover is not visible\",\n trigger: ':not(.mycart-popover:visible)',\n run: () => {},\n },\n ]\n);\n", "/** @odoo-modules */\n\nimport tour from 'web_tour.tour';\nimport wTourUtils from 'website.tour_utils';\n\n\ntour.register('category_page_and_products_snippet_edition', {\n test: true,\n url: wTourUtils.getClientActionUrl('/shop'),\n}, [\n {\n content: \"Navigate to category\",\n trigger: 'iframe .o_wsale_filmstip > li:contains(\"Test Category\")',\n },\n wTourUtils.clickOnEdit(),\n Object.assign(wTourUtils.dragNDrop({id: 's_dynamic_snippet_products', name: 'Products'}), {\n content: \"Drag and drop the product snippet inside the category area\",\n run: 'drag_and_drop iframe #category_header',\n }),\n {\n content: \"Click on the product snippet to show its options\",\n trigger: 'iframe #category_header .s_dynamic_snippet_products',\n },\n {\n content: \"Open category option dropdown\",\n trigger: 'we-select[data-attribute-name=\"productCategoryId\"] we-toggler',\n },\n {\n content: \"Choose the option to use the current page's category\",\n trigger: 'we-button[data-select-data-attribute=\"current\"]',\n },\n ...wTourUtils.clickOnSave(),\n]);\n\ntour.register('category_page_and_products_snippet_use', {\n test: true,\n url: `/shop`,\n}, [\n {\n content: \"Navigate to category\",\n trigger: '.o_wsale_filmstip > li:contains(\"Test Category\")',\n },\n {\n content: \"Check that the snippet displays the right products\",\n // Wait for at least one shown product\n trigger: '#category_header .s_dynamic_snippet_products:has(.o_carousel_product_img_link)',\n run: function () {\n // Fetch the category's id from the url.\n const productCategoryId = window.location.href.match('/shop/category/test-category-(\\\\d+)')[1]\n const productGridEl = this.$anchor[0].closest('#products_grid');\n const regex = new RegExp(`^/shop/[\\\\w-/]+-(\\\\d+)\\\\?category=${productCategoryId}$`);\n const allPageProductIDs = [...productGridEl.querySelectorAll('.oe_product_image_link')]\n .map(el => el.getAttribute('href').match(regex)[1]);\n\n const $shownProductLinks = this.$anchor.find('.o_carousel_product_img_link');\n const regex2 = new RegExp(`^/shop/[\\\\w-/]+-(\\\\d+)(?:#attr=\\\\d*)?$`);\n for (const shownProductLinkEl of $shownProductLinks) {\n const productID = shownProductLinkEl.getAttribute('href').match(regex2)[1];\n if (!allPageProductIDs.includes(productID)) {\n console.error(`The snippet displays a product (${productID}) which does not belong to the current category (${allPageProductIDs})`);\n }\n }\n },\n },\n]);\n", "odoo.define('website_sale_tour.tour', function (require) {\n 'use strict';\n\n var tour = require(\"web_tour.tour\");\n var rpc = require(\"web.rpc\");\n const tourUtils = require('website_sale.tour_utils');\n\n tour.register('website_sale_tour_1', {\n test: true,\n url: '/shop?search=Storage Box Test',\n }, [\n // Testing b2c with Tax-Excluded Prices\n {\n content: \"Open product page\",\n trigger: '.oe_product_cart a:contains(\"Storage Box Test\")',\n },\n {\n content: \"Add one more storage box\",\n trigger: '.js_add_cart_json:eq(1)',\n },\n {\n content: \"Check b2b Tax-Excluded Prices\",\n trigger: '.product_price .oe_price .oe_currency_value:containsExact(79.00)',\n run: function () {}, // it's a check\n },\n {\n content: \"Click on add to cart\",\n trigger: '#add_to_cart',\n },\n tourUtils.goToCart({quantity: 2}),\n {\n content: \"Check for 2 products in cart and proceed to checkout\",\n extra_trigger: '#cart_products tr:contains(\"Storage Box Test\") input.js_quantity:propValue(2)',\n trigger: 'a[href*=\"/shop/checkout\"]',\n },\n {\n content: \"Check Price b2b subtotal\",\n trigger: 'tr#order_total_untaxed .oe_currency_value:containsExact(158.00)',\n run: function () {}, // it's a check\n },\n {\n content: \"Check Price b2b Sale Tax(15%)\",\n trigger: 'tr#order_total_taxes .oe_currency_value:containsExact(23.70)',\n run: function () {}, // it's a check\n },\n {\n content: \"Check Price b2b Total amount\",\n trigger: 'tr#order_total .oe_currency_value:containsExact(181.70)',\n run: function () {}, // it's a check\n },\n {\n content: \"Fulfill billing address form\",\n trigger: 'select[name=\"country_id\"]',\n run: function () {\n $('input[name=\"name\"]').val('abc');\n $('input[name=\"phone\"]').val('99999999');\n $('input[name=\"email\"]').val('abc@odoo.com');\n $('input[name=\"street\"]').val('SO1 Billing Street, 33');\n $('input[name=\"city\"]').val('SO1BillingCity');\n $('input[name=\"zip\"]').val('10000');\n $('#country_id option:eq(1)').attr('selected', true);\n },\n },\n {\n content: \"Shipping address is not same as billing address\",\n trigger: '#shipping_use_same',\n },\n {\n content: \"Click on next button\",\n trigger: '.oe_cart .btn:contains(\"Next\")',\n },\n {\n content: \"Fulfill shipping address form\",\n trigger: 'select[name=\"country_id\"]',\n extra_trigger: 'h2:contains(\"Shipping Address\")',\n run: function () {\n $('input[name=\"name\"]').val('def');\n $('input[name=\"phone\"]').val('8888888888');\n $('input[name=\"street\"]').val('17, SO1 Shipping Road');\n $('input[name=\"city\"]').val('SO1ShippingCity');\n $('input[name=\"zip\"]').val('10000');\n $('#country_id option:eq(1)').attr('selected', true);\n },\n },\n {\n content: \"Click on next button\",\n trigger: '.oe_cart .btn:contains(\"Next\")',\n },\n {\n content: \"Check selected billing address is same as typed in previous step\",\n trigger: '#shipping_and_billing:contains(SO1 Billing Street, 33):contains(SO1BillingCity):contains(Afghanistan)',\n run: function () {}, // it's a check\n },\n {\n content: \"Check selected shipping address is same as typed in previous step\",\n trigger: '#shipping_and_billing:contains(17, SO1 Shipping Road):contains(SO1ShippingCity):contains(Afghanistan)',\n run: function () {}, // it's a check\n },\n {\n content: \"Click for edit address\",\n trigger: 'a:contains(\"Edit\") i',\n },\n {\n content: \"Click for edit billing address\",\n trigger: '.js_edit_address:first',\n },\n {\n content: \"Change billing address form\",\n trigger: 'select[name=\"country_id\"]',\n extra_trigger: 'h2:contains(\"Your Address\")',\n run: function () {\n $('input[name=\"name\"]').val('abcd');\n $('input[name=\"phone\"]').val('11111111');\n $('input[name=\"street\"]').val('SO1 Billing Street Edited, 33');\n $('input[name=\"city\"]').val('SO1BillingCityEdited');\n },\n },\n {\n content: \"Click on next button\",\n trigger: '.oe_cart .btn:contains(\"Next\")',\n },\n {\n content: \"Confirm Address\",\n trigger: 'a.btn:contains(\"Confirm\")',\n },\n {\n content: \"Check selected billing address is same as typed in previous step\",\n trigger: '#shipping_and_billing:contains(SO1 Billing Street Edited, 33):contains(SO1BillingCityEdited):contains(Afghanistan)',\n run: function () {}, // it's a check\n },\n {\n content: \"Select `Wire Transfer` payment method\",\n trigger: '#payment_method label:contains(\"Wire Transfer\")',\n },\n {\n content: \"Pay Now\",\n // extra_trigger: '#payment_method label:contains(\"Wire Transfer\") input:checked,#payment_method:not(:has(\"input:radio:visible\"))',\n trigger: 'button[name=\"o_payment_submit_button\"]:visible:not(:disabled)',\n },\n {\n content: \"Sign up\",\n trigger: '.oe_cart a:contains(\"Sign Up\")',\n },\n {\n content: \"Submit login\",\n trigger: '.oe_signup_form',\n run: function () {\n $('.oe_signup_form input[name=\"password\"]').val(\"1admin@admin\");\n $('.oe_signup_form input[name=\"confirm_password\"]').val(\"1admin@admin\");\n $('.oe_signup_form').submit();\n },\n },\n {\n content: \"See Quotations\",\n trigger: '.o_portal_docs a:contains(\"Quotations\")',\n },\n // Sign in as admin change config auth_signup -> b2b, sale_show_tax -> total and Logout\n {\n content: \"Open Dropdown for logout\",\n trigger: '#top_menu li.dropdown:visible a:contains(\"abcd\")',\n },\n {\n content: \"Logout\",\n trigger: '#o_logout:contains(\"Logout\")',\n },\n {\n content: \"Sign in as admin\",\n trigger: 'header a[href=\"/web/login\"]',\n },\n {\n content: \"Submit login\",\n trigger: '.oe_login_form',\n run: function () {\n $('.oe_login_form input[name=\"login\"]').val(\"admin\");\n $('.oe_login_form input[name=\"password\"]').val(\"admin\");\n $('.oe_login_form input[name=\"redirect\"]').val(\"/\");\n $('.oe_login_form').submit();\n },\n },\n {\n content: \"Configuration Settings for 'Tax Included' and sign up 'On Invitation'\",\n extra_trigger: '.o_frontend_to_backend_nav', // Check if the user is connected\n trigger: '#wrapwrap',\n run: function () {\n var def1 = rpc.query({\n model: 'res.config.settings',\n method: 'create',\n args: [{\n 'auth_signup_uninvited': 'b2b',\n 'show_line_subtotals_tax_selection': 'tax_included',\n 'group_show_line_subtotals_tax_excluded': false,\n 'group_show_line_subtotals_tax_included': true,\n }],\n });\n var def2 = def1.then(function (resId) {\n return rpc.query({\n model: 'res.config.settings',\n method: 'execute',\n args: [[resId]],\n });\n });\n def2.then(function () {\n window.location.href = '/web/session/logout?redirect=/shop?search=Storage Box Test';\n });\n },\n },\n // Testing b2b with Tax-Included Prices\n {\n content: \"Open product page\",\n trigger: '.oe_product_cart a:contains(\"Storage Box Test\")',\n },\n {\n content: \"Add one more Storage Box Test\",\n trigger: '.js_add_cart_json:eq(1)',\n },\n {\n content: \"Check b2c Tax-Included Prices\",\n trigger: '.product_price .oe_price .oe_currency_value:containsExact(90.85)',\n run: function () {}, // it's a check\n },\n {\n content: \"Click on add to cart\",\n trigger: '#add_to_cart',\n },\n tourUtils.goToCart({quantity: 2}),\n {\n content: \"Check for 2 products in cart and proceed to checkout\",\n extra_trigger: '#cart_products tr:contains(\"Storage Box Test\") input.js_quantity:propValue(2)',\n trigger: 'a[href*=\"/shop/checkout\"]',\n },\n {\n content: \"Check Price b2c total\",\n trigger: 'tr#order_total_untaxed .oe_currency_value:containsExact(158.00)',\n run: function () {}, // it's a check\n },\n {\n content: \"Check Price b2c Sale Tax(15%)\",\n trigger: 'tr#order_total_taxes .oe_currency_value:containsExact(23.70)',\n run: function () {}, // it's a check\n },\n {\n content: \"Check Price b2c Total amount\",\n trigger: 'tr#order_total .oe_currency_value:containsExact(181.70)',\n run: function () {}, // it's a check\n },\n {\n content: \"Click on Sign in Button\",\n trigger: '.oe_cart a:contains(\" Sign in\")',\n },\n {\n content: \"Submit login\",\n trigger: '.oe_login_form',\n run: function () {\n $('.oe_login_form input[name=\"login\"]').val(\"abc@odoo.com\");\n $('.oe_login_form input[name=\"password\"]').val(\"1admin@admin\");\n $('.oe_login_form').submit();\n },\n },\n {\n content: \"Add new shipping address\",\n trigger: '.one_kanban form[action^=\"/shop/address\"] .btn',\n },\n {\n content: \"Fulfill shipping address form\",\n trigger: 'select[name=\"country_id\"]',\n run: function () {\n $('input[name=\"name\"]').val('ghi');\n $('input[name=\"phone\"]').val('7777777777');\n $('input[name=\"street\"]').val('SO2New Shipping Street, 5');\n $('input[name=\"city\"]').val('SO2NewShipping');\n $('input[name=\"zip\"]').val('1200');\n $('#country_id option:eq(1)').attr('selected', true);\n },\n },\n {\n content: \"Click on next button\",\n trigger: '.oe_cart .btn:contains(\"Next\")',\n },\n {\n content: \"Select `Wire Transfer` payment method\",\n trigger: '#payment_method label:contains(\"Wire Transfer\")',\n },\n {\n content: \"Pay Now\",\n extra_trigger: '#payment_method label:contains(\"Wire Transfer\") input:checked,#payment_method:not(:has(\"input:radio:visible\"))',\n trigger: 'button[name=\"o_payment_submit_button\"]:visible:not(:disabled)',\n },\n {\n content: \"Open Dropdown for See quotation\",\n extra_trigger: '.oe_cart .oe_website_sale_tx_status',\n trigger: '#top_menu li.dropdown:visible a:contains(\"abc\")',\n },\n {\n content: \"My account\",\n extra_trigger: '#top_menu li.dropdown .js_usermenu.show',\n trigger: '#top_menu .dropdown-menu a[href=\"/my/home\"]:visible',\n },\n {\n content: \"See Quotations\",\n trigger: '.o_portal_docs a:contains(\"Quotations\") .badge:containsExact(2)',\n },\n\n // enable extra step on website checkout and check extra step on checkout process\n {\n content: \"Open Dropdown for logout\",\n trigger: '#top_menu li.dropdown:visible a:contains(\"abc\")',\n },\n {\n content: \"Logout\",\n trigger: '#o_logout:contains(\"Logout\")',\n },\n {\n content: \"Sign in as admin\",\n trigger: 'header a[href=\"/web/login\"]',\n },\n {\n content: \"Submit login\",\n trigger: '.oe_login_form',\n run: function () {\n $('.oe_login_form input[name=\"login\"]').val(\"admin\");\n $('.oe_login_form input[name=\"password\"]').val(\"admin\");\n $('.oe_login_form input[name=\"redirect\"]').val(\"/shop/cart\");\n $('.oe_login_form').submit();\n },\n }]);\n\n tour.register('website_sale_tour_2', {\n test: true,\n url: '/shop/cart',\n }, [\n {\n content: \"Open Dropdown for logout\",\n extra_trigger: '.progress-wizard-step:contains(\"Extra Info\")',\n trigger: '#top_menu li.dropdown:visible a:contains(\"Mitchell Admin\")',\n },\n {\n content: \"Logout\",\n trigger: '#o_logout:contains(\"Logout\")',\n },\n {\n content: \"Sign in as abc\",\n trigger: 'header a[href=\"/web/login\"]',\n },\n {\n content: \"Submit login\",\n trigger: '.oe_login_form',\n run: function () {\n $('.oe_login_form input[name=\"login\"]').val(\"abc@odoo.com\");\n $('.oe_login_form input[name=\"password\"]').val(\"1admin@admin\");\n $('.oe_login_form input[name=\"redirect\"]').val(\"/shop?search=Storage Box Test\");\n $('.oe_login_form').submit();\n },\n },\n {\n content: \"Open product page\",\n trigger: '.oe_product_cart a:contains(\"Storage Box Test\")',\n },\n {\n content: \"Click on add to cart\",\n trigger: '#add_to_cart',\n },\n tourUtils.goToCart(),\n {\n content: \"Proceed to checkout\",\n trigger: 'a[href*=\"/shop/checkout\"]',\n },\n {\n content: \"Click on next button\",\n trigger: '.oe_cart .btn:contains(\"Next\")',\n },\n {\n content: \"Check selected billing address is same as typed in previous step\",\n trigger: '#shipping_and_billing:contains(SO1 Billing Street Edited, 33):contains(SO1BillingCityEdited):contains(Afghanistan)',\n run: function () {}, // it's a check\n },\n {\n content: \"Check selected shipping address is same as typed in previous step\",\n trigger: '#shipping_and_billing:contains(SO2New Shipping Street, 5):contains(SO2NewShipping):contains(Afghanistan)',\n run: function () {}, // it's a check\n },\n {\n content: \"Select `Wire Transfer` payment method\",\n trigger: '#payment_method label:contains(\"Wire Transfer\")',\n },\n {\n content: \"Pay Now\",\n extra_trigger: '#payment_method label:contains(\"Wire Transfer\") input:checked,#payment_method:not(:has(\"input:radio:visible\"))',\n trigger: 'button[name=\"o_payment_submit_button\"]:visible',\n }]);\n});\n", "/** @odoo-module **/\n\nimport wTourUtils from 'website.tour_utils';\n\nwTourUtils.registerWebsitePreviewTour('website_sale_tour_backend', {\n test: true,\n url: '/shop/cart',\n edition: true,\n}, [\n {\n content: \"open customize tab\",\n trigger: '.o_we_customize_snippet_btn',\n },\n {\n content: \"Enable Extra step\",\n extra_trigger: '#oe_snippets .o_we_customize_panel',\n trigger: '[data-customize-website-views=\"website_sale.extra_info_option\"] we-checkbox',\n },\n ...wTourUtils.clickOnSave(),\n ],\n);\n", "odoo.define('website_sale_tour.website_sale_fiscal_position_tour', function (require) {\n 'use strict';\n\n var tour = require(\"web_tour.tour\");\n\n tour.register('website_sale_fiscal_position_portal_tour', {\n test: true,\n url: '/shop?search=Super%20Product'\n }, [\n {\n content: \"Check price\",\n trigger: \".oe_product:contains('Super product') .product_price:contains('80.00')\",\n run: function() {} // Check\n },\n ]);\n\n tour.register('website_sale_fiscal_position_public_tour', {\n test: true,\n url: '/shop?search=Super%20Product'\n }, [\n {\n content: \"Toggle Pricelist\",\n trigger: \".o_pricelist_dropdown > .dropdown-toggle\",\n run: 'click',\n },\n {\n content: \"Change Pricelist\",\n trigger: \".dropdown-item:contains('EUROPE EUR')\",\n run: 'click',\n },\n {\n content: \"Check price\",\n trigger: \".oe_product:contains('Super product') .product_price:contains('92.00')\",\n run: function() {} // Check\n },\n ]);\n});\n", "odoo.define('website_sale.google_analytics', function (require) {\n'use strict';\n\nconst tour = require(\"web_tour.tour\");\nconst websiteSaleTracking = require('website_sale.tracking');\n\nlet itemId;\n\nwebsiteSaleTracking.include({\n // Purposely don't call super to avoid call to third party (GA) during tests\n _onViewItem(event, data) {\n $('body').attr('view-event-id', data.item_id);\n },\n _onAddToCart(event, data) {\n $('body').attr('cart-event-id', data.item_id);\n },\n});\n\ntour.register('google_analytics_view_item', {\n test: true,\n url: '/shop?search=Colored T-Shirt',\n},\n[\n {\n content: \"select Colored T-Shirt\",\n trigger: '.oe_product_cart a:contains(\"Colored T-Shirt\")',\n },\n {\n content: \"wait until `_getCombinationInfo()` rpc is done\",\n trigger: 'body[view-event-id]',\n timeout: 25000,\n run: () => {\n const $body = $('body');\n itemId = $body.attr('view-event-id');\n $body.removeAttr('view-event-id');\n }\n },\n {\n content: 'select another variant',\n extra_trigger: 'body:not([view-event-id])',\n trigger: 'ul.js_add_cart_variants ul.list-inline li:has(label.active) + li:has(label) input',\n },\n {\n content: 'wait until `_getCombinationInfo()` rpc is done (2)',\n // a new view event should have been generated, for another variant\n trigger: `body[view-event-id][view-event-id!=${itemId}]`,\n timeout: 25000,\n run: () => {}, // it's a check\n },\n]);\n\ntour.register('google_analytics_add_to_cart', {\n test: true,\n url: '/shop?search=Basic Shirt',\n},\n[\n {\n content: \"select Basic Shirt\",\n trigger: '.oe_product_cart a:contains(\"Basic Shirt\")',\n },\n {\n content: \"click add to cart button on product page\",\n trigger: '#add_to_cart',\n },\n {\n content: 'check add to cart event',\n extra_trigger: 'body[cart-event-id]',\n trigger: 'a:has(.my_cart_quantity:containsExact(1))',\n timeout: 25000,\n run: () => {}, // it's a check\n },\n]);\n\n});\n", "/** @odoo-module **/\n\nimport wTourUtils from \"website.tour_utils\";\n\nconst clickOnImgAndWaitForLoad = [\n {\n content: \"Click on the product image\",\n trigger: \"iframe #o-carousel-product img[alt='Test Remove Image']\",\n },\n {\n content: \"Check that the snippet editor of the clicked image has been loaded\",\n trigger: \"we-customizeblock-options:has(we-title:contains('Re-order'))\",\n run: () => null,\n },\n];\nconst enterEditModeOfTestProduct = [\n {\n content: \"Click on the product anchor\",\n trigger: \"iframe a:contains('Test Remove Image')\",\n },\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n];\n\nconst removeImg = [\n {\n content: \"Click on Remove\",\n trigger: \"we-customizeblock-options:has(we-title:contains('Image')) we-button[data-name='media_wsale_remove']\",\n },\n // If the snippet editor is not visible, the remove process is considered as\n // finished.\n {\n content: \"Check that the snippet editor is not visible\",\n trigger: \".o_we_customize_panel:not(:has(we-customizeblock-options:has(we-title:contains('Re-order'))))\",\n run: () => null,\n },\n];\n\nwTourUtils.registerWebsitePreviewTour(\"add_and_remove_main_product_image_no_variant\", {\n url: \"/shop?search=Test Remove Image\",\n test: true,\n}, [\n ...enterEditModeOfTestProduct,\n {\n content: \"Double click on the product image\",\n trigger: \"iframe #o-carousel-product img[alt='Test Remove Image']\",\n run: \"dblclick\",\n },\n {\n content: \"Click on the new image\",\n trigger: \".o_select_media_dialog img[title='s_default_image.jpg']\",\n },\n {\n content: \"Check that the snippet editor of the clicked image has been loaded\",\n trigger: \"we-customizeblock-options:has(we-title:contains('Re-order'))\",\n run: () => null,\n },\n ...removeImg,\n]);\nwTourUtils.registerWebsitePreviewTour(\"remove_main_product_image_with_variant\", {\n url: \"/shop?search=Test Remove Image\",\n test: true,\n}, [\n ...enterEditModeOfTestProduct,\n ...clickOnImgAndWaitForLoad,\n ...wTourUtils.clickOnSave(),\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n ...clickOnImgAndWaitForLoad,\n ...removeImg,\n]);\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\nimport wsTourUtils from 'website_sale.tour_utils';\nimport wTourUtils from 'website.tour_utils';\n\ntour.register('website_sale_reorder_from_portal', {\n test: true,\n url: '/my/orders',\n },\n [\n // Initial reorder, nothing in cart\n {\n content: 'Select first order',\n trigger: '.o_portal_my_doc_table a:first',\n },\n wTourUtils.clickOnElement('Reorder Again', '.o_wsale_reorder_button'),\n wTourUtils.clickOnElement('Confirm', '.o_wsale_reorder_confirm'),\n wsTourUtils.assertCartContains({productName: 'Reorder Product 1'}),\n wsTourUtils.assertCartContains({productName: 'Reorder Product 2'}),\n {\n content: \"Check that quantity is 1\",\n trigger: \".js_quantity[value='1']\",\n },\n // Second reorder, add reorder to cart\n {\n content: \"Go back to my orders\",\n trigger: \"body\",\n run: () => {\n window.location = \"/my/orders\";\n }\n },\n {\n content: 'Select first order',\n trigger: '.o_portal_my_doc_table a:first',\n },\n wTourUtils.clickOnElement('Reorder Again', '.o_wsale_reorder_button'),\n wTourUtils.clickOnElement('Confirm', '.o_wsale_reorder_confirm'),\n wTourUtils.clickOnElement('No', 'button:contains(No)'),\n wsTourUtils.assertCartContains({productName: 'Reorder Product 1'}),\n wsTourUtils.assertCartContains({productName: 'Reorder Product 2'}),\n {\n content: \"Check that quantity is 2\",\n trigger: \".js_quantity[value='2']\",\n },\n // Third reorder, clear cart and reorder\n {\n content: \"Go back to my orders\",\n trigger: \"body\",\n run: () => {\n window.location = \"/my/orders\";\n }\n },\n {\n content: 'Select first order',\n trigger: '.o_portal_my_doc_table a:first',\n },\n wTourUtils.clickOnElement('Reorder Again', '.o_wsale_reorder_button'),\n wTourUtils.clickOnElement('Confirm', '.o_wsale_reorder_confirm'),\n wTourUtils.clickOnElement('Yes', 'button:contains(Yes)'),\n wsTourUtils.assertCartContains({productName: 'Reorder Product 1'}),\n wsTourUtils.assertCartContains({productName: 'Reorder Product 2'}),\n {\n content: \"Check that quantity is 1\",\n trigger: \".js_quantity[value='1']\",\n },\n ]\n);\n", "/** @odoo-modules */\n\nimport wTourUtils from 'website.tour_utils';\n\nwTourUtils.registerWebsitePreviewTour('website_sale_restricted_editor_ui', {\n test: true,\n url: `/shop`,\n}, [\n {\n content: \"Open the site menu to check what is inside\",\n trigger: '[data-menu-xmlid=\"website.menu_site\"]',\n },\n {\n // Not very robust but still nice to have an extra check as this is the\n // main purpose of this tour\n content: \"First check the user is not a designer\",\n trigger: '.dropdown-menu:has([data-menu-xmlid=\"website_sale.menu_product_pages\"]):not(:has([data-menu-xmlid=\"website.menu_website_pages_list\"]))',\n },\n {\n content: \"Ensure the publish and 'edit-in-backend' buttons are not shown\",\n trigger: '.o_menu_systray:not(:has(.o_switch_danger_success)):not(:has(.o_website_edit_in_backend))',\n // Wait for the possibility to edit to appear\n extra_trigger: '.o_menu_systray .o_edit_website_container a',\n },\n {\n content: \"Navigate to the first product\",\n trigger: 'iframe .oe_product_image_link',\n },\n {\n content: \"Click on publish/unpublish\",\n trigger: '.o_menu_systray_item .o_switch_danger_success:has(input:checked)',\n },\n {\n content: \"Click on edit-in-backend\",\n trigger: '.o_menu_systray .o_website_edit_in_backend a',\n extra_trigger: '.o_menu_systray_item:not([data-processing]) .o_switch_danger_success:has(input:not(:checked))',\n },\n {\n content: \"Check that you landed on a form view and that the record was unpublished\",\n trigger: '.o_form_sheet [name=\"is_published\"] .fa-globe.text-danger',\n run: () => {},\n },\n]);\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\n\nfunction fail(errorMessage) {\n tour._consume_tour(tour.running_tour, errorMessage);\n}\n\nfunction assert(current, expected, info) {\n if (current !== expected) {\n fail(info + ': \"' + current + '\" instead of \"' + expected + '\".');\n }\n}\n\ntour.register('tour_shop_archived_variant_multi', {\n test: true,\n url: '/shop?search=Test Product 2',\n},\n[\n {\n content: \"select Test Product\",\n trigger: '.oe_product_cart a:containsExact(\"Test Product 2\")',\n },\n {\n content: 'click on the first variant',\n trigger: 'input[data-attribute_name=\"Size\"][data-value_name=\"Small\"]',\n },\n {\n content: \"click on the second variant\",\n trigger: 'input[data-attribute_name=\"Color\"][data-value_name=\"Black\"]',\n },\n {\n content: \"Check that brand b is not available and select it\",\n trigger: '.css_not_available input[data-attribute_name=\"Brand\"][data-value_name=\"Brand B\"]',\n },\n {\n content: \"check combination is not possible\",\n trigger: '.js_main_product.css_not_available .css_not_available_msg:contains(\"This combination does not exist.\")'\n },\n {\n content: \"check add to cart not possible\",\n trigger: '#add_to_cart.disabled',\n run: function () {},\n },\n {\n content: \"change second variant to remove warning\",\n trigger: 'input[data-attribute_name=\"Color\"][data-value_name=\"White\"]',\n },\n {\n content: \"Check that second variant is disabled\",\n trigger: '.css_not_available input[data-attribute_name=\"Color\"][data-value_name=\"Black\"]',\n run: function () {},\n },\n]);\n\ntour.register('test_09_pills_variant', {\n test: true,\n url: '/shop?search=Test Product 2',\n},\n[\n {\n content: \"select Test Product\",\n trigger: '.oe_product_cart a:containsExact(\"Test Product 2\")',\n },\n {\n content: \"check there are two radio boxes, both hidden\",\n trigger: '.js_main_product',\n run: function() {\n var buttons = $('input.js_variant_change');\n\n function isVisuallyHidden(elem) {\n const style = window.getComputedStyle(elem);\n return style.display === \"none\" ||\n style.visibility === \"hidden\" ||\n style.opacity === \"0\" ||\n (style.width === \"0px\" && style.height === \"0px\")\n }\n\n assert(buttons.length, 2, \"there should be two radio inputs\")\n assert(isVisuallyHidden(buttons[0]), true, \"first radio input is not hidden\")\n assert(isVisuallyHidden(buttons[1]), true, \"second radio input is not hidden\")\n assert(buttons[0].checked, true, \"first radio input should be checked\")\n },\n },\n {\n content: \"click on the second variant label\",\n trigger: 'label:contains(\"Small\")',\n },\n {\n content: 'check second variant is selected',\n trigger: 'li.o_variant_pills.active:contains(\"Small\")',\n run: function () {\n var button = $('input.js_variant_change[data-attribute_name=\"Size\"][data-value_name=\"Small\"]');\n assert(button.length, 1, \"there should be one radio input\")\n assert(button[0].checked, true, \"the radio input should be checked\")\n }\n },\n]);\n", "odoo.define('website_sale.tour_shop_cart_recovery', function (require) {\n'use strict';\n\nvar localStorage = require('web.local_storage');\nvar tour = require('web_tour.tour');\nconst tourUtils = require('website_sale.tour_utils');\nrequire('web.dom_ready');\n\nvar orderIdKey = 'website_sale.tour_shop_cart_recovery.orderId';\nvar recoveryLinkKey = 'website_sale.tour_shop_cart_recovery.recoveryLink';\n\ntour.register('shop_cart_recovery', {\n test: true,\n url: '/shop?search=Acoustic Bloc Screens',\n},\n[\n {\n content: \"select Acoustic Bloc Screens\",\n trigger: '.oe_product_cart a:containsExact(\"Acoustic Bloc Screens\")',\n },\n {\n content: \"click add to cart\",\n trigger: '#product_details #add_to_cart',\n },\n tourUtils.goToCart(),\n {\n content: \"check product is in cart, get cart id, logout, go to login\",\n trigger: 'td.td-product_name:contains(\"Acoustic Bloc Screens\")',\n run: function () {\n var orderId = $('.my_cart_quantity').data('order-id');\n localStorage.setItem(orderIdKey, orderId);\n window.location.href = \"/web/session/logout?redirect=/web/login\";\n },\n },\n {\n content: \"login as admin and go to the SO (backend)\",\n trigger: '.oe_login_form',\n run: function () {\n var orderId = localStorage.getItem(orderIdKey);\n var url = \"/web#action=sale.action_orders&view_type=form&id=\" + orderId;\n var $loginForm = $('.oe_login_form');\n $loginForm.find('input[name=\"login\"]').val(\"admin\");\n $loginForm.find('input[name=\"password\"]').val(\"admin\");\n $loginForm.find('input[name=\"redirect\"]').val(url);\n $loginForm.submit();\n },\n },\n {\n content: \"click action\",\n trigger: '.dropdown-toggle:contains(\"Action\")',\n },\n {\n content: \"click Send a Cart Recovery Email\",\n trigger: 'span:containsExact(\"Send a Cart Recovery Email\")',\n },\n {\n content: \"click Send email\",\n trigger: '.btn[name=\"action_send_mail\"]',\n },\n {\n content: \"check the mail is sent, grab the recovery link, and logout\",\n trigger: '.o_Message_content a:containsExact(\"Resume order\")',\n run: function () {\n var link = $('.o_Message_content a:containsExact(\"Resume order\")').attr('href');\n localStorage.setItem(recoveryLinkKey, link);\n window.location.href = \"/web/session/logout?redirect=/\";\n }\n },\n {\n content: \"go to the recovery link\",\n trigger: 'a[href=\"/web/login\"]',\n run: function () {\n window.location.href = localStorage.getItem(recoveryLinkKey);\n },\n },\n {\n content: \"check the page is working, click on restore\",\n extra_trigger: 'p:contains(\"This is your current cart\")',\n trigger: 'p:contains(\"restore\") a:contains(\"Click here\")',\n },\n {\n content: \"check product is in restored cart\",\n trigger: 'td.td-product_name:contains(\"Acoustic Bloc Screens\")',\n run: function () {},\n },\n]);\n});\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\nimport tourUtils from 'website_sale.tour_utils';\n\ntour.register('compare_list_price_price_list_display', {\n test: true,\n url: '/shop?search=test_product'\n },\n [\n tourUtils.assertProductPrice(\"price_reduce\", \"1,000\", \"test_product_default\"),\n tourUtils.assertProductPrice(\"price_reduce\", \"2,000\", \"test_product_with_compare_list_price\"),\n tourUtils.assertProductPrice(\"base_price\", \"2,500\", \"test_product_with_compare_list_price\"),\n tourUtils.assertProductPrice(\"price_reduce\", \"2,000\", \"test_product_with_pricelist\"),\n tourUtils.assertProductPrice(\"price_reduce\", \"4,000\", \"test_product_with_pricelist_and_compare_list_price\"),\n tourUtils.assertProductPrice(\"base_price\", \"4,500\", \"test_product_with_pricelist_and_compare_list_price\"),\n\n ...tourUtils.selectPriceList('pricelist_other_currency'),\n\n tourUtils.assertProductPrice(\"price_reduce\", \"2,000\", \"test_product_default\"),\n tourUtils.assertProductPrice(\"price_reduce\", \"4,000\", \"test_product_with_compare_list_price\"),\n tourUtils.assertProductPrice(\"base_price\", \"5,000\", \"test_product_with_compare_list_price\"),\n tourUtils.assertProductPrice(\"price_reduce\", \"4,000\", \"test_product_with_pricelist\"),\n tourUtils.assertProductPrice(\"price_reduce\", \"8,000\", \"test_product_with_pricelist_and_compare_list_price\"),\n tourUtils.assertProductPrice(\"base_price\", \"9,000\", \"test_product_with_pricelist_and_compare_list_price\"),\n\n ...tourUtils.selectPriceList('pricelist_with_discount'),\n\n tourUtils.assertProductPrice(\"price_reduce\", \"1,000\", \"test_product_default\"),\n tourUtils.assertProductPrice(\"price_reduce\", \"2,000\", \"test_product_with_compare_list_price\"),\n tourUtils.assertProductPrice(\"base_price\", \"2,500\", \"test_product_with_compare_list_price\"),\n tourUtils.assertProductPrice(\"price_reduce\", \"1,500\", \"test_product_with_pricelist\"),\n tourUtils.assertProductPrice(\"price_reduce\", \"3,500\", \"test_product_with_pricelist_and_compare_list_price\"),\n tourUtils.assertProductPrice(\"base_price\", \"4,500\", \"test_product_with_pricelist_and_compare_list_price\"),\n\n ...tourUtils.selectPriceList('pricelist_without_discount'),\n\n tourUtils.assertProductPrice(\"price_reduce\", \"1,000\", \"test_product_default\"),\n tourUtils.assertProductPrice(\"price_reduce\", \"2,000\", \"test_product_with_compare_list_price\"),\n tourUtils.assertProductPrice(\"base_price\", \"2,500\", \"test_product_with_compare_list_price\"),\n tourUtils.assertProductPrice(\"price_reduce\", \"1,500\", \"test_product_with_pricelist\"),\n tourUtils.assertProductPrice(\"base_price\", \"2,000\", \"test_product_with_pricelist\"),\n tourUtils.assertProductPrice(\"price_reduce\", \"3,500\", \"test_product_with_pricelist_and_compare_list_price\"),\n tourUtils.assertProductPrice(\"base_price\", \"4,500\", \"test_product_with_pricelist_and_compare_list_price\"),\n\n ]\n);\n", "odoo.define(\"website_sale.tour_shop_custom_attribute_value\", function (require) {\n \"use strict\";\n\n var tour = require(\"web_tour.tour\");\n const tourUtils = require('website_sale.tour_utils');\n\n tour.register(\"shop_custom_attribute_value\", {\n url: \"/shop?search=Customizable Desk\",\n test: true,\n }, [{\n content: \"click on Customizable Desk\",\n trigger: '.oe_product_cart a:contains(\"Customizable Desk (TEST)\")',\n }, {\n trigger: 'li.js_attribute_value span:contains(Custom TEST)',\n extra_trigger: 'li.js_attribute_value',\n run: 'click',\n }, {\n trigger: 'input.variant_custom_value',\n run: 'text Wood',\n }, {\n id: 'add_cart_step',\n trigger: 'a:contains(ADD TO CART)',\n run: 'click',\n },\n tourUtils.goToCart(),\n {\n trigger: 'span:contains(Custom TEST: Wood)',\n extra_trigger: '#cart_products',\n run: function (){}, // check\n }]);\n});\n", "/** @odoo-module **/\n\nimport tourUtils from 'website_sale.tour_utils';\nimport wTourUtils from 'website.tour_utils';\n\nwTourUtils.registerWebsitePreviewTour('shop_customize', {\n url: '/shop',\n edition: true,\n test: true,\n},\n [\n ...wTourUtils.clickOnSave(),\n {\n content: \"select product attribute Steel\",\n extra_trigger: \"iframe body:not(.editor_enable)\",\n trigger: 'iframe form.js_attributes input:not(:checked) + label:contains(Steel - Test)',\n },\n {\n content: \"check the selection\",\n trigger: 'iframe form.js_attributes input:checked + label:contains(Steel - Test)',\n run: function () {}, // it's a check\n },\n {\n content: \"select product\",\n extra_trigger: 'iframe body:not(:has(.oe_website_sale .oe_product_cart:eq(3)))',\n trigger: 'iframe .oe_product_cart a:contains(\"Test Product\")',\n },\n {\n content: \"check list view of variants is disabled initially\",\n extra_trigger: \"iframe #product_detail\",\n trigger: 'iframe body:not(:has(.js_product_change))',\n run: function () {},\n },\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n content: \"open customize tab\",\n trigger: '.o_we_customize_snippet_btn',\n },\n {\n content: \"open 'Variants' selector\",\n extra_trigger: '#oe_snippets .o_we_customize_panel',\n trigger: '[data-name=\"variants_opt\"] we-toggler',\n },\n {\n content: \"click on 'Products List' of the 'Variants' selector\",\n trigger: 'we-button[data-name=\"variants_products_list_opt\"]',\n },\n ...wTourUtils.clickOnSave(),\n {\n context: \"check variant price\",\n extra_trigger: \"iframe body:not(.editor_enable)\",\n trigger: 'iframe .form-check:contains(\"Aluminium\") .badge:contains(\"+\") .oe_currency_value:contains(\"50.4\")',\n run: function () {},\n },\n {\n content: \"check price is 750\",\n trigger: \"iframe .product_price .oe_price .oe_currency_value:containsExact(750.00)\",\n run: function () {},\n },\n {\n content: \"switch to another variant\",\n trigger: \"iframe .js_product label:contains('Aluminium')\",\n },\n {\n content: \"verify that price has changed when changing variant\",\n trigger: \"iframe .product_price .oe_price .oe_currency_value:containsExact(800.40)\",\n run: function () {},\n },\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n content: \"open customize tab\",\n trigger: '.o_we_customize_snippet_btn',\n },\n {\n content: \"open 'Variants' selector\",\n extra_trigger: '#oe_snippets .o_we_customize_panel',\n trigger: '[data-name=\"variants_opt\"] we-toggler',\n },\n {\n content: \"click on 'Options' of the 'Variants' selector\",\n trigger: 'we-button[data-name=\"variants_options_opt\"]',\n },\n ...wTourUtils.clickOnSave(),\n {\n content: \"check page loaded after list of variant customization disabled\",\n extra_trigger: \"iframe body:not(.editor_enable)\",\n trigger: \"iframe .js_product:not(:has(.js_product_change))\",\n run: function () {}, // it's a check\n },\n {\n content: \"check price is 750\",\n trigger: \"iframe .product_price .oe_price .oe_currency_value:containsExact(750.00)\",\n run: function () {},\n },\n {\n content: \"switch to Aluminium variant\",\n trigger: 'iframe .js_product input[data-value_name=\"Aluminium\"]',\n },\n {\n content: \"verify that price has changed when changing variant\",\n trigger: \"iframe .product_price .oe_price .oe_currency_value:containsExact(800.40)\",\n run: function () {}, // it's a check\n },\n {\n content: \"switch back to Steel variant\",\n trigger: \"iframe .js_product label:contains('Steel - Test')\",\n },\n {\n content: \"check price is 750\",\n trigger: \"iframe .product_price .oe_price .oe_currency_value:containsExact(750.00)\",\n run: function () {},\n },\n {\n content: \"click on 'Add to Cart' button\",\n trigger: \"iframe a:contains(ADD TO CART)\",\n },\n {\n content: \"check quantity\",\n trigger: 'iframe .my_cart_quantity:containsExact(1),.o_extra_menu_items .fa-plus',\n run: function () {}, // it's a check\n },\n tourUtils.goToCart({backend: true}),\n {\n content: \"click on shop\",\n trigger: \"iframe a:contains(Continue Shopping)\",\n extra_trigger: 'iframe body:not(:has(#products_grid_before .js_attributes))',\n },\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n content: \"open customize tab\",\n trigger: '.o_we_customize_snippet_btn',\n },\n {\n content: \"remove 'Attributes'\",\n extra_trigger: '#oe_snippets .o_we_customize_panel',\n trigger: 'we-button[data-name=\"attributes_opt\"]',\n },\n ...wTourUtils.clickOnSave(),\n {\n content: \"wait to exit edit mode\",\n trigger: '.o_website_preview:not(.editor_has_snippets)',\n },\n {\n content: \"finish\",\n extra_trigger: 'iframe body:not(:has(#products_grid_before .js_attributes))',\n trigger: 'iframe #wrap:not(:has(li:has(.my_cart_quantity):visible))',\n run: function () {}, // it's a check\n },\n ],\n);\n", "odoo.define('website_sale.tour_shop_deleted_archived_variants', function (require) {\n'use strict';\n\nvar tour = require('web_tour.tour');\n\n// This tour relies on a data created from the python test.\ntour.register('tour_shop_deleted_archived_variants', {\n test: true,\n url: '/shop?search=Test Product 2',\n},\n[\n {\n content: \"check price on /shop (template price)\",\n trigger: '.oe_product_cart .oe_currency_value:contains(\"1.00\")',\n },\n {\n content: \"select Test Product 2\",\n trigger: '.oe_product_cart a:containsExact(\"Test Product 2\")',\n },\n {\n content: \"check price (3rd variant)\",\n trigger: '.oe_currency_value:contains(\"31.00\")'\n },\n {\n content: \"click on the second variant\",\n trigger: 'input[data-attribute_name=\"My Attribute\"][data-value_name=\"My Value 2\"]',\n },\n {\n content: \"check combination is not possible\",\n trigger: '.js_main_product.css_not_available .css_not_available_msg:contains(\"This combination does not exist.\")'\n },\n {\n content: \"click on the 3rd variant to reset the warning\",\n trigger: 'input[data-attribute_name=\"My Attribute\"][data-value_name=\"My Value 3\"]',\n },\n {\n content: \"check price (3rd variant)\",\n trigger: '.oe_currency_value:contains(\"31.00\")'\n },\n {\n content: \"click on the first variant\",\n trigger: 'input[data-attribute_name=\"My Attribute\"][data-value_name=\"My Value 1\"]',\n },\n {\n content: \"check combination is not possible\",\n trigger: '.js_main_product.css_not_available .css_not_available_msg:contains(\"This combination does not exist.\")'\n },\n {\n content: \"check add to cart not possible\",\n trigger: '#add_to_cart.disabled',\n run: function () {},\n }\n]);\n});\n", "odoo.define('website_sale.tour_shop_dynamic_variants', function (require) {\n'use strict';\n\nvar tour = require('web_tour.tour');\nconst tourUtils = require('website_sale.tour_utils');\n\n// This tour relies on a data created from the python test.\ntour.register('tour_shop_dynamic_variants', {\n test: true,\n url: '/shop?search=Dynamic Product',\n},\n[\n {\n content: \"select Dynamic Product\",\n trigger: '.oe_product_cart a:containsExact(\"Dynamic Product\")',\n },\n {\n content: \"click on the second variant\",\n trigger: 'input[data-attribute_name=\"Dynamic Attribute\"][data-value_name=\"Dynamic Value 2\"]',\n },\n {\n content: \"wait for variant to be loaded\",\n trigger: '.oe_price .oe_currency_value:contains(\"0.00\")',\n run: function () {},\n },\n {\n content: \"click add to cart\",\n extra_trigger: 'body:has(input[type=\"hidden\"][name=\"product_id\"][value=0])',\n trigger: '#add_to_cart',\n },\n tourUtils.goToCart(),\n {\n content: \"check the variant is in the cart\",\n trigger: 'td.td-product_name:contains(Dynamic Product (Dynamic Value 2))',\n },\n]);\n});\n", "/** @odoo-module **/\n\nimport wTourUtils from \"website.tour_utils\";\n\nwTourUtils.registerWebsitePreviewTour(\"shop_editor\", {\n test: true,\n url: \"/shop\",\n edition: true,\n}, [{\n content: \"Click on pricelist dropdown\",\n trigger: \"iframe div.o_pricelist_dropdown a[data-bs-toggle=dropdown]\",\n}, {\n trigger: \"iframe input[name=search]\",\n extra_trigger: \"iframe div.o_pricelist_dropdown a[data-bs-toggle=dropdown][aria-expanded=true]\",\n content: \"Click somewhere else in the shop.\",\n}, {\n trigger: \"iframe div.o_pricelist_dropdown a[data-bs-toggle=dropdown]\",\n extra_trigger: \"iframe div.o_pricelist_dropdown a[data-bs-toggle=dropdown][aria-expanded=false]\",\n content: \"Click on the pricelist again.\",\n}]);\n\nwTourUtils.registerWebsitePreviewTour(\"shop_editor_set_product_ribbon\", {\n test: true,\n url: \"/shop\",\n edition: true,\n}, [{\n content: \"Click on first product\",\n trigger: \"iframe .oe_product:first\",\n}, {\n content: \"Open the ribbon selector\",\n trigger: \".o_wsale_ribbon_select we-toggler\",\n}, {\n content: \"Select a ribbon\",\n trigger: '.o_wsale_ribbon_select we-button:contains(\"Sale\")',\n},\n...wTourUtils.clickOnSave(),\n{\n content: \"Check that the ribbon was properly saved\",\n trigger: 'iframe .oe_product:first .o_ribbon:contains(\"Sale\")',\n run: () => null,\n}]);\n", "/** @odoo-module **/\n\nimport tourUtils from 'website_sale.tour_utils';\nimport wTourUtils from 'website.tour_utils';\n\nwTourUtils.registerWebsitePreviewTour('shop_list_view_b2c', {\n test: true,\n url: '/shop?search=Test Product',\n},\n [\n {\n content: \"check price on /shop\",\n trigger: 'iframe .oe_product_cart .oe_currency_value:contains(\"825.00\")',\n run: () => {}, // It's a check.\n },\n {\n content: \"select product\",\n trigger: 'iframe .oe_product_cart a:contains(\"Test Product\")',\n },\n {\n content: \"check products list is disabled initially (when on /product page)\",\n trigger: 'iframe body:not(:has(.js_product_change))',\n extra_trigger: 'iframe #product_details',\n run: () => {}, // It's a check.\n },\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n content: \"open customize tab\",\n trigger: '.o_we_customize_snippet_btn',\n },\n {\n content: \"open 'Variants' selector\",\n extra_trigger: '#oe_snippets .o_we_customize_panel',\n trigger: '[data-name=\"variants_opt\"] we-toggler',\n },\n {\n content: \"click on 'Products List' of the 'Variants' selector\",\n trigger: 'we-button[data-name=\"variants_products_list_opt\"]',\n },\n ...wTourUtils.clickOnSave(),\n {\n content: \"check page loaded after 'Products List' enabled\",\n trigger: 'iframe .js_product_change',\n run: () => {}, // It's a check.\n },\n {\n context: \"check variant price\",\n trigger: 'iframe .form-check:contains(\"Aluminium\") .badge:contains(\"+\") .oe_currency_value:contains(\"55.44\")',\n run: () => {}, // It's a check.\n },\n {\n content: \"check price is 825\",\n trigger: 'iframe .product_price .oe_price .oe_currency_value:containsExact(825.00)',\n run: () => {}, // It's a check.\n },\n {\n content: \"switch to another variant\",\n trigger: 'iframe .js_product label:contains(\"Aluminium\")',\n },\n {\n content: \"verify that price has changed when changing variant\",\n trigger: 'iframe .product_price .oe_price .oe_currency_value:containsExact(880.44)',\n run: () => {}, // It's a check.\n },\n {\n content: \"click on 'Add to Cart' button\",\n trigger: 'iframe a:contains(ADD TO CART)',\n },\n tourUtils.goToCart({backend: true}),\n {\n content: \"check price on /cart\",\n trigger: 'iframe #cart_products .oe_currency_value:containsExact(880.44)',\n run: () => {}, // It's a check.\n },\n ],\n);\n", "odoo.define('website_sale.tour_shop_mail', function (require) {\n'use strict';\n\nvar tour = require('web_tour.tour');\nconst tourUtils = require('website_sale.tour_utils');\n\nrequire('web.dom_ready');\n\ntour.register('shop_mail', {\n test: true,\n url: '/shop?search=Acoustic Bloc Screens',\n},\n[\n {\n content: \"select Acoustic Bloc Screens\",\n trigger: '.oe_product_cart a:containsExact(\"Acoustic Bloc Screens\")',\n },\n {\n content: \"click add to cart\",\n trigger: '#product_details #add_to_cart',\n },\n tourUtils.goToCart(),\n {\n content: \"check product is in cart, get cart id, go to backend\",\n trigger: 'td.td-product_name:contains(\"Acoustic Bloc Screens\")',\n run: function () {\n var orderId = $('.my_cart_quantity').data('order-id');\n window.location.href = \"/web#action=sale.action_orders&view_type=form&id=\" + orderId;\n },\n },\n {\n content: \"click confirm\",\n trigger: '.btn[name=\"action_confirm\"]',\n },\n {\n content: \"click send by email\",\n trigger: '.btn[name=\"action_quotation_send\"]',\n extra_trigger: '.o_statusbar_status .o_arrow_button_current:contains(\"Sales Order\")',\n },\n {\n content: \"Open recipients dropdown\",\n trigger: '.o_field_many2many_tags_email[name=partner_ids] input',\n run: 'click',\n },\n {\n content: \"Select azure interior\",\n trigger: '.ui-menu-item a:contains(Interior24)',\n in_modal: false,\n },\n {\n content: \"click Send email\",\n trigger: '.btn[name=\"action_send_mail\"]',\n extra_trigger: '.o_badge_text:contains(\"Azure\")',\n },\n {\n content: \"wait mail to be sent, and go see it\",\n trigger: '.o_Message_content:contains(\"Your\"):contains(\"order\")',\n },\n]);\n});\n", "odoo.define('website_sale.tour_shop_no_variant_attribute', function (require) {\n'use strict';\n\nvar tour = require('web_tour.tour');\nconst tourUtils = require('website_sale.tour_utils');\n\n// This tour relies on a data created from the python test.\ntour.register('tour_shop_no_variant_attribute', {\n test: true,\n url: '/shop?search=Test Product 3',\n},\n[\n {\n content: \"select Test Product 3\",\n trigger: '.oe_product_cart a:containsExact(\"Test Product 3\")',\n },\n {\n content: \"check price\",\n trigger: '.oe_currency_value:contains(\"1.00\")',\n run: function () {},\n },\n {\n content: \"add to cart\",\n trigger: 'a:contains(ADD TO CART)',\n },\n tourUtils.goToCart(),\n {\n content: \"check no_variant value is present\",\n trigger: '.td-product_name:contains(No Variant Attribute: No Variant Value)',\n extra_trigger: '#cart_products',\n run: function () {},\n },\n {\n content: \"check price is correct\",\n trigger: '.td-price:contains(11.0)',\n run: function () {},\n },\n]);\n});\n", "odoo.define('website_sale_tour.website_sale_shop_pricelist_tour', function (require) {\n 'use strict';\n\n var tour = require(\"web_tour.tour\");\n\n tour.register('website_sale_shop_pricelist_tour', {\n test: true,\n url: '/shop'\n }, [\n {\n content: \"Check pricelist\",\n trigger: \".o_pricelist_dropdown:contains('Public Pricelist 1')\",\n run: function() {} // Check\n },\n {\n content: \"Go to login page\",\n trigger: \".nav-link:contains('Sign in')\"\n },\n {\n content: \"Submit login\",\n trigger: '.oe_login_form',\n run: function () {\n $('.oe_login_form input[name=\"login\"]').val(\"toto\");\n $('.oe_login_form input[name=\"password\"]').val(\"long_enough_password\");\n $('.oe_login_form input[name=\"redirect\"]').val(\"/shop\");\n $('.oe_login_form').submit();\n }\n },\n {\n content: \"Check pricelist\",\n trigger: \".o_pricelist_dropdown:contains('User Pricelist')\",\n run: function() {} // Check\n },\n ]);\n});\n", "odoo.define('website_sale.tour_shop_zoom', function (require) {\n'use strict';\n\nvar tour = require('web_tour.tour');\n\nvar imageSelector = '#o-carousel-product .carousel-item.active img';\nvar imageName = \"A Colorful Image\";\nvar nameGreen = \"Forest Green\";\n\n// This tour relies on a data created from the python test.\ntour.register('shop_zoom', {\n test: true,\n url: '/shop?debug=1&search=' + imageName,\n},\n[\n {\n content: \"select \" + imageName,\n trigger: '.oe_product_cart a:containsExact(\"' + imageName + '\")',\n },\n {\n content: \"click on the image\",\n trigger: imageSelector,\n run: 'clicknoleave',\n },\n {\n content: \"check that the image viewer opened\",\n trigger: '.o_wsale_image_viewer',\n run: () => {},\n },\n {\n content: \"close the image viewer\",\n trigger: '.o_wsale_image_viewer_header span.fa-times',\n },\n {\n content: \"change variant\",\n trigger: 'input[data-attribute_name=\"Beautiful Color\"][data-value_name=\"' + nameGreen + '\"]',\n run: 'click',\n },\n {\n content: \"wait for variant to be loaded\",\n trigger: '.oe_currency_value:contains(\"21.00\")'\n },\n {\n content: \"click on the image\",\n trigger: imageSelector,\n run: 'clicknoleave',\n },\n {\n content: \"check there is a zoom on that big image\",\n trigger: '.o_wsale_image_viewer',\n run: () => {},\n },\n]);\n});\n", "/** @odoo-module */\n\nimport wTourUtils from 'website.tour_utils';\nimport wSaleTourUtils from 'website_sale.tour_utils';\n\nconst optionBlock = 'dynamic_snippet_products';\nconst productsSnippet = {id: 's_dynamic_snippet_products', name: 'Products'};\nconst templates = [\n \"dynamic_filter_template_product_product_add_to_cart\",\n \"dynamic_filter_template_product_product_view_detail\",\n \"dynamic_filter_template_product_product_mini_image\",\n \"dynamic_filter_template_product_product_mini_price\",\n \"dynamic_filter_template_product_product_mini_name\",\n \"dynamic_filter_template_product_product_centered\",\n \"dynamic_filter_template_product_product_borderless_1\",\n \"dynamic_filter_template_product_product_borderless_2\",\n \"dynamic_filter_template_product_product_banner\",\n \"dynamic_filter_template_product_product_horizontal_card\",\n \"dynamic_filter_template_product_product_horizontal_card_2\",\n \"dynamic_filter_template_product_product_card_group\",\n];\n\nfunction changeTemplate(templateKey) {\n const templateClass = templateKey.replace(/dynamic_filter_template_/, \"s_\");\n return [\n wTourUtils.changeOption(optionBlock, 'we-select[data-name=\"template_opt\"] we-toggler', 'template'),\n wTourUtils.changeOption(optionBlock, `we-button[data-select-data-attribute=\"website_sale.${templateKey}\"]`),\n {\n content: 'Check the template is applied',\n trigger: `iframe .s_dynamic_snippet_products.${templateClass} .carousel`,\n run: () => null, // It's a check\n },\n ];\n}\n\nlet templatesSteps = [];\nfor (const templateKey of templates) {\n templatesSteps = templatesSteps.concat(changeTemplate(templateKey));\n}\nwTourUtils.registerWebsitePreviewTour('website_sale.snippet_products', {\n test: true,\n url: '/',\n edition: true,\n},\n[\n wTourUtils.dragNDrop(productsSnippet),\n wTourUtils.clickOnSnippet(productsSnippet),\n ...templatesSteps,\n ...changeTemplate('dynamic_filter_template_product_product_add_to_cart'),\n ...wTourUtils.clickOnSave(),\n {\n trigger: \"iframe .s_dynamic_snippet_products .o_carousel_product_card_body .js_add_cart\",\n run: 'click',\n },\n wSaleTourUtils.goToCart({backend: true}),\n]);\n\nwTourUtils.registerWebsitePreviewTour('website_sale.products_snippet_recently_viewed', {\n test: true,\n url: '/',\n edition: true,\n},\n[\n wTourUtils.dragNDrop(productsSnippet),\n wTourUtils.clickOnSnippet(productsSnippet),\n ...changeTemplate('dynamic_filter_template_product_product_add_to_cart'),\n wTourUtils.changeOption(optionBlock, 'we-select[data-name=\"filter_opt\"] we-toggler', 'filter'),\n wTourUtils.changeOption(optionBlock, 'we-select[data-name=\"filter_opt\"] we-button:contains(\"Recently Viewed\")', 'filter'),\n ...wTourUtils.clickOnSave(),\n {\n content: 'make delete icon appear',\n trigger: 'iframe .s_dynamic_snippet_products .o_carousel_product_card',\n run: function () {\n const $iframe = $('.o_iframe').contents();\n const $productCard = $iframe.find('.o_carousel_product_card:has(a img[alt=\"Storage Box\"])');\n console.log($productCard);\n $productCard.find('.js_remove').attr('style', 'display: block;');\n }\n },\n {\n trigger: 'iframe .s_dynamic_snippet_products .o_carousel_product_card .js_remove',\n run: 'click',\n },\n]);\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\n\n\ntour.register('test_mrp_manual_consumption', {test: true}, [\n {\n trigger: 'div[name=move_raw_ids] td[name=\"quantity_done\"]:last:contains(\"5.00\")',\n run: () => {},\n },\n {\n trigger: 'div[name=move_raw_ids] td[name=\"quantity_done\"]:last',\n run: 'click',\n },\n {\n trigger: 'div[name=\"quantity_done\"] input',\n run: 'text 6.0'\n },\n {\n content: \"Click Pager\",\n trigger: \".o_pager_value:first()\",\n },\n {\n trigger: \"input[id='qty_producing']\",\n run: 'text 8.0',\n },\n {\n content: \"Click Pager\",\n trigger: \".o_pager_value:first()\",\n },\n {\n trigger: 'div[name=move_raw_ids] td[name=\"quantity_done\"]:last:contains(\"6.00\")',\n run: () => {},\n },\n {\n trigger: 'button[name=button_mark_done]',\n run: 'click',\n },\n {\n trigger: 'button[name=action_confirm]',\n extra_trigger: '.o_technical_modal',\n run: 'click',\n },\n {\n trigger: 'button[name=action_backorder]',\n run: 'click',\n },\n {\n trigger: \"input[id='qty_producing']\",\n run: 'text 2.0',\n },\n {\n content: \"Click Pager\",\n trigger: \".o_pager_value:first()\",\n },\n {\n trigger: 'div[name=move_raw_ids] td[name=\"quantity_done\"]:last:contains(\"2.00\")',\n run: () => {},\n },\n {\n trigger: 'button[name=button_mark_done]',\n run: 'click',\n },\n {\n trigger: 'button[name=action_confirm]',\n extra_trigger: '.o_technical_modal',\n run: 'click',\n },\n ...tour.stepUtils.saveForm(),\n]);\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\n\ntour.register('hr_employee_tour', {\n test: true,\n url: '/web',\n}, [\n tour.stepUtils.showAppsMenuItem(),\n {\n content: \"Open Employees app\",\n trigger: \".o_app[data-menu-xmlid='hr.menu_hr_root']\",\n run: 'click',\n },\n {\n content: \"Open an Employee Profile\",\n trigger: \".o_kanban_record_title:contains('Johnny H.')\",\n run: 'click',\n },\n {\n content: \"Open user account menu\",\n trigger: \".o_user_menu .oe_topbar_name\",\n run: 'click',\n }, {\n content: \"Open My Profile\",\n trigger: \"[data-menu=settings]\",\n run: 'click',\n },\n]);\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\n\n/**\n * As 'hr' changes the flow a bit and displays the user preferences form in a full view instead of\n * a modal, we adapt the steps of the original tour accordingly.\n */\ntour.tours['mail/static/tests/tours/user_modify_own_profile_tour.js'].steps = [{\n content: 'Open user account menu',\n trigger: '.o_user_menu button',\n}, {\n content: \"Open preferences / profile screen\",\n trigger: '[data-menu=settings]',\n}, {\n content: \"Update the email address\",\n trigger: 'div[name=\"email\"] input',\n run: 'text updatedemail@example.com',\n}, ...tour.stepUtils.saveForm()];\n", "odoo.define('hr_expense.tests.tours', function (require) {\n \"use strict\";\n\n var tour = require('web_tour.tour');\n\n tour.register('hr_expense_test_tour', {\n test: true,\n url: \"/web\",\n }, [tour.stepUtils.showAppsMenuItem(),\n {\n content: \"Go to Expense\",\n trigger: '.o_app[data-menu-xmlid=\"hr_expense.menu_hr_expense_root\"]',\n },\n {\n content: \"Check Upload Button\",\n trigger: '.o_button_upload_expense',\n run() {\n const button = document.querySelector('.o_button_upload_expense');\n if(!button) {\n console.error('Missing Upload button in My Expenses to Report > List View');\n }\n }\n },\n {\n content: \"Check Create Report Button, but not click on it\",\n trigger: \"button.o_switch_view.o_list.active\",\n run() {\n const button = Array.from(document.querySelectorAll('.btn-secondary'))\n .filter(element => element.textContent.includes('Create Report'));\n if(!button) {\n console.error('Missing Create Report button in My Expenses to Report > List View');\n }\n }\n },\n {\n content: \"Go to kanban view\",\n trigger: \"button.o_switch_view.o_kanban\",\n },\n {\n content: \"Check Upload Button\",\n trigger: \"button.o_switch_view.o_kanban.active\",\n run() {\n const button = document.querySelector('.o_button_upload_expense');\n if(!button) {\n console.error('Missing Upload button in My Expenses to Report > Kanban View');\n }\n }\n },\n {\n content: \"Check Create Report Button, but not click on it\",\n trigger: \"button.o_switch_view.o_kanban.active\",\n run() {\n const button = Array.from(document.querySelectorAll('.btn-secondary'))\n .filter(element => element.textContent.includes('Create Report'));\n if(!button) {\n console.error('Missing Create Report button in My Expenses to Report > Kanban View');\n }\n }\n },\n {\n content: \"Go to Reporting\",\n trigger: 'button[data-menu-xmlid=\"hr_expense.menu_hr_expense_reports\"]',\n },\n {\n content: \"Go to Expenses Analysis\",\n trigger: 'a[data-menu-xmlid=\"hr_expense.menu_hr_expense_all_expenses\"]',\n },\n {\n content: \"Go to list view\",\n trigger: \"button.o_switch_view.o_list\",\n },\n {\n content: \"Check Upload Button\",\n trigger: \"button.o_switch_view.o_list.active\",\n run() {\n const button = document.querySelector('.o_button_upload_expense');\n if(!button) {\n console.error('Missing Upload button in Expenses Analysis > List View');\n }\n }\n },\n ]);\n});\n", "odoo.define('hr_holidays.tour_time_off_request_calendar_view', function (require) {\n'use strict';\n\nvar tour = require('web_tour.tour');\n\ntour.register('time_off_request_calendar_view', {\n test: true,\n url: '/web',\n},\n[\n tour.stepUtils.showAppsMenuItem(),\n {\n content: \"Open Time Off app\",\n trigger: '.o_app[data-menu-xmlid=\"hr_holidays.menu_hr_holidays_root\"]',\n },\n {\n content: \"Click on the first Thursday of the year\",\n trigger: '.fc-day-top.fc-thu',\n run: () => {\n const el = document.querySelector('.fc-day-top.fc-thu').firstChild;\n el.scrollIntoView();\n\n const fromPosition = el.getBoundingClientRect();\n fromPosition.x += el.offsetWidth / 2;\n fromPosition.y += el.offsetHeight / 2;\n\n el.dispatchEvent(new MouseEvent(\"mousedown\", {\n bubbles: true,\n which: 1,\n button: 0,\n clientX: fromPosition.x,\n clientY: fromPosition.y}));\n el.dispatchEvent(new MouseEvent(\"mouseup\", {\n bubbles: true,\n which: 1,\n button: 0,\n clientX: fromPosition.x,\n clientY: fromPosition.y }));\n }\n },\n {\n content: \"Save the leave\",\n trigger: '.btn:contains(\"Save\")',\n run: 'click',\n }\n]);\n});\n", "odoo.define('event.event_configurator_tour', function (require) {\n\"use strict\";\n\nvar tour = require('web_tour.tour');\n\ntour.register('event_configurator_tour', {\n url: \"/web\",\n test: true,\n}, [tour.stepUtils.showAppsMenuItem(), {\n trigger: '.o_app[data-menu-xmlid=\"sale.sale_menu_root\"]',\n edition: 'community'\n}, {\n trigger: '.o_app[data-menu-xmlid=\"sale.sale_menu_root\"]',\n edition: 'enterprise'\n}, {\n trigger: \".o_list_button_add\",\n extra_trigger: \".o_sale_order\"\n}, {\n trigger: \"a:contains('Add a product')\"\n}, {\n trigger: 'div[name=\"product_id\"] input, div[name=\"product_template_id\"] input',\n run: function (actions) {\n actions.text('Event Registration');\n }\n}, {\n trigger: 'ul.ui-autocomplete a:contains(\"Event Registration\")',\n run: 'click'\n}, {\n trigger: 'div[name=\"event_id\"] input',\n run: 'click'\n}, {\n trigger: 'ul.ui-autocomplete a:contains(\"Design\")',\n run: 'click',\n in_modal: false\n}, {\n trigger: 'div[name=\"event_ticket_id\"] input',\n run: 'click'\n}, {\n trigger: 'ul.ui-autocomplete a:contains(\"VIP\")',\n run: 'click',\n in_modal: false\n}, {\n trigger: '.o_event_sale_js_event_configurator_ok'\n}, {\n trigger: \"td[name='name'][data-tooltip*='VIP']\",\n run: function () {} // check\n}, {\n trigger: 'ul.nav a:contains(\"Order Lines\")',\n run: 'click'\n}, {\n content: \"search the partner\",\n trigger: 'div[name=\"partner_id\"] input',\n run: 'text Azure'\n}, {\n content: \"select the partner\",\n trigger: 'ul.ui-autocomplete > li > a:contains(Azure)',\n}, {\n trigger: 'td:contains(\"Event\")',\n run: 'click'\n}, {\n trigger: 'button.fa-pencil'\n}, {\n trigger: 'div[name=\"event_ticket_id\"] input',\n run: 'click'\n}, {\n trigger: 'ul.ui-autocomplete a:contains(\"Standard\")',\n run: 'click',\n in_modal: false\n}, {\n trigger: '.o_event_sale_js_event_configurator_ok'\n}, {\n trigger: \"td[name='name'][data-tooltip*='Standard']\",\n run: function () {} // check\n}, ...tour.stepUtils.saveForm()\n]);\n\n});\n", "odoo.define(\"website_event.tour\", function (require) {\n \"use strict\";\n\n const {_t} = require(\"web.core\");\n const {Markup} = require('web.utils');\n const wTourUtils = require('website.tour_utils');\n\n\n wTourUtils.registerWebsitePreviewTour(\"website_event_tour\", {\n test: true,\n url: \"/\",\n }, [{\n content: _t(\"Click here to add new content to your website.\"),\n trigger: \".o_menu_systray .o_new_content_container > a\",\n consumeVisibleOnly: true,\n position: 'bottom',\n }, {\n trigger: \"a[data-module-xml-id='base.module_website_event']\",\n content: _t(\"Click here to create a new event.\"),\n position: \"bottom\",\n }, {\n trigger: '.modal-dialog div[name=\"name\"] input',\n content: Markup(_t(\"Create a name for your new event and click \\\"Continue\\\". e.g: Technical Training\")),\n run: 'text Technical Training',\n position: \"left\",\n }, {\n trigger: '.modal-dialog div[name=date_begin]',\n content: _t(\"Open date range picker. Pick a Start date for your event\"),\n run: function () {\n $('input[id=\"date_begin\"]').val('09/30/2020 08:00:00').change();\n $('input[id=\"date_end\"]').val('10/02/2020 23:00:00').change();\n $('input[id=\"date_begin\"]').click();\n }\n }, {\n content: _t(\"Apply change.\"),\n trigger: '.daterangepicker[data-name=date_begin] .applyBtn',\n in_modal: false,\n }, {\n trigger: '.modal-footer button.btn-primary',\n extra_trigger: '.modal-dialog input[type=text][value!=\"\"]',\n content: Markup(_t(\"Click Continue to create the event.\")),\n position: \"right\",\n }, {\n trigger: \"#oe_snippets.o_loaded #snippet_structure .oe_snippet:eq(2) .oe_snippet_thumbnail\",\n content: _t(\"Drag this block and drop it in your page.\"),\n position: \"bottom\",\n run: \"drag_and_drop\",\n }, {\n trigger: \"button[data-action=save]\",\n content: _t(\"Once you click on save, your event is updated.\"),\n position: \"bottom\",\n // Wait until the drag and drop is resolved (causing a history step)\n // before clicking save.\n extra_trigger: \".o_we_external_history_buttons button[data-action=undo]:not([disabled])\",\n }, {\n trigger: \".o_menu_systray_item .o_switch_danger_success\",\n extra_trigger: \"iframe body:not(.editor_enable)\",\n content: _t(\"Click to publish your event.\"),\n position: \"top\",\n }, {\n trigger: \".o_website_edit_in_backend > a\",\n content: _t(\"Click here to customize your event further.\"),\n position: \"bottom\",\n }]);\n});\n", "/** @odoo-modules */\n\nimport tour from 'web_tour.tour';\n\ntour.register('website_event_pages_seo', {\n test: true,\n // The tour must start on an event's custom page (not register page)\n // url: `/event/openwood-collection-online-reveal-8/page/introduction-openwood-collection-online-reveal`,\n}, [\n {\n content: \"Open the site menu\",\n trigger: '[data-menu-xmlid=\"website.menu_site\"]',\n extra_trigger: 'iframe #o_wevent_event_submenu', // Ensure we landed on the event page\n },\n {\n content: \"Open the optimize SEO dialog\",\n trigger: '[data-menu-xmlid=\"website.menu_optimize_seo\"]',\n },\n {\n content: \"Fill in the title input\",\n trigger: '[for=\"website_meta_title\"] + input',\n run: 'text Hello, world!',\n },\n {\n content: \"Save the dialog\",\n trigger: '.modal-footer .btn-primary',\n },\n {\n content: \"Check that the page title is adapted, inside and outside the iframe\",\n trigger: 'html:has(title:containsExactText(Hello, world!))',\n extra_trigger: 'iframe html:has(title:containsExactText(Hello, world!))',\n },\n]);\n", "/** @odoo-module **/\n\nimport { ErrorPopup } from 'point_of_sale.tour.ErrorPopupTourMethods';\nimport { PosLoyalty } from 'pos_loyalty.tour.PosCouponTourMethods';\nimport { ProductScreen } from 'point_of_sale.tour.ProductScreenTourMethods';\nimport { TicketScreen } from 'point_of_sale.tour.TicketScreenTourMethods';\nimport { Chrome } from 'point_of_sale.tour.ChromeTourMethods';\nimport { PartnerListScreen } from 'point_of_sale.tour.PartnerListScreenTourMethods';\nimport { getSteps, startSteps } from 'point_of_sale.tour.utils';\nimport Tour from 'web_tour.tour';\n\n//#region EWalletProgramTour1\n\nstartSteps();\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\n\n// Topup 50$ for partner_aaa\nProductScreen.do.clickDisplayedProduct('Top-up eWallet');\nPosLoyalty.check.orderTotalIs('50.00');\nProductScreen.do.clickPayButton(false);\n// If there's no partner, we asked to redirect to the partner list screen.\nChrome.do.confirmPopup();\nPartnerListScreen.check.isShown();\nPartnerListScreen.do.clickPartner('AAAAAAA');\nPosLoyalty.exec.finalizeOrder('Cash');\n\n// Topup 10$ for partner_bbb\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('BBBBBBB');\nProductScreen.exec.addOrderline('Top-up eWallet', '1', '10');\nPosLoyalty.check.orderTotalIs('10.00');\nPosLoyalty.exec.finalizeOrder('Cash');\n\nTour.register('EWalletProgramTour1', { test: true, url: '/pos/web' }, getSteps());\n\n//#endregion\n\n//#region EWalletProgramTour2\n\nconst getEWalletText = (suffix) => 'eWallet' + (suffix !== '' ? ` ${suffix}` : '');\n\nstartSteps();\nProductScreen.do.clickHomeCategory();\nProductScreen.exec.addOrderline('Whiteboard Pen', '2', '6', '12.00');\nPosLoyalty.check.eWalletButtonState({ highlighted: false });\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('AAAAAAA');\nPosLoyalty.check.eWalletButtonState({ highlighted: true, text: getEWalletText('Pay') });\nPosLoyalty.do.clickEWalletButton(getEWalletText('Pay'));\nPosLoyalty.check.orderTotalIs('0.00');\nPosLoyalty.exec.finalizeOrder('Cash');\n\n// Consume partner_bbb's full eWallet.\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('BBBBBBB');\nPosLoyalty.check.eWalletButtonState({ highlighted: false });\nProductScreen.exec.addOrderline('Desk Pad', '6', '6', '36.00');\nPosLoyalty.check.eWalletButtonState({ highlighted: true, text: getEWalletText('Pay') });\nPosLoyalty.do.clickEWalletButton(getEWalletText('Pay'));\nPosLoyalty.check.orderTotalIs('26.00');\nPosLoyalty.exec.finalizeOrder('Cash');\n\n// Switching partners should work.\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('BBBBBBB');\nProductScreen.exec.addOrderline('Desk Pad', '2', '19', '38.00');\nPosLoyalty.check.eWalletButtonState({ highlighted: false });\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('AAAAAAA');\nPosLoyalty.check.eWalletButtonState({ highlighted: true, text: getEWalletText('Pay') });\nPosLoyalty.do.clickEWalletButton(getEWalletText('Pay'));\nPosLoyalty.check.orderTotalIs('0.00');\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('BBBBBBB');\nPosLoyalty.check.eWalletButtonState({ highlighted: false });\nPosLoyalty.check.orderTotalIs('38.00');\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('AAAAAAA');\nPosLoyalty.check.eWalletButtonState({ highlighted: true, text: getEWalletText('Pay') });\nPosLoyalty.do.clickEWalletButton(getEWalletText('Pay'));\nPosLoyalty.check.orderTotalIs('0.00');\nPosLoyalty.exec.finalizeOrder('Cash');\n\n// Refund with eWallet.\n// - Make an order to refund.\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('BBBBBBB');\nProductScreen.exec.addOrderline('Whiteboard Pen', '1', '20', '20.00');\nPosLoyalty.check.orderTotalIs('20.00');\nPosLoyalty.exec.finalizeOrder('Cash');\n// - Refund order.\nProductScreen.do.clickRefund();\nTicketScreen.check.filterIs('Paid');\nTicketScreen.do.selectOrder('-0004');\nTicketScreen.check.partnerIs('BBBBBBB');\nTicketScreen.do.confirmRefund();\nProductScreen.check.isShown();\nPosLoyalty.check.eWalletButtonState({ highlighted: true, text: getEWalletText('Refund') });\nPosLoyalty.do.clickEWalletButton(getEWalletText('Refund'));\nPosLoyalty.check.orderTotalIs('0.00');\nPosLoyalty.exec.finalizeOrder('Cash');\n\nTour.register('EWalletProgramTour2', { test: true, url: '/pos/web' }, getSteps());\n\n//#endregion\n\n//#region ExpiredEWalletProgramTour\n\nstartSteps();\n\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('AAAA');\nProductScreen.exec.addOrderline('Whiteboard Pen', '2', '6', '12.00');\nPosLoyalty.check.eWalletButtonState({ highlighted: false });\nPosLoyalty.do.clickEWalletButton();\nErrorPopup.check.isShown();\nErrorPopup.do.clickConfirm();\n\nTour.register('ExpiredEWalletProgramTour', { test: true, url: '/pos/web' }, getSteps());\n\n//#endregion\n\nstartSteps();\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer(\"partner_a\");\nPosLoyalty.check.eWalletButtonState({ highlighted: false });\nProductScreen.exec.addOrderline(\"product_a\", \"1\");\nPosLoyalty.check.eWalletButtonState({ highlighted: true, text: getEWalletText(\"Pay\") });\nPosLoyalty.do.clickEWalletButton(getEWalletText(\"Pay\"));\nPosLoyalty.check.pointsAwardedAre(\"100\"),\nPosLoyalty.exec.finalizeOrder(\"Cash\", \"90\");\nTour.register(\"PosLoyaltyPointsEwallet\", { test: true, url: \"/pos/web\" }, getSteps());\n", "/** @odoo-module **/\n\nimport { PosLoyalty } from 'pos_loyalty.tour.PosCouponTourMethods';\nimport { ProductScreen } from 'point_of_sale.tour.ProductScreenTourMethods';\nimport { TextInputPopup } from 'point_of_sale.tour.TextInputPopupTourMethods';\nimport { getSteps, startSteps } from 'point_of_sale.tour.utils';\nimport Tour from 'web_tour.tour';\n\n//#region GiftCardProgramCreateSetTour1\nstartSteps();\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\nProductScreen.do.clickDisplayedProduct('Gift Card');\nPosLoyalty.check.orderTotalIs('50.00');\nPosLoyalty.exec.finalizeOrder('Cash');\nTour.register('GiftCardProgramCreateSetTour1', { test: true, url: '/pos/web' }, getSteps());\n//#endregion\n\n//#region GiftCardProgramCreateSetTour2\nstartSteps();\nProductScreen.do.clickHomeCategory();\nProductScreen.do.clickDisplayedProduct('Whiteboard Pen');\nPosLoyalty.do.enterCode('044123456');\nPosLoyalty.check.orderTotalIs('0.00');\nPosLoyalty.exec.finalizeOrder('Cash');\nTour.register('GiftCardProgramCreateSetTour2', { test: true, url: '/pos/web' }, getSteps());\n//#endregion\n\n//#region GiftCardProgramScanUseTour\nstartSteps();\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\n// Pay the 5$ gift card.\nProductScreen.do.clickDisplayedProduct('Gift Card');\nTextInputPopup.check.isShown();\nTextInputPopup.do.inputText('044123456');\nTextInputPopup.do.clickConfirm();\nPosLoyalty.check.orderTotalIs('5.00');\nPosLoyalty.exec.finalizeOrder('Cash');\n// Partially use the gift card. (4$)\nProductScreen.exec.addOrderline('Desk Pad', '2', '2', '4.0');\nPosLoyalty.do.enterCode('044123456');\nPosLoyalty.check.orderTotalIs('0.00');\nPosLoyalty.exec.finalizeOrder('Cash');\n// Use the remaining of the gift card. (5$ - 4$ = 1$)\nProductScreen.exec.addOrderline('Whiteboard Pen', '6', '6', '36.0');\nPosLoyalty.do.enterCode('044123456');\nPosLoyalty.check.orderTotalIs('35.00');\nPosLoyalty.exec.finalizeOrder('Cash');\nTour.register('GiftCardProgramScanUseTour', { test: true, url: '/pos/web' }, getSteps());\n//#endregion\n\nstartSteps();\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\nProductScreen.do.clickDisplayedProduct('Gift Card');\nTextInputPopup.check.isShown();\nTextInputPopup.do.inputText('044123456');\nTextInputPopup.do.clickConfirm();\nPosLoyalty.check.orderTotalIs('50.00');\nPosLoyalty.exec.finalizeOrder('Cash');\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer(\"partner_a\");\nProductScreen.exec.addOrderline(\"product_a\", \"1\");\nPosLoyalty.do.enterCode(\"044123456\");\nPosLoyalty.check.orderTotalIs(\"50.00\");\nPosLoyalty.check.pointsAwardedAre(\"100\"),\nPosLoyalty.exec.finalizeOrder(\"Cash\", \"50\");\nTour.register(\"PosLoyaltyPointsGiftcard\", { test: true, url: \"/pos/web\" }, getSteps());\n\nstartSteps();\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\nProductScreen.do.clickDisplayedProduct('Gift Card');\nTextInputPopup.check.isShown();\nTextInputPopup.do.inputText('044123456');\nTextInputPopup.do.clickConfirm();\nPosLoyalty.check.orderTotalIs('50.00');\nPosLoyalty.exec.finalizeOrder('Cash');\nProductScreen.do.clickDisplayedProduct(\"Test Product A\");\nPosLoyalty.do.enterCode(\"044123456\");\nPosLoyalty.check.orderTotalIs(\"50.00\");\nProductScreen.check.checkTaxAmount(\"-6.52\");\nTour.register(\"PosLoyaltyGiftCardTaxes\", { test: true }, getSteps());\n\nstartSteps();\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\nProductScreen.do.clickDisplayedProduct('Gift Card');\nTextInputPopup.check.isShown();\nTextInputPopup.do.inputText('044123456');\nTextInputPopup.do.clickConfirm();\nPosLoyalty.check.orderTotalIs('0.00');\nProductScreen.do.pressNumpad(\"Price 5\");\nPosLoyalty.check.orderTotalIs('5.00');\nPosLoyalty.exec.finalizeOrder('Cash');\nTour.register(\"PosLoyaltyGiftCardNoPoints\", { test: true }, getSteps());\n", "/** @odoo-module **/\n\nimport { PosLoyalty } from 'pos_loyalty.tour.PosCouponTourMethods';\nimport { ProductScreen } from 'point_of_sale.tour.ProductScreenTourMethods';\nimport { SelectionPopup } from 'point_of_sale.tour.SelectionPopupTourMethods';\nimport { getSteps, startSteps } from 'point_of_sale.tour.utils';\nimport Tour from 'web_tour.tour';\n\nconst getEWalletText = (suffix) => 'eWallet' + (suffix !== '' ? ` ${suffix}` : '');\n\nstartSteps();\n// One card for gift_card_1.\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\nProductScreen.do.clickDisplayedProduct('Gift Card');\nSelectionPopup.check.hasSelectionItem('gift_card_1');\nSelectionPopup.check.hasSelectionItem('gift_card_2');\nSelectionPopup.do.clickItem('gift_card_1');\nProductScreen.do.pressNumpad('Price');\nProductScreen.do.pressNumpad('1 0');\nPosLoyalty.check.orderTotalIs('10.00');\nPosLoyalty.exec.finalizeOrder('Cash');\n// One card for gift_card_1.\nProductScreen.do.clickDisplayedProduct('Gift Card');\nSelectionPopup.do.clickItem('gift_card_2');\nProductScreen.do.pressNumpad('Price');\nProductScreen.do.pressNumpad('2 0');\nPosLoyalty.check.orderTotalIs('20.00');\nPosLoyalty.exec.finalizeOrder('Cash');\n// Top up ewallet_1 for AAAAAAA.\nProductScreen.do.clickDisplayedProduct('Top-up eWallet');\nSelectionPopup.check.hasSelectionItem('ewallet_1');\nSelectionPopup.check.hasSelectionItem('ewallet_2');\nSelectionPopup.do.clickItem('ewallet_1');\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('AAAAAAA');\nProductScreen.do.pressNumpad('Price');\nProductScreen.do.pressNumpad('3 0');\nPosLoyalty.check.orderTotalIs('30.00');\nPosLoyalty.exec.finalizeOrder('Cash');\n// Top up ewallet_2 for AAAAAAA.\nProductScreen.do.clickDisplayedProduct('Top-up eWallet');\nSelectionPopup.do.clickItem('ewallet_2');\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('AAAAAAA');\nProductScreen.do.pressNumpad('Price');\nProductScreen.do.pressNumpad('4 0');\nPosLoyalty.check.orderTotalIs('40.00');\nPosLoyalty.exec.finalizeOrder('Cash');\n// Top up ewallet_1 for BBBBBBB.\nProductScreen.do.clickDisplayedProduct('Top-up eWallet');\nSelectionPopup.do.clickItem('ewallet_1');\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('BBBBBBB');\nPosLoyalty.check.orderTotalIs('50.00');\nPosLoyalty.exec.finalizeOrder('Cash');\n// Consume 12$ from ewallet_1 of AAAAAAA.\nProductScreen.exec.addOrderline('Whiteboard Pen', '2', '6', '12.00');\nPosLoyalty.check.eWalletButtonState({ highlighted: false });\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('AAAAAAA');\nPosLoyalty.check.eWalletButtonState({ highlighted: true, text: getEWalletText('Pay') });\nPosLoyalty.do.clickEWalletButton(getEWalletText('Pay'));\nSelectionPopup.check.hasSelectionItem('ewallet_1');\nSelectionPopup.check.hasSelectionItem('ewallet_2');\nSelectionPopup.do.clickItem('ewallet_1');\nPosLoyalty.check.orderTotalIs('0.00');\nPosLoyalty.exec.finalizeOrder('Cash');\n\nTour.register('MultipleGiftWalletProgramsTour', { test: true, url: '/pos/web' }, getSteps());\n", "/** @odoo-module **/\n\nimport { PosLoyalty } from 'pos_loyalty.tour.PosCouponTourMethods';\nimport { ProductScreen } from 'point_of_sale.tour.ProductScreenTourMethods';\nimport { getSteps, startSteps } from 'point_of_sale.tour.utils';\nimport Tour from 'web_tour.tour';\n\nstartSteps();\n\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\n\n// Order1: Generates 2 points.\nProductScreen.exec.addOrderline('Whiteboard Pen', '2');\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('Test Partner AAA');\nPosLoyalty.check.orderTotalIs('6.40');\nPosLoyalty.exec.finalizeOrder('Cash');\n\n// Order2: Consumes points to get free product.\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('Test Partner AAA');\nProductScreen.do.clickDisplayedProduct('Whiteboard Pen');\nProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '1.00');\nProductScreen.do.clickDisplayedProduct('Whiteboard Pen');\nProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '2.00');\n// At this point, Test Partner AAA has 4 points.\nPosLoyalty.check.isRewardButtonHighlighted(true);\nProductScreen.do.clickDisplayedProduct('Whiteboard Pen');\nProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '3.00');\nPosLoyalty.check.hasRewardLine('Free Product - Whiteboard Pen', '-3.20', '1.00');\nPosLoyalty.check.isRewardButtonHighlighted(false);\nPosLoyalty.check.orderTotalIs('6.40');\nPosLoyalty.exec.finalizeOrder('Cash');\n\n// Order3: Generate 4 points.\n// - Initially gets free product, but was removed automatically by changing the\n// number of items to zero.\n// - It's impossible to checked here if the free product reward is really removed\n// so we check in the backend the number of orders that consumed the reward.\nProductScreen.exec.addOrderline('Whiteboard Pen', '4');\nPosLoyalty.check.orderTotalIs('12.80');\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('Test Partner AAA');\nPosLoyalty.check.isRewardButtonHighlighted(true);\nProductScreen.do.clickDisplayedProduct('Whiteboard Pen');\nPosLoyalty.check.hasRewardLine('Free Product - Whiteboard Pen', '-3.20', '1.00');\nPosLoyalty.check.isRewardButtonHighlighted(false);\nProductScreen.do.pressNumpad('Backspace');\n// At this point, the reward line should have been automatically removed\n// because there is not enough points to purchase it. Unfortunately, we\n// can't check that here.\nPosLoyalty.check.orderTotalIs('0.00');\nProductScreen.do.clickDisplayedProduct('Whiteboard Pen');\nProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '1.00');\nProductScreen.do.clickDisplayedProduct('Whiteboard Pen');\nProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '2.00');\nProductScreen.do.clickDisplayedProduct('Whiteboard Pen');\nProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '3.00');\nProductScreen.do.clickDisplayedProduct('Whiteboard Pen');\nPosLoyalty.check.isRewardButtonHighlighted(false);\nProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '4.00');\nPosLoyalty.check.isRewardButtonHighlighted(true);\n\nPosLoyalty.check.orderTotalIs('12.80');\nPosLoyalty.exec.finalizeOrder('Cash');\n\nTour.register('PosLoyaltyLoyaltyProgram1', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\n\nProductScreen.do.clickHomeCategory();\n\n// Order1: Immediately set the customer to Test Partner AAA which has 4 points.\n// - He has enough points to purchase a free product but since there is still\n// no product in the order, reward button should not yet be highlighted.\n// - Furthermore, clicking the reward product should not add it as reward product.\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('Test Partner AAA');\n// No item in the order, so reward button is off.\nPosLoyalty.check.isRewardButtonHighlighted(false);\nProductScreen.do.clickDisplayedProduct('Whiteboard Pen');\nPosLoyalty.check.isRewardButtonHighlighted(true);\nProductScreen.do.clickDisplayedProduct('Whiteboard Pen');\nPosLoyalty.check.hasRewardLine('Free Product - Whiteboard Pen', '-3.20', '1.00');\nPosLoyalty.check.isRewardButtonHighlighted(false);\nPosLoyalty.check.orderTotalIs('3.20');\nPosLoyalty.exec.finalizeOrder('Cash');\n\n// Order2: Generate 4 points for Test Partner CCC.\n// - Reference: Order2_CCC\n// - But set Test Partner BBB first as the customer.\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('Test Partner BBB');\nPosLoyalty.check.isRewardButtonHighlighted(false);\nProductScreen.do.clickDisplayedProduct('Whiteboard Pen');\nProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '1.00');\nProductScreen.do.clickDisplayedProduct('Whiteboard Pen');\nProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '2.00');\nProductScreen.do.clickDisplayedProduct('Whiteboard Pen');\nProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '3.00');\nPosLoyalty.check.isRewardButtonHighlighted(false);\nProductScreen.do.clickDisplayedProduct('Whiteboard Pen');\nProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '4.00');\nPosLoyalty.check.isRewardButtonHighlighted(true);\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('Test Partner CCC');\nPosLoyalty.check.customerIs('Test Partner CCC');\nPosLoyalty.check.orderTotalIs('12.80');\nPosLoyalty.exec.finalizeOrder('Cash');\n\n// Order3: Generate 3 points for Test Partner BBB.\n// - Reference: Order3_BBB\n// - But set Test Partner CCC first as the customer.\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('Test Partner CCC');\nPosLoyalty.check.isRewardButtonHighlighted(false);\nProductScreen.exec.addOrderline('Whiteboard Pen', '3');\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('Test Partner BBB');\nPosLoyalty.check.customerIs('Test Partner BBB');\nPosLoyalty.check.orderTotalIs('9.60');\nPosLoyalty.exec.finalizeOrder('Cash');\n\n// Order4: Should not have reward because the customer will be removed.\n// - Reference: Order4_no_reward\nProductScreen.do.clickDisplayedProduct('Whiteboard Pen');\nProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '1.00');\nPosLoyalty.check.isRewardButtonHighlighted(false);\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('Test Partner CCC');\nPosLoyalty.check.isRewardButtonHighlighted(true);\nPosLoyalty.do.clickRewardButton();\nPosLoyalty.check.hasRewardLine('Free Product - Whiteboard Pen', '-3.20', '1.00');\nProductScreen.do.clickPartnerButton();\n// This deselects the customer.\nPosLoyalty.do.unselectPartner();\nPosLoyalty.check.customerIs('Customer');\nPosLoyalty.check.orderTotalIs('6.40');\nPosLoyalty.exec.finalizeOrder('Cash');\n\nTour.register('PosLoyaltyLoyaltyProgram2', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\n\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\n\n// Generates 10.2 points and use points to get the reward product with zero sale price\nProductScreen.exec.addOrderline('Desk Organizer', '2');\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('Test Partner AAA');\n\n// At this point, the free_product program is triggered.\n// The reward button should be highlighted.\nPosLoyalty.check.isRewardButtonHighlighted(true);\n\nPosLoyalty.do.clickRewardButton();\nPosLoyalty.check.hasRewardLine('Free Product - Whiteboard Pen', '0.0', '1.00');\n\nPosLoyalty.check.orderTotalIs('10.2');\nPosLoyalty.exec.finalizeOrder('Cash');\n\nTour.register('PosLoyaltyLoyaltyProgram3', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\n\nProductScreen.do.clickHomeCategory();\nProductScreen.do.confirmOpeningPopup();\n\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('AAA Partner');\nProductScreen.exec.addOrderline('Test Product 1', '1.00', '100');\nProductScreen.check.totalAmountIs('80.00');\n\nTour.register('PosLoyaltyPromotion', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\n\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\n\n// Generates 10.2 points and use points to get the reward product with zero sale price\nProductScreen.exec.addOrderline('Desk Organizer', '3');\nPosLoyalty.exec.finalizeOrder('Cash');\n\nTour.register('PosLoyaltyNextOrderCouponExpirationDate', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\n\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\n\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('Test Partner');\n\nProductScreen.exec.addOrderline('Desk Organizer', '1');\nProductScreen.exec.addOrderline('Whiteboard Pen', '1');\n\nPosLoyalty.do.clickRewardButton();\n\nPosLoyalty.check.orderTotalIs('5.10');\nPosLoyalty.exec.finalizeOrder('Cash');\n\nTour.register('PosLoyaltyDontGrantPointsForRewardOrderLines', { test: true, url: '/pos/web' }, getSteps());\n", "/** @odoo-module **/\n\nimport { PosLoyalty } from 'pos_loyalty.tour.PosCouponTourMethods';\nimport { ProductScreen } from 'point_of_sale.tour.ProductScreenTourMethods';\nimport { SelectionPopup } from 'point_of_sale.tour.SelectionPopupTourMethods';\nimport { getSteps, startSteps } from 'point_of_sale.tour.utils';\nimport Tour from 'web_tour.tour';\n\nstartSteps();\n\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\n\nProductScreen.exec.addOrderline('Desk Organizer', '2');\n\n// At this point, the free_product program is triggered.\n// The reward button should be highlighted.\nPosLoyalty.check.isRewardButtonHighlighted(true);\n// Since the reward button is highlighted, clicking the reward product should be added as reward.\nProductScreen.do.clickDisplayedProduct('Desk Organizer');\nProductScreen.check.selectedOrderlineHas('Desk Organizer', '3.00');\nPosLoyalty.check.hasRewardLine('Free Product - Desk Organizer', '-5.10', '1.00');\n// In the succeeding 2 clicks on the product, it is considered as a regular product.\n// In the third click, the product will be added as reward.\nProductScreen.do.clickDisplayedProduct('Desk Organizer');\nProductScreen.do.clickDisplayedProduct('Desk Organizer');\nPosLoyalty.check.isRewardButtonHighlighted(true);\nProductScreen.do.clickDisplayedProduct('Desk Organizer');\nProductScreen.check.selectedOrderlineHas('Desk Organizer', '6.00');\nPosLoyalty.check.hasRewardLine('Free Product - Desk Organizer', '-10.20', '2.00');\n\n\nProductScreen.do.clickDisplayedProduct('Desk Organizer');\nPosLoyalty.check.isRewardButtonHighlighted(false);\nPosLoyalty.check.orderTotalIs('25.50');\n// Finalize order that consumed a reward.\nPosLoyalty.exec.finalizeOrder('Cash');\n\nProductScreen.do.clickDisplayedProduct('Desk Organizer');\nProductScreen.check.selectedOrderlineHas('Desk Organizer', '1.00');\nProductScreen.do.clickDisplayedProduct('Desk Organizer');\nProductScreen.check.selectedOrderlineHas('Desk Organizer', '2.00');\nProductScreen.do.clickDisplayedProduct('Desk Organizer');\nPosLoyalty.check.hasRewardLine('Free Product - Desk Organizer', '-5.10', '1.00');\nProductScreen.do.pressNumpad('Backspace');\nProductScreen.check.selectedOrderlineHas('Desk Organizer', '0.00');\nProductScreen.do.clickDisplayedProduct('Desk Organizer');\nProductScreen.check.selectedOrderlineHas('Desk Organizer', '1.00');\nProductScreen.do.clickDisplayedProduct('Desk Organizer');\nProductScreen.check.selectedOrderlineHas('Desk Organizer', '2.00');\nPosLoyalty.check.isRewardButtonHighlighted(true);\n// Finalize order but without the reward.\n// This step is important. When syncing the order, no reward should be synced.\nPosLoyalty.check.orderTotalIs('10.20');\nPosLoyalty.exec.finalizeOrder('Cash');\n\n\nProductScreen.exec.addOrderline('Magnetic Board', '2');\nPosLoyalty.check.isRewardButtonHighlighted(false);\nProductScreen.do.clickDisplayedProduct('Magnetic Board');\nPosLoyalty.check.isRewardButtonHighlighted(true);\nProductScreen.do.clickDisplayedProduct('Whiteboard Pen');\nPosLoyalty.check.isRewardButtonHighlighted(false);\nPosLoyalty.check.hasRewardLine('Free Product - Whiteboard Pen', '-3.20', '1.00');\nProductScreen.do.clickOrderline('Magnetic Board', '3.00');\nProductScreen.check.selectedOrderlineHas('Magnetic Board', '3.00');\nProductScreen.do.pressNumpad('6');\nProductScreen.check.selectedOrderlineHas('Magnetic Board', '6.00');\nPosLoyalty.check.isRewardButtonHighlighted(true);\nPosLoyalty.do.clickRewardButton();\nPosLoyalty.check.isRewardButtonHighlighted(false);\nPosLoyalty.check.hasRewardLine('Free Product - Whiteboard Pen', '-6.40', '2.00');\n// Finalize order that consumed a reward.\nPosLoyalty.check.orderTotalIs('11.88');\nPosLoyalty.exec.finalizeOrder('Cash');\n\nProductScreen.exec.addOrderline('Magnetic Board', '6');\nProductScreen.do.clickDisplayedProduct('Whiteboard Pen');\nPosLoyalty.check.hasRewardLine('Free Product - Whiteboard Pen', '-3.20', '1.00');\nPosLoyalty.check.isRewardButtonHighlighted(true);\n\nProductScreen.do.clickOrderline('Magnetic Board', '6.00');\nProductScreen.do.pressNumpad('Backspace');\n// At this point, the reward should have been removed.\nPosLoyalty.check.isRewardButtonHighlighted(false);\nProductScreen.check.selectedOrderlineHas('Magnetic Board', '0.00');\nProductScreen.do.clickDisplayedProduct('Magnetic Board');\nProductScreen.check.selectedOrderlineHas('Magnetic Board', '1.00');\nProductScreen.do.clickDisplayedProduct('Magnetic Board');\nProductScreen.check.selectedOrderlineHas('Magnetic Board', '2.00');\nProductScreen.do.clickDisplayedProduct('Magnetic Board');\nProductScreen.check.selectedOrderlineHas('Magnetic Board', '3.00');\nPosLoyalty.check.hasRewardLine('Free Product - Whiteboard Pen', '-3.20', '1.00');\nPosLoyalty.check.isRewardButtonHighlighted(false);\n\nPosLoyalty.check.orderTotalIs('5.94');\nPosLoyalty.exec.finalizeOrder('Cash');\n\n// Promotion: 2 items of shelves, get desk_pad/monitor_stand free\n// This is the 5th order.\nProductScreen.do.clickDisplayedProduct('Wall Shelf Unit');\nProductScreen.check.selectedOrderlineHas('Wall Shelf Unit', '1.00');\nPosLoyalty.check.isRewardButtonHighlighted(false);\nProductScreen.do.clickDisplayedProduct('Small Shelf');\nProductScreen.check.selectedOrderlineHas('Small Shelf', '1.00');\nPosLoyalty.check.isRewardButtonHighlighted(true);\n// Click reward product. Should be automatically added as reward.\nProductScreen.do.clickDisplayedProduct('Desk Pad');\nPosLoyalty.check.isRewardButtonHighlighted(false);\nPosLoyalty.check.hasRewardLine('Free Product', '-1.98', '1.00');\n// Remove the reward line. The next steps will check if cashier\n// can select from the different reward products.\nProductScreen.do.pressNumpad('Backspace');\nProductScreen.do.pressNumpad('Backspace');\nPosLoyalty.check.isRewardButtonHighlighted(true);\nPosLoyalty.do.clickRewardButton();\nSelectionPopup.check.hasSelectionItem('Monitor Stand');\nSelectionPopup.check.hasSelectionItem('Desk Pad');\nSelectionPopup.do.clickItem('Desk Pad');\nPosLoyalty.check.isRewardButtonHighlighted(false);\nPosLoyalty.check.hasRewardLine('Free Product', '-1.98', '1.00');\nProductScreen.do.pressNumpad('Backspace');\nProductScreen.do.pressNumpad('Backspace');\nPosLoyalty.check.isRewardButtonHighlighted(true);\nPosLoyalty.do.claimReward('Monitor Stand');\nPosLoyalty.check.isRewardButtonHighlighted(false);\nProductScreen.check.selectedOrderlineHas('Monitor Stand', '1.00', '3.19');\nPosLoyalty.check.hasRewardLine('Free Product', '-3.19', '1.00');\nPosLoyalty.check.orderTotalIs('4.81');\nPosLoyalty.exec.finalizeOrder('Cash');\n\nTour.register('PosLoyaltyFreeProductTour', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\n\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\n\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('AAA Partner');\nProductScreen.exec.addOrderline('Test Product A', '1');\nPosLoyalty.check.isRewardButtonHighlighted(true);\nPosLoyalty.do.clickRewardButton();\nPosLoyalty.check.hasRewardLine('Free Product - Test Product A', '-11.50', '1.00');\n\nTour.register('PosLoyaltyFreeProductTour2', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\n\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\n\nProductScreen.do.clickDisplayedProduct('Test Product A');\nProductScreen.check.selectedOrderlineHas('Test Product A', '1.00', '40.00');\nProductScreen.do.clickDisplayedProduct('Test Product B');\nProductScreen.check.selectedOrderlineHas('Test Product B', '1.00', '40.00');\nPosLoyalty.do.clickRewardButton();\nSelectionPopup.do.clickItem(\"$ 10 per order on specific products\");\nPosLoyalty.check.hasRewardLine('$ 10 per order on specific products', '-10.00', '1.00');\nPosLoyalty.check.orderTotalIs('70.00');\nPosLoyalty.do.clickRewardButton();\nSelectionPopup.do.clickItem(\"$ 10 per order on specific products\");\nPosLoyalty.check.orderTotalIs('60.00');\nPosLoyalty.do.clickRewardButton();\nSelectionPopup.do.clickItem(\"$ 30 per order on specific products\");\nPosLoyalty.check.hasRewardLine('$ 30 per order on specific products', '-30.00', '1.00');\nPosLoyalty.check.orderTotalIs('30.00');\n\nTour.register('PosLoyaltySpecificDiscountTour', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\n\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\n\nProductScreen.do.clickDisplayedProduct('Test Product A');\nProductScreen.do.clickDisplayedProduct('Test Product C');\nPosLoyalty.check.orderTotalIs('130.00');\nPosLoyalty.check.isRewardButtonHighlighted(true);\nPosLoyalty.do.clickRewardButton();\nPosLoyalty.check.orderTotalIs('130.00');\n\nTour.register('PosLoyaltySpecificDiscountWithFreeProductTour', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\n\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\n\nProductScreen.do.clickDisplayedProduct('Product A');\nProductScreen.check.selectedOrderlineHas('Product A', '1.00', '15.00');\nPosLoyalty.check.orderTotalIs('15.00');\n\nProductScreen.do.clickDisplayedProduct('Product B');\nProductScreen.check.selectedOrderlineHas('Product B', '1.00', '50.00');\nPosLoyalty.check.orderTotalIs('40.00');\n\nTour.register('PosLoyaltySpecificDiscountWithRewardProductDomainTour', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\n\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\n\nProductScreen.do.clickDisplayedProduct('Product A');\nProductScreen.check.selectedOrderlineHas('Product A', '1.00', '15.00');\nPosLoyalty.check.orderTotalIs('15.00');\n\nProductScreen.do.clickDisplayedProduct('Product B');\nProductScreen.check.selectedOrderlineHas('Product B', '1.00', '50.00');\nPosLoyalty.check.orderTotalIs('40.00');\n\nTour.register('PosLoyaltySpecificDiscountCategoryTour', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\n\nProductScreen.do.clickDisplayedProduct(\"Desk Organizer\");\nProductScreen.do.clickDisplayedProduct(\"Desk Organizer\");\nPosLoyalty.check.isRewardButtonHighlighted(true);\nPosLoyalty.do.clickRewardButton();\nSelectionPopup.do.clickItem(\"product_a\");\nPosLoyalty.check.hasRewardLine(\"Free Product\", \"-2\", \"1.00\");\nPosLoyalty.check.isRewardButtonHighlighted(false);\n\nProductScreen.do.clickDisplayedProduct(\"Desk Organizer\");\nProductScreen.do.clickDisplayedProduct(\"Desk Organizer\");\nPosLoyalty.check.isRewardButtonHighlighted(true);\nPosLoyalty.do.clickRewardButton();\nSelectionPopup.do.clickItem(\"product_b\");\nPosLoyalty.check.hasRewardLine(\"Free Product\", \"-5\", \"1.00\");\nPosLoyalty.check.isRewardButtonHighlighted(false);\n\nProductScreen.do.clickDisplayedProduct(\"Desk Organizer\");\nProductScreen.do.clickDisplayedProduct(\"Desk Organizer\");\nPosLoyalty.check.isRewardButtonHighlighted(true);\nPosLoyalty.do.clickRewardButton();\nSelectionPopup.do.clickItem(\"product_b\");\nPosLoyalty.check.hasRewardLine(\"Free Product\", \"-10\", \"2.00\");\nPosLoyalty.check.isRewardButtonHighlighted(false);\n\nTour.register(\"PosLoyaltyRewardProductTag\", { test: true, url: \"/pos/web\" }, getSteps());\n\nstartSteps();\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\nProductScreen.do.clickDisplayedProduct('Product A');\nPosLoyalty.do.enterCode('563412');\nPosLoyalty.check.hasRewardLine('10% on your order', '-1.50');\n\nTour.register(\"test_loyalty_on_order_with_fixed_tax\", { test: true, url: \"/pos/web\" }, getSteps());\n", "/** @odoo-module **/\n\nimport { PosLoyalty } from 'pos_loyalty.tour.PosCouponTourMethods';\nimport { ProductScreen } from 'point_of_sale.tour.ProductScreenTourMethods';\nimport { getSteps, startSteps } from 'point_of_sale.tour.utils';\nimport Tour from 'web_tour.tour';\n\n// --- PoS Loyalty Tour Basic Part 1 ---\n// Generate coupons for PosLoyaltyTour2.\nstartSteps();\n\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\n\n// basic order\n// just accept the automatically applied promo program\n// applied programs:\n// - on cheapest product\nProductScreen.exec.addOrderline('Whiteboard Pen', '5');\nPosLoyalty.check.hasRewardLine('90% on the cheapest product', '-2.88');\nPosLoyalty.do.selectRewardLine('on the cheapest product');\nPosLoyalty.check.orderTotalIs('13.12');\nPosLoyalty.exec.finalizeOrder('Cash');\n\n// remove the reward from auto promo program\n// no applied programs\nProductScreen.exec.addOrderline('Whiteboard Pen', '6');\nPosLoyalty.check.hasRewardLine('on the cheapest product', '-2.88');\nPosLoyalty.check.orderTotalIs('16.32');\nPosLoyalty.exec.removeRewardLine('90% on the cheapest product');\nPosLoyalty.check.orderTotalIs('19.2');\nPosLoyalty.exec.finalizeOrder('Cash');\n\n// order with coupon code from coupon program\n// applied programs:\n// - coupon program\nProductScreen.exec.addOrderline('Desk Organizer', '9');\nPosLoyalty.check.hasRewardLine('on the cheapest product', '-4.59');\nPosLoyalty.exec.removeRewardLine('90% on the cheapest product');\nPosLoyalty.check.orderTotalIs('45.90');\nPosLoyalty.do.enterCode('invalid_code');\nPosLoyalty.do.enterCode('1234');\nPosLoyalty.check.hasRewardLine('Free Product - Desk Organizer', '-15.30');\nPosLoyalty.exec.finalizeOrder('Cash');\n\n// Use coupon but eventually remove the reward\n// applied programs:\n// - on cheapest product\nProductScreen.exec.addOrderline('Letter Tray', '4');\nProductScreen.exec.addOrderline('Desk Organizer', '9');\nPosLoyalty.check.hasRewardLine('90% on the cheapest product', '-4.75');\nPosLoyalty.check.orderTotalIs('62.27');\nPosLoyalty.do.enterCode('5678');\nPosLoyalty.check.hasRewardLine('Free Product - Desk Organizer', '-15.30');\nPosLoyalty.check.orderTotalIs('46.97');\nPosLoyalty.exec.removeRewardLine('Free Product');\nPosLoyalty.check.orderTotalIs('62.27');\nPosLoyalty.exec.finalizeOrder('Cash');\n\n// specific product discount\n// applied programs:\n// - on cheapest product\n// - on specific products\nProductScreen.exec.addOrderline('Magnetic Board', '10') // 1.98\nProductScreen.exec.addOrderline('Desk Organizer', '3') // 5.1\nProductScreen.exec.addOrderline('Letter Tray', '4') // 4.8 tax 10%\nPosLoyalty.check.hasRewardLine('90% on the cheapest product', '-1.78')\nPosLoyalty.check.orderTotalIs('54.44')\nPosLoyalty.do.enterCode('promocode')\nPosLoyalty.check.hasRewardLine('50% on specific products', '-16.66') // 17.55 - 1.78*0.5\nPosLoyalty.check.orderTotalIs('37.78')\nPosLoyalty.exec.finalizeOrder('Cash')\n\nTour.register('PosLoyaltyTour1', { test: true, url: '/pos/web' }, getSteps());\n\n// --- PoS Loyalty Tour Basic Part 2 ---\n// Using the coupons generated from PosLoyaltyTour1.\nstartSteps();\n\nProductScreen.do.clickHomeCategory();\n\n// Test that global discount and cheapest product discounts can be accumulated.\n// Applied programs:\n// - global discount\n// - on cheapest discount\nProductScreen.exec.addOrderline('Desk Organizer', '10'); // 5.1\nPosLoyalty.check.hasRewardLine('on the cheapest product', '-4.59');\nProductScreen.exec.addOrderline('Letter Tray', '4'); // 4.8 tax 10%\nPosLoyalty.check.hasRewardLine('on the cheapest product', '-4.75');\nPosLoyalty.do.enterCode('123456');\nPosLoyalty.check.hasRewardLine('10% on your order', '-5.10');\nPosLoyalty.check.hasRewardLine('10% on your order', '-1.64');\nPosLoyalty.check.orderTotalIs('60.63'); //SUBTOTAL\nPosLoyalty.exec.finalizeOrder('Cash');\n\n// Scanning coupon twice.\n// Also apply global discount on top of free product to check if the\n// calculated discount is correct.\n// Applied programs:\n// - coupon program (free product)\n// - global discount\n// - on cheapest discount\nProductScreen.exec.addOrderline('Desk Organizer', '11'); // 5.1 per item\nPosLoyalty.check.hasRewardLine('90% on the cheapest product', '-4.59');\nPosLoyalty.check.orderTotalIs('51.51');\n// add global discount and the discount will be replaced\nPosLoyalty.do.enterCode('345678');\nPosLoyalty.check.hasRewardLine('10% on your order', '-5.15');\n// add free product coupon (for qty=11, free=4)\n// the discount should change after having free products\n// it should go back to cheapest discount as it is higher\nPosLoyalty.do.enterCode('5678');\nPosLoyalty.check.hasRewardLine('Free Product - Desk Organizer', '-20.40');\nPosLoyalty.check.hasRewardLine('90% on the cheapest product', '-4.59');\n// set quantity to 18\n// free qty stays the same since the amount of points on the card only allows for 4 free products\nProductScreen.do.pressNumpad('Backspace 8')\nPosLoyalty.check.hasRewardLine('10% on your order', '-6.68');\nPosLoyalty.check.hasRewardLine('Free Product - Desk Organizer', '-20.40');\n// scan the code again and check notification\nPosLoyalty.do.enterCode('5678');\nPosLoyalty.check.orderTotalIs('60.13');\nPosLoyalty.exec.finalizeOrder('Cash');\n\n// Specific products discount (with promocode) and free product (1357)\n// Applied programs:\n// - discount on specific products\n// - free product\nProductScreen.exec.addOrderline('Desk Organizer', '6'); // 5.1 per item\nPosLoyalty.check.hasRewardLine('on the cheapest product', '-4.59');\nPosLoyalty.exec.removeRewardLine('90% on the cheapest product');\nPosLoyalty.do.enterCode('promocode');\nPosLoyalty.check.hasRewardLine('50% on specific products', '-15.30');\nPosLoyalty.do.enterCode('1357');\nPosLoyalty.check.hasRewardLine('Free Product - Desk Organizer', '-10.20');\nPosLoyalty.check.hasRewardLine('50% on specific products', '-10.20');\nPosLoyalty.check.orderTotalIs('10.20');\nPosLoyalty.exec.finalizeOrder('Cash');\n\n// Check reset program\n// Enter two codes and reset the programs.\n// The codes should be checked afterwards. They should return to new.\n// Applied programs:\n// - cheapest product\nProductScreen.exec.addOrderline('Monitor Stand', '6'); // 3.19 per item\nPosLoyalty.do.enterCode('098765');\nPosLoyalty.check.hasRewardLine('90% on the cheapest product', '-2.87');\nPosLoyalty.check.hasRewardLine('10% on your order', '-1.63');\nPosLoyalty.check.orderTotalIs('14.64');\nPosLoyalty.exec.removeRewardLine('90% on the cheapest product');\nPosLoyalty.check.hasRewardLine('10% on your order', '-1.91');\nPosLoyalty.check.orderTotalIs('17.23');\nPosLoyalty.do.resetActivePrograms();\nPosLoyalty.check.hasRewardLine('90% on the cheapest product', '-2.87');\nPosLoyalty.check.orderTotalIs('16.27');\nPosLoyalty.exec.finalizeOrder('Cash');\n\nTour.register('PosLoyaltyTour2', { test: true, url: '/pos/web' }, getSteps());\n\n// --- PoS Loyalty Tour Basic Part 3 ---\n\nstartSteps();\n\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\n\nProductScreen.do.clickDisplayedProduct('Promo Product');\nPosLoyalty.check.orderTotalIs('34.50');\nProductScreen.do.clickDisplayedProduct('Product B');\nPosLoyalty.check.hasRewardLine('100% on specific products', '25.00');\nProductScreen.do.clickDisplayedProduct('Product A');\nPosLoyalty.check.hasRewardLine('100% on specific products', '15.00');\nPosLoyalty.check.orderTotalIs('34.50');\nProductScreen.do.clickDisplayedProduct('Product A');\nPosLoyalty.check.hasRewardLine('100% on specific products', '21.82');\nPosLoyalty.check.hasRewardLine('100% on specific products', '18.18');\nPosLoyalty.check.orderTotalIs('49.50');\n\n\nTour.register('PosLoyaltyTour3', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\n\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\n\nProductScreen.exec.addOrderline('Test Product 1', '1');\nProductScreen.exec.addOrderline('Test Product 2', '1');\nProductScreen.do.clickPricelistButton();\nProductScreen.do.selectPriceList('Public Pricelist');\nPosLoyalty.do.enterCode('abcda');\nPosLoyalty.check.orderTotalIs('0.00');\nProductScreen.do.clickPricelistButton();\nProductScreen.do.selectPriceList('Test multi-currency');\nPosLoyalty.check.orderTotalIs('0.00');\n\nTour.register('PosLoyaltyTour4', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\n\nProductScreen.do.clickHomeCategory();\n\nProductScreen.exec.addOrderline('Test Product 1', '1.00', '100');\nPosLoyalty.do.clickDiscountButton();\nPosLoyalty.do.clickConfirmButton();\nProductScreen.check.totalAmountIs('92.00');\n\nTour.register('PosLoyaltyTour5', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\n\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\n\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('AAA Partner');\nProductScreen.do.clickDisplayedProduct('Test Product A');\nPosLoyalty.do.clickRewardButton();\nProductScreen.check.totalAmountIs('139');\n\nTour.register('PosLoyaltyTour6', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\n\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\n\nProductScreen.exec.addOrderline('Test Product', '1');\nPosLoyalty.check.orderTotalIs('100');\nPosLoyalty.do.enterCode('abcda');\nPosLoyalty.check.orderTotalIs('90');\n\nTour.register('PosLoyaltyTour7', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\n\nProductScreen.do.clickHomeCategory();\nProductScreen.do.confirmOpeningPopup();\n\nProductScreen.do.clickDisplayedProduct('Product B');\nProductScreen.do.clickDisplayedProduct('Product A');\nProductScreen.check.totalAmountIs('50.00');\n\nTour.register('PosLoyaltyTour8', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\n\nProductScreen.do.clickHomeCategory();\nProductScreen.do.confirmOpeningPopup();\n\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('AAA Partner');\nProductScreen.do.clickDisplayedProduct('Product B');\nProductScreen.do.clickDisplayedProduct('Product A');\nProductScreen.check.totalAmountIs('210.00');\nPosLoyalty.check.isRewardButtonHighlighted(true);\nPosLoyalty.do.clickRewardButton();\nProductScreen.check.totalAmountIs('205.00');\nPosLoyalty.check.isRewardButtonHighlighted(true);\nPosLoyalty.do.clickRewardButton();\nProductScreen.check.totalAmountIs('200.00');\n\nTour.register('PosLoyaltyTour9', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\n\nProductScreen.do.clickHomeCategory();\nProductScreen.do.confirmOpeningPopup();\n\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('AAA Partner');\nProductScreen.do.clickDisplayedProduct('Product Test');\nProductScreen.check.totalAmountIs('1.00');\nPosLoyalty.check.isRewardButtonHighlighted(true);\nPosLoyalty.do.claimReward('Free Product B');\nPosLoyalty.check.hasRewardLine('Free Product B', '-1.00');\nProductScreen.check.totalAmountIs('1.00');\nPosLoyalty.check.isRewardButtonHighlighted(false);\n\nTour.register('PosLoyaltyTour10', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\n\nProductScreen.do.clickHomeCategory();\nProductScreen.do.confirmOpeningPopup();\n\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('AAA Partner');\nPosLoyalty.check.customerIs('AAA Partner');\nProductScreen.exec.addOrderline('Product Test', '3');\nProductScreen.check.totalAmountIs('150.00');\nPosLoyalty.check.isRewardButtonHighlighted(false);\nPosLoyalty.exec.finalizeOrder('Cash');\n\nTour.register('PosLoyaltyTour11.1', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\n\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer('AAA Partner');\nPosLoyalty.check.customerIs('AAA Partner');\nProductScreen.do.clickDisplayedProduct('Product Test');\nProductScreen.check.totalAmountIs('50.00');\nPosLoyalty.check.isRewardButtonHighlighted(false);\nPosLoyalty.do.enterCode('123456');\nPosLoyalty.check.isRewardButtonHighlighted(true);\nPosLoyalty.do.clickRewardButton();\nPosLoyalty.check.hasRewardLine('Free Product', '-3.00');\nPosLoyalty.check.isRewardButtonHighlighted(false);\nProductScreen.check.totalAmountIs('50.00');\nPosLoyalty.exec.finalizeOrder('Cash');\n\nTour.register('PosLoyaltyTour11.2', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\n\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\n\nProductScreen.exec.addOrderline('Free Product A', '2');\nProductScreen.do.clickDisplayedProduct('Free Product A');\nProductScreen.check.totalAmountIs('2.00');\nPosLoyalty.check.hasRewardLine('Free Product', '-1.00');\n\nProductScreen.exec.addOrderline('Free Product B', '2');\nProductScreen.do.clickDisplayedProduct('Free Product B');\nProductScreen.check.totalAmountIs('4.00');\nPosLoyalty.check.hasRewardLine('Free Product', '-2.00');\n\nProductScreen.exec.addOrderline('Free Product B', '5');\nProductScreen.do.clickDisplayedProduct('Free Product B');\nProductScreen.check.totalAmountIs('6.00');\nPosLoyalty.check.hasRewardLine('Free Product', '-3.00');\n\nTour.register('PosLoyaltyTour12', { test: true, url: '/pos/web' }, getSteps());\n\nstartSteps();\n\nProductScreen.do.clickHomeCategory();\nProductScreen.do.confirmOpeningPopup();\n\nProductScreen.do.clickDisplayedProduct('Product A');\nProductScreen.check.selectedOrderlineHas('Product A', '1.00', '20.00');\nPosLoyalty.check.orderTotalIs('20.00');\n\nProductScreen.do.clickDisplayedProduct('Product B');\nProductScreen.check.selectedOrderlineHas('Product B', '1.00', '30.00');\nPosLoyalty.check.orderTotalIs('50.00');\n\nProductScreen.do.clickDisplayedProduct('Product A');\nProductScreen.check.selectedOrderlineHas('Product A', '2.00', '40.00');\nPosLoyalty.check.orderTotalIs('66.00');\n\nTour.register('PosLoyaltyMinAmountAndSpecificProductTour', {test: true, url: '/pos/web'}, getSteps());\n\nfunction createOrderCoupon(totalAmount, couponName, couponAmount, loyaltyPoints) {\n return [\n ProductScreen.do.confirmOpeningPopup(),\n ProductScreen.do.clickHomeCategory(),\n ProductScreen.do.clickPartnerButton(),\n ProductScreen.do.clickCustomer(\"partner_a\"),\n ProductScreen.exec.addOrderline(\"product_a\", \"1\"),\n ProductScreen.exec.addOrderline(\"product_b\", \"1\"),\n PosLoyalty.do.enterCode(\"promocode\"),\n PosLoyalty.check.hasRewardLine(`${couponName}`, `${couponAmount}`),\n PosLoyalty.check.orderTotalIs(`${totalAmount}`),\n PosLoyalty.check.pointsAwardedAre(`${loyaltyPoints}`),\n PosLoyalty.exec.finalizeOrder(\"Cash\"),\n ];\n}\n\nstartSteps();\ncreateOrderCoupon(\"135.00\", \"10% on your order\", \"-15.00\", \"135\");\nTour.register(\"PosLoyaltyPointsDiscountNoDomainProgramNoDomain\", { test: true, url: \"/pos/web\" }, getSteps());\n\nstartSteps();\ncreateOrderCoupon(\"135.00\", \"10% on your order\", \"-15.00\", \"100\");\nTour.register(\"PosLoyaltyPointsDiscountNoDomainProgramDomain\", { test: true, url: \"/pos/web\" }, getSteps());\n\nstartSteps();\ncreateOrderCoupon(\"140.00\", \"10% on food\", \"-10.00\", \"90\");\nTour.register(\"PosLoyaltyPointsDiscountWithDomainProgramDomain\", { test: true, url: \"/pos/web\" }, getSteps());\n\nstartSteps();\nProductScreen.do.confirmOpeningPopup(),\nProductScreen.do.clickHomeCategory(),\nProductScreen.do.clickPartnerButton(),\nProductScreen.do.clickCustomer(\"partner_a\"),\nProductScreen.exec.addOrderline(\"product_a\", \"1\"),\nPosLoyalty.check.hasRewardLine('10% on your order', '-10.00');\nPosLoyalty.check.orderTotalIs('90'),\nPosLoyalty.check.pointsAwardedAre(\"90\"),\nPosLoyalty.exec.finalizeOrder(\"Cash\", \"90\"),\nTour.register(\"PosLoyaltyPointsGlobalDiscountProgramNoDomain\", { test: true, url: \"/pos/web\" }, getSteps());\n\nstartSteps();\n\nProductScreen.do.clickHomeCategory();\nProductScreen.do.confirmOpeningPopup();\n\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer(\"partner_a\");\n\nProductScreen.do.clickDisplayedProduct('Test Product A');\nPosLoyalty.check.checkNoClaimableRewards();\nProductScreen.check.selectedOrderlineHas('Test Product A', '1.00', '100.00');\nPosLoyalty.exec.finalizeOrder(\"Cash\");\n\nTour.register('PosLoyaltyArchivedRewardProductsInactive', {test: true, url: '/pos/web'}, getSteps());\n\nstartSteps();\n\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer(\"partner_a\");\n\nProductScreen.do.clickDisplayedProduct('Test Product A');\nPosLoyalty.check.isRewardButtonHighlighted(true);\nProductScreen.check.selectedOrderlineHas('Test Product A', '1.00', '100.00');\nPosLoyalty.exec.finalizeOrder(\"Cash\");\n\nTour.register('PosLoyaltyArchivedRewardProductsActive', {test: true, url: '/pos/web'}, getSteps());\n\nstartSteps();\n\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickPartnerButton();\nProductScreen.do.clickCustomer(\"partner_a\");\n\nProductScreen.exec.addOrderline(\"Test Product A\", \"5\"),\nProductScreen.do.clickDisplayedProduct('Test Product B');\nPosLoyalty.check.hasRewardLine('10% on your order', '-3.00');\nPosLoyalty.check.hasRewardLine('10% on Test Product B', '-0.45');\nPosLoyalty.exec.finalizeOrder(\"Cash\");\n\nTour.register('PosLoyalty2DiscountsSpecificGlobal', {test: true, url: '/pos/web'}, getSteps());\n", "odoo.define('pos_loyalty.tour.PosCouponTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n const { Do: ProductScreenDo } = require('point_of_sale.tour.ProductScreenTourMethods');\n const { Do: PaymentScreenDo, Check: PaymentScreenCheck } = require('point_of_sale.tour.PaymentScreenTourMethods');\n const { Do: ReceiptScreenDo } = require('point_of_sale.tour.ReceiptScreenTourMethods');\n const { Do: ChromeDo } = require('point_of_sale.tour.ChromeTourMethods');\n\n const ProductScreen = { do: new ProductScreenDo() };\n const PaymentScreen = { do: new PaymentScreenDo(), check: new PaymentScreenCheck() };\n const ReceiptScreen = { do: new ReceiptScreenDo() };\n const Chrome = { do: new ChromeDo() };\n\n class Do {\n selectRewardLine(rewardName) {\n return [\n {\n content: 'select reward line',\n trigger: `.orderline.program-reward .product-name:contains(\"${rewardName}\")`,\n },\n {\n content: 'check reward line if selected',\n trigger: `.orderline.selected.program-reward .product-name:contains(\"${rewardName}\")`,\n run: function () {}, // it's a check\n },\n ];\n }\n enterCode(code) {\n const steps = [\n {\n content: 'open code input dialog',\n trigger: '.control-button:contains(\"Enter Code\")',\n },\n {\n content: `enter code value: ${code}`,\n trigger: '.popup-textinput input[type=\"text\"]',\n run: `text ${code}`,\n },\n {\n content: 'confirm inputted code',\n trigger: '.popup-textinput .button.confirm',\n },\n {\n content: 'verify popup is closed',\n trigger: 'body:not(:has(.popup-textinput))',\n run: function () {}, // it's a check\n },\n ];\n return steps;\n }\n resetActivePrograms() {\n return [\n {\n content: 'open code input dialog',\n trigger: '.control-button:contains(\"Reset Programs\")',\n },\n ];\n }\n clickRewardButton() {\n return [\n {\n content: 'open reward dialog',\n trigger: '.control-button:contains(\"Reward\")',\n },\n ];\n }\n clickEWalletButton(text = 'eWallet') {\n return [{ trigger: `.control-button:contains(\"${text}\")` }];\n }\n claimReward(rewardName) {\n return [\n {\n content: 'open reward dialog',\n trigger: '.control-button:contains(\"Reward\")',\n },\n {\n content: 'select reward',\n trigger: `.selection-item:contains(\"${rewardName}\")`,\n }\n ];\n }\n unselectPartner() {\n return [{ trigger: '.unselect-tag' }];\n }\n clickDiscountButton() {\n return [\n {\n content: 'click discount button',\n trigger: '.js_discount',\n },\n ];\n }\n clickConfirmButton() {\n return [\n {\n content: 'click confirm button',\n trigger: '.button.confirm',\n },\n ];\n }\n }\n\n class Check {\n hasRewardLine(rewardName, amount, qty) {\n const steps = [\n {\n content: 'check if reward line is there',\n trigger: `.orderline.program-reward span.product-name:contains(\"${rewardName}\")`,\n run: function () {},\n },\n {\n content: 'check if the reward price is correct',\n trigger: `.orderline.program-reward span.price:contains(\"${amount}\")`,\n run: function () {},\n },\n ];\n if (qty) {\n steps.push({\n content: 'check if the reward qty is correct',\n trigger: `.order .orderline.program-reward .product-name:contains(\"${rewardName}\") ~ .info-list em:contains(\"${qty}\")`,\n run: function () {},\n });\n }\n return steps;\n }\n orderTotalIs(total_str) {\n return [\n {\n content: 'order total contains ' + total_str,\n trigger: '.order .total .value:contains(\"' + total_str + '\")',\n run: function () {}, // it's a check\n },\n ];\n }\n checkNoClaimableRewards() {\n return [\n {\n content: 'check that no reward can be claimed',\n trigger: \".control-button:contains('Reward'):not(.highlight)\",\n run: function () {}, // it's a check\n }\n ]\n }\n isRewardButtonHighlighted(isHighlighted) {\n return [\n {\n trigger: isHighlighted\n ? '.control-button.highlight:contains(\"Reward\")'\n : '.control-button:contains(\"Reward\"):not(:has(.highlight))',\n run: function () {}, // it's a check\n },\n ];\n }\n eWalletButtonState({ highlighted, text = 'eWallet' }) {\n return [\n {\n trigger: highlighted\n ? `.control-button.highlight:contains(\"${text}\")`\n : `.control-button:contains(\"${text}\"):not(:has(.highlight))`,\n run: function () {}, // it's a check\n },\n ];\n }\n customerIs(name) {\n return [\n {\n trigger: `.actionpad button.set-partner:contains(\"${name}\")`,\n run: function () {},\n }\n ]\n }\n pointsAwardedAre(points_str) {\n return [\n {\n content: 'loyalty points awarded ' + points_str,\n trigger: '.loyalty-points-won.value:contains(\"' + points_str + '\")',\n run: function () {}, // it's a check\n },\n ];\n }\n }\n\n class Execute {\n constructor() {\n this.do = new Do();\n this.check = new Check();\n }\n finalizeOrder(paymentMethod, amount) {\n const actions = [\n ...ProductScreen.do.clickPayButton(),\n ...PaymentScreen.do.clickPaymentMethod(paymentMethod),\n ];\n if (amount) {\n actions.push(...PaymentScreen.do.pressNumpad([...amount].join(' ')));\n } else {\n actions.push(\n ...PaymentScreen.check.remainingIs('0.0'),\n ...PaymentScreen.check.changeIs('0.0'),\n )\n }\n actions.push(\n ...PaymentScreen.do.clickValidate(),\n ...ReceiptScreen.do.clickNextOrder(),\n );\n return actions;\n }\n removeRewardLine(name) {\n return [\n ...this.do.selectRewardLine(name),\n ...ProductScreen.do.pressNumpad('Backspace'),\n ...Chrome.do.confirmPopup(),\n ];\n }\n }\n\n return createTourMethods('PosLoyalty', Do, Check, Execute);\n});\n", "/** @odoo-module **/\n\nimport { PosLoyalty } from 'pos_loyalty.tour.PosCouponTourMethods';\nimport { ProductScreen } from 'point_of_sale.tour.ProductScreenTourMethods';\nimport { getSteps, startSteps } from 'point_of_sale.tour.utils';\nimport Tour from 'web_tour.tour';\n\n// First tour should not get any automatic rewards\nstartSteps();\n\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickHomeCategory();\n\n// Not valid -> date\nProductScreen.exec.addOrderline('Whiteboard Pen', '5');\nPosLoyalty.check.checkNoClaimableRewards();\nPosLoyalty.exec.finalizeOrder('Cash');\n\nTour.register('PosLoyaltyValidity1', { test: true, url: '/pos/web' }, getSteps());\n\n// Second tour\nstartSteps();\n\nProductScreen.do.clickHomeCategory();\n\n// Valid\nProductScreen.exec.addOrderline('Whiteboard Pen', '5');\nPosLoyalty.check.hasRewardLine('90% on the cheapest product', '-2.88');\nPosLoyalty.exec.finalizeOrder('Cash');\n\n// Not valid -> usage\nProductScreen.exec.addOrderline('Whiteboard Pen', '5');\nPosLoyalty.check.checkNoClaimableRewards();\nPosLoyalty.exec.finalizeOrder('Cash');\n\nTour.register('PosLoyaltyValidity2', { test: true, url: '/pos/web' }, getSteps());\n", "odoo.define('website_profile.tour_website_profile_description', function (require) {\n 'use strict';\n\n var tour = require(\"web_tour.tour\");\n\n tour.register(\"website_profile_description\", {\n test: true,\n url: \"/profile/users\",\n }, [{\n content: \"Click on one user profile card\",\n trigger: \"div[onclick]:contains(\\\"test_user\\\")\",\n },{\n content: \"Edit profile\",\n trigger: \"a:contains('EDIT PROFILE')\",\n }, {\n content: \"Add some content\",\n trigger: \".odoo-editor-editable p\",\n run: \"text content

code here

\",\n }, {\n content: \"Save changes\",\n trigger: \"button:contains('Update')\",\n }, {\n content: \"Check the content is saved\",\n trigger:\n \"span[data-oe-field='website_description']:contains('content

code here

')\",\n }]\n );\n});\n", "/** @odoo-module **/\n\nimport slidesTourTools from '@website_slides/../tests/tours/slides_tour_tools';\nimport wTourUtils from 'website.tour_utils';\n\n/**\n * Global use case:\n * a user (website publisher) creates a course;\n * they update it;\n * they create some lessons in it;\n * they publish it;\n */\nwTourUtils.registerWebsitePreviewTour('course_publisher_standard', {\n url: '/slides',\n test: true,\n}, [{\n content: 'eLearning: click on New (top-menu)',\n trigger: 'div.o_new_content_container a'\n}, {\n content: 'eLearning: click on New Course',\n trigger: '#o_new_content_menu_choices a:contains(\"Course\")'\n}, {\n content: 'eLearning: set name',\n trigger: 'div[name=\"name\"] input',\n run: 'text How to D\u00e9boulonnate',\n}, {\n content: 'eLearning: click on tags',\n trigger: '.o_field_many2many_tags input',\n run: 'text Gard',\n}, {\n content: 'eLearning: select Gardening tag',\n trigger: '.ui-autocomplete a:contains(\"Gardening\")',\n in_modal: false,\n}, {\n content: 'eLearning: set description',\n trigger: '.o_field_html[name=\"description\"]',\n run: 'text D\u00e9boulonnate is very common at Fleurus',\n}, {\n content: 'eLearning: we want reviews',\n trigger: '.o_field_boolean[name=\"allow_comment\"] input',\n}, {\n content: 'eLearning: seems cool, create it',\n trigger: 'button:contains(\"Save\")',\n},\n...wTourUtils.clickOnEditAndWaitEditMode(),\n{\n content: 'eLearning: double click image to edit it',\n trigger: 'iframe img.o_wslides_course_pict',\n run: 'dblclick',\n}, {\n content: 'eLearning: click \"Add URL\" to trigger URL box',\n trigger: '.o_upload_media_url_button',\n}, {\n content: 'eLearning: add a bioutifoul URL',\n trigger: 'input.o_we_url_input',\n run: 'text https://upload.wikimedia.org/wikipedia/commons/thumb/1/12/ThreeTimeAKCGoldWinnerPembrookeWelshCorgi.jpg/800px-ThreeTimeAKCGoldWinnerPembrookeWelshCorgi.jpg'\n}, {\n content: 'eLearning: click \"Add URL\" really adding image',\n trigger: '.o_upload_media_url_button',\n extra_trigger: '.o_we_url_success',\n}, {\n content: 'eLearning: is the Corgi set ?',\n trigger: 'iframe img.o_wslides_course_pict',\n run: function () {\n const $imgCorgi = $('.o_website_preview iframe').contents().find('img.o_wslides_course_pict');\n const expectedImageUrlRegex=/GoldWinnerPembrookeWelshCorgi.jpg/;\n if (expectedImageUrlRegex.test($imgCorgi.attr('src'))) {\n $imgCorgi.addClass('o_wslides_tour_success');\n }\n },\n}, {\n content: 'eLearning: the Corgi is set !',\n trigger: 'iframe img.o_wslides_course_pict.o_wslides_tour_success',\n}, {\n content: 'eLearning: save course edition',\n trigger: 'button[data-action=\"save\"]',\n}, {\n content: 'eLearning: course create with current member',\n extra_trigger: 'iframe body:not(.editor_enable)', // wait for editor to close\n trigger: 'iframe .o_wslides_js_course_join:contains(\"You\\'re enrolled\")',\n run: function () {} // check membership\n}\n].concat(\n slidesTourTools.addExistingCourseTag(true),\n slidesTourTools.addNewCourseTag('The Most Awesome Course', true),\n slidesTourTools.addSection('Introduction', true),\n slidesTourTools.addArticleToSection('Introduction', 'MyArticle', true),\n [{\n content: \"eLearning: check editor is loaded for article\",\n trigger: 'iframe body.editor_enable',\n timeout: 30000,\n run: () => null, // it's a check\n}, {\n content: \"eLearning: save article\",\n trigger: '.o_we_website_top_actions button.btn-primary:contains(\"Save\")',\n}, {\n content: \"eLearning: use breadcrumb to go back to channel\",\n trigger: 'iframe .o_wslides_course_nav a:contains(\"D\u00e9boulonnate\")',\n}]\n// [\n// {\n// content: 'eLearning: move new course inside introduction',\n// trigger: 'div.o_wslides_slides_list_drag',\n// // run: 'drag_and_drop div.o_wslides_slides_list_drag ul.ui-sortable:first',\n// run: 'drag_and_drop div.o_wslides_slides_list_drag a.o_wslides_js_slide_section_add',\n// }]\n));\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\n\n/**\n * Global use case:\n * an user (either employee, website restricted editor or portal) joins a public\n course;\n * they have access to the full course content when they are a member of the\n course;\n * they use fullscreen player to complete the course;\n * they rate the course;\n */\ntour.register('course_member', {\n url: '/slides',\n test: true\n}, [\n// eLearning: go on free course and join it\n{\n trigger: 'a:contains(\"Basics of Gardening - Test\")'\n}, {\n trigger: 'a:contains(\"Join this Course\")'\n}, {\n trigger: '.o_wslides_js_course_join:contains(\"You\\'re enrolled\")',\n run: function () {} // check membership\n}, {\n trigger: 'a:contains(\"Gardening: The Know-How\")',\n},\n// eLearning: follow course by cliking on first lesson and going to fullscreen player\n{\n trigger: '.o_wslides_fs_slide_name:contains(\"Home Gardening\")',\n run: 'click',\n},\n// eLearning: share the first slide\n{\n trigger: '.o_wslides_fs_share'\n}, {\n trigger: '.o_wslides_js_share_email input',\n run: 'text friend@example.com'\n}, {\n trigger: '.o_wslides_js_share_email button',\n}, {\n trigger: '.o_wslides_js_share_email:contains(\"Sharing is caring\")',\n run: function () {} // check email has been sent\n}, {\n trigger: '.modal-footer button:contains(\"Close\")',\n},\n// eLeaning: course completion\n{\n trigger: '.o_wslides_fs_sidebar_header',\n run: function () {\n // check navigation with arrow keys\n var event = jQuery.Event(\"keydown\");\n event.key = \"ArrowLeft\";\n // go back once\n $(document).trigger(event);\n // check that it selected the previous tab\n if ($('.o_wslides_fs_sidebar_list_item.active:contains(\"Gardening: The Know-How\")').length === 0) {\n return;\n }\n // getting here means that navigation worked\n $('.o_wslides_fs_sidebar_header').addClass('navigation-success-1');\n }\n}, {\n trigger: '.o_wslides_fs_sidebar_header.navigation-success-1',\n extra_trigger: '.o_wslides_progress_percentage:contains(\"40\")',\n run: function () {\n // check navigation with arrow keys\n var event = jQuery.Event(\"keydown\");\n event.key = \"ArrowRight\";\n $(document).trigger(event);\n // check that it selected the next/next tab\n if ($('.o_wslides_fs_sidebar_list_item.active:contains(\"Home Gardening\")').length === 0) {\n return;\n }\n // getting here means that navigation worked\n $('.o_wslides_fs_sidebar_header').addClass('navigation-success-2');\n }\n}, {\n trigger: '.o_wslides_progress_percentage:contains(\"40\")',\n run: function () {} // check progression\n}, {\n trigger: '.o_wslides_fs_sidebar_header.navigation-success-2',\n extra_trigger: '.o_wslides_progress_percentage:contains(\"40\")',\n run: function () {\n // check navigation with arrow keys\n var event = jQuery.Event(\"keydown\");\n event.key = \"ArrowRight\";\n setTimeout(function () {\n $(document).trigger(event);\n // check that it selected the next/next tab\n if ($('.o_wslides_fs_sidebar_list_item.active:contains(\"Mighty Carrots\")').length === 0) {\n return;\n }\n // getting here means that navigation worked\n $('.o_wslides_fs_sidebar_header').addClass('navigation-success-3');\n }, 300);\n }\n}, {\n trigger: '.o_wslides_progress_percentage:contains(\"60\")',\n run: function () {} // check progression\n}, {\n trigger: '.o_wslides_fs_sidebar_header.navigation-success-3',\n extra_trigger: '.o_wslides_progress_percentage:contains(\"60\")',\n run: function () {} // check that previous step succeeded\n}, {\n trigger: '.o_wslides_fs_slide_name:contains(\"How to Grow and Harvest The Best Strawberries | Basics\")',\n run: 'click',\n}, {\n trigger: '.o_wslides_fs_sidebar_section_slides li:contains(\"How to Grow and Harvest The Best Strawberries | Basics\") .o_wslides_slide_completed',\n run: function () {} // check that video slide is marked as 'done'\n}, {\n trigger: '.o_wslides_progress_percentage:contains(\"80\")',\n run: function () {} // check progression\n},\n// eLearning: last slide is a quiz, complete it\n{\n trigger: '.o_wslides_fs_slide_name:contains(\"Test your knowledge\")',\n run: 'click',\n}, {\n trigger: '.o_wslides_js_lesson_quiz_question:first .list-group a:first'\n}, {\n trigger: '.o_wslides_js_lesson_quiz_question:last .list-group a:first'\n}, {\n trigger: '.o_wslides_js_lesson_quiz_submit'\n}, {\n // check that we have a properly motivational message to motivate us!\n trigger: '.o_wslides_quiz_modal_rank_motivational > div > div:contains(\"Reach the next rank and gain a very nice mug !\")'\n}, {\n trigger: 'a:contains(\"End course\")'\n},\n// eLearning: ending course redirect to /slides, course is completed now\n{\n trigger: 'div:contains(\"Basics of Gardening\") span:contains(\"Completed\")',\n run: function () {} // check that the course is marked as completed\n},\n// eLearning: go back on course and rate it (new rate or update it, both should work)\n{\n trigger: 'a:contains(\"Basics of Gardening\")'\n}, {\n trigger: 'button[data-bs-target=\"#ratingpopupcomposer\"]'\n}, {\n trigger: 'div.o_portal_chatter_composer_input i.fa:eq(2)',\n extra_trigger: 'div.modal_shown',\n run: 'click',\n in_modal: false,\n}, {\n trigger: 'div.o_portal_chatter_composer_input textarea',\n run: 'text This is a great course. Top !',\n in_modal: false,\n}, {\n trigger: 'button.o_portal_chatter_composer_btn',\n in_modal: false,\n}, {\n trigger: 'a[id=\"review-tab\"]'\n}, {\n trigger: '.o_portal_chatter_message:contains(\"This is a great course. Top !\")',\n run: function () {}, // check review is correctly added\n}\n]);\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\nimport FullScreen from '@website_slides/js/slides_course_fullscreen_player';\n\n/**\n * Alter this method for test purposes.\n * This will make the video start at 10 minutes.\n * As it lasts 10min24s, it will mark it as completed immediately.\n */\nFullScreen.include({\n _renderSlide: function () {\n\n var slide = this.get('slide');\n slide.embedUrl += '&start=260';\n this.set('slide', slide);\n\n return this._super.call(this, arguments);\n }\n});\n\n/**\n * Global use case:\n * an user (either employee, website restricted editor or portal) joins a public\n course;\n * they have access to the full course content when they are a member of the\n course;\n * they use fullscreen player to complete the course;\n * they rate the course;\n */\ntour.register('course_member_youtube', {\n url: '/slides',\n test: true\n}, [\n// eLearning: go on /all, find free course and join it\n{\n trigger: 'a.o_wslides_home_all_slides'\n}, {\n trigger: 'a:contains(\"Choose your wood\")'\n}, {\n trigger: 'a:contains(\"Join this Course\")'\n}, {\n trigger: '.o_wslides_js_course_join:contains(\"You\\'re enrolled\")',\n run: function () {} // check membership\n}, {\n trigger: 'a:contains(\"Comparing Hardness of Wood Species\")',\n}, {\n trigger: '.o_wslides_progress_percentage:contains(\"50\")',\n run: function () {} // check progression\n}, {\n trigger: 'a:contains(\"Wood Bending With Steam Box\")',\n}, {\n trigger: '.player',\n run: function () {} // check player loading\n}, {\n trigger: '.o_wslides_fs_sidebar_section_slides li:contains(\"Wood Bending With Steam Box\") .o_wslides_slide_completed',\n run: function () {} // check that video slide is marked as 'done'\n}, {\n trigger: '.o_wslides_progress_percentage:contains(\"100\")',\n run: function () {} // check progression\n}, {\n trigger: 'a:contains(\"Back to course\")'\n}\n]);\n", "/** @odoo-module **/\n\nimport wTourUtils from 'website.tour_utils';\nimport slidesTourTools from '@website_slides/../tests/tours/slides_tour_tools';\n\n/**\n * Global use case:\n * a user (website restricted editor) creates a course;\n * they update it;\n * they create some lessons in it;\n * they publishe it;\n */\nwTourUtils.registerWebsitePreviewTour('course_publisher', {\n // TODO: replace by wTourUtils.getClientActionURL when it's added\n url: '/slides',\n test: true\n}, [{\n content: 'eLearning: click on New (top-menu)',\n trigger: 'div.o_new_content_container a'\n}, {\n content: 'eLearning: click on New Course',\n trigger: '#o_new_content_menu_choices a:contains(\"Course\")'\n}, {\n content: 'eLearning: set name',\n trigger: 'input[name=\"name\"]',\n run: 'text How to D\u00e9boulonnate',\n}, {\n content: 'eLearning: click on tags',\n trigger: '.o_field_many2manytags input',\n run: 'text Gard',\n}, {\n content: 'eLearning: select gardener tag',\n trigger: '.ui-autocomplete a:contains(\"Gardener\")',\n in_modal: false,\n}, {\n content: 'eLearning: set description',\n trigger: '.o_field_html[name=\"description\"]',\n run: 'text D\u00e9boulonnate is very common at Fleurus',\n}, {\n content: 'eLearning: we want reviews',\n trigger: '.o_field_boolean[name=\"allow_comment\"] input',\n}, {\n content: 'eLearning: seems cool, create it',\n trigger: 'button:contains(\"Save\")',\n},\n...wTourUtils.clickOnEditAndWaitEditMode(),\n{\n content: 'eLearning: double click image to edit it',\n trigger: 'iframe img.o_wslides_course_pict',\n run: 'dblclick',\n}, {\n content: 'eLearning: click \"Add URL\" to trigger URL box',\n trigger: '.o_upload_media_url_button',\n}, {\n content: 'eLearning: add a bioutifoul URL',\n trigger: 'input.o_we_url_input',\n run: 'text https://upload.wikimedia.org/wikipedia/commons/thumb/1/12/ThreeTimeAKCGoldWinnerPembrookeWelshCorgi.jpg/800px-ThreeTimeAKCGoldWinnerPembrookeWelshCorgi.jpg'\n}, {\n content: 'eLearning: click \"Add URL\" really adding image',\n trigger: '.o_upload_media_url_button',\n extra_trigger: '.o_we_url_success',\n}, {\n content: 'eLearning: is the Corgi set ?',\n trigger: 'iframe img.o_wslides_course_pict',\n run: function () {\n const $imgCorgi = $('.o_website_preview iframe').contents().find('img.o_wslides_course_pict');\n if ($imgCorgi.attr('src').endsWith('GoldWinnerPembrookeWelshCorgi.jpg')) {\n $imgCorgi.addClass('o_wslides_tour_success');\n }\n },\n}, {\n content: 'eLearning: the Corgi is set !',\n trigger: 'iframe img.o_wslides_course_pict.o_wslides_tour_success',\n}, {\n content: 'eLearning: save course edition',\n trigger: 'button[data-action=\"save\"]',\n}, {\n content: 'eLearning: course create with current member',\n extra_trigger: 'iframe body:not(.editor_enable)', // wait for editor to close\n trigger: 'iframe .o_wslides_js_course_join:contains(\"You\\'re enrolled\")',\n run: function () {} // check membership\n}\n].concat(\n slidesTourTools.addExistingCourseTag(true),\n slidesTourTools.addNewCourseTag('The Most Awesome Course', true),\n slidesTourTools.addSection('Introduction', true),\n slidesTourTools.addVideoToSection('Introduction', false, true),\n [{\n content: 'eLearning: publish newly added course',\n trigger: 'iframe span:contains(\"Dschinghis Khan - Dschinghis Khan (1979)\")', // wait for slide to appear\n // trigger: 'span.o_wslides_js_slide_toggle_is_preview:first',\n run: function () {\n $('.o_website_preview iframe').contents().find('span.o_wslides_js_slide_toggle_is_preview:first')[0].click();\n }\n}]\n// [\n// {\n// content: 'eLearning: move new course inside introduction',\n// trigger: 'div.o_wslides_slides_list_drag',\n// // run: 'drag_and_drop div.o_wslides_slides_list_drag ul.ui-sortable:first',\n// run: 'drag_and_drop div.o_wslides_slides_list_drag a.o_wslides_js_slide_section_add',\n// }]\n));\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\n\n/**\n * This tour test that a log note isn't considered\n * as a course review. And also that a member can\n * add only one review.\n */\ntour.register('course_reviews', {\n url: '/slides',\n test: true\n}, [\n{\n trigger: 'a:contains(\"Basics of Gardening - Test\")',\n}, {\n trigger: 'a[id=\"review-tab\"]',\n}, {\n trigger: '.o_portal_chatter_message:contains(\"Log note\")',\n run: function() {},\n}, {\n trigger: 'span:contains(\"Add Review\")',\n // If it fails here, it means the log note is considered as a review\n}, {\n trigger: 'div.o_portal_chatter_composer_body textarea',\n extra_trigger: 'div.modal_shown',\n run: 'text Great course!',\n in_modal: false,\n}, {\n trigger: 'button.o_portal_chatter_composer_btn',\n in_modal: false,\n}, {\n trigger: 'a[id=\"review-tab\"]',\n}, {\n trigger: 'button:contains(\"Visible\")',\n}, {\n trigger: 'span:contains(\"Edit Review\")',\n // If it fails here, it means the system is allowing you to add another review.\n}, {\n trigger: 'div.o_portal_chatter_composer_body textarea:contains(\"Great course!\")',\n run: function() {},\n}\n]);\n", "/** @odoo-module **/\n\nimport wTourUtils from 'website.tour_utils';\n\n/**\n * Global use case:\n * - a user (website restricted editor) lands on the fullscreen view of a course ;\n * - they click on the website editor \"Edit\" button ;\n * - they are redirected to the non-fullscreen view with the editor opened.\n *\n * This tour tests a fix made when editing a course in fullscreen view.\n * See \"Fullscreen#_onWebEditorClick\" for more information.\n *\n */\n wTourUtils.registerWebsitePreviewTour('full_screen_web_editor', {\n url: '/slides',\n test: true,\n}, [{\n // open to the course\n trigger: 'iframe a:contains(\"Basics of Gardening\")'\n}, {\n // click on a slide to open the fullscreen view\n trigger: 'iframe a.o_wslides_js_slides_list_slide_link:contains(\"Home Gardening\")'\n}, {\n trigger: 'iframe .o_wslides_fs_main',\n run: function () {} // check we land on the fullscreen view\n},\n...wTourUtils.clickOnEditAndWaitEditMode()\n, {\n trigger: 'iframe .o_wslides_lesson_main',\n run: function () {} // check we are redirected on the detailed view\n}]);\n", "/** @odoo-module **/\n\n/*\n * PUBLISHER / CONTENT CREATION\n */\n\nvar addSection = function (sectionName, backend = false) {\n\treturn [\n{\n content: 'eLearning: click on Add Section',\n trigger: (backend ? 'iframe ' : '' ) + 'a.o_wslides_js_slide_section_add',\n}, {\n content: 'eLearning: set section name',\n trigger: (backend ? 'iframe ' : '' ) + 'input[name=\"name\"]',\n run: 'text ' + sectionName,\n}, {\n content: 'eLearning: create section',\n trigger: (backend ? 'iframe ' : '' ) + 'footer.modal-footer button:contains(\"Save\")'\n}, {\n\tcontent: 'eLearning: section created empty',\n\ttrigger: (backend ? 'iframe ' : '' ) + 'div.o_wslides_slide_list_category_header:contains(\"' + sectionName + '\")',\n}];\n};\n\nvar addVideoToSection = function (sectionName, saveAsDraft, backend = false) {\n\tvar base_steps = [\n{\n\tcontent: 'eLearning: add content to section',\n\ttrigger: (backend ? 'iframe ' : '' ) + 'div.o_wslides_slide_list_category_header:contains(\"' + sectionName + '\") a:contains(\"Add Content\")',\n}, {\n\tcontent: 'eLearning: click on video',\n\ttrigger: (backend ? 'iframe ' : '' ) + 'a[data-slide-category=video]',\n}, {\n\tcontent: 'eLearning: fill video link',\n\ttrigger: (backend ? 'iframe ' : '' ) + 'input[name=video_url]',\n\trun: 'text https://www.youtube.com/watch?v=pzmI3vAIhbE',\n}, {\n content: 'eLearning: click outside to trigger onchange',\n trigger: (backend ? 'iframe ' : '' ) + 'div.o_w_slide_upload_modal_container',\n run: ({ tip_widget }) => {\n\t\ttip_widget.$anchor[0].click();\n\t},\n}];\n\tif (saveAsDraft) {\n\t\tbase_steps = [].concat(base_steps, [{\n content: 'eLearning: save as draft slide',\n extra_trigger: (backend ? 'iframe ' : '' ) + 'div.o_slide_preview img:not([src=\"/website_slides/static/src/img/document.png\"])', // wait for onchange to perform its duty\n trigger: (backend ? 'iframe ' : '' ) + 'footer.modal-footer button:contains(\"Save as Draft\")',\n}]);\n\t}\n\telse {\n\t\tbase_steps = [].concat(base_steps, [{\n content: 'eLearning: create and publish slide',\n extra_trigger: (backend ? 'iframe ' : '' ) + 'div.o_slide_preview img:not([src=\"/website_slides/static/src/img/document.png\"])', // wait for onchange to perform its duty\n trigger: (backend ? 'iframe ' : '' ) + 'footer.modal-footer button:contains(\"Publish\")',\n}]);\n\t}\n\treturn base_steps;\n};\n\nvar addArticleToSection = function (sectionName, pageName, backend) {\n\treturn [\n{\n\tcontent: 'eLearning: add content to section',\n\ttrigger: (backend ? 'iframe ' : '' ) + 'div.o_wslides_slide_list_category_header:contains(\"' + sectionName + '\") a:contains(\"Add Content\")',\n}, {\n\tcontent: 'eLearning: click on article',\n\ttrigger: (backend ? 'iframe ' : '' ) + 'a[data-slide-category=article]',\n}, {\n\tcontent: 'eLearning: fill article title',\n\ttrigger: (backend ? 'iframe ' : '' ) + 'input[name=name]',\n\trun: 'text ' + pageName,\n}, {\n content: 'eLearning: click on tags',\n trigger: (backend ? 'iframe ' : '' ) + 'ul.select2-choices:first',\n}, {\n content: 'eLearning: select Practice tag',\n trigger: (backend ? 'iframe ' : '' ) + 'div.select2-result-label:contains(\"Practice\")',\n in_modal: false,\n}, {\n\tcontent: 'eLearning: fill article completion time',\n\ttrigger: (backend ? 'iframe ' : '' ) + 'input[name=duration]',\n\trun: 'text 4',\n}, {\n content: 'eLearning: create and publish slide',\n trigger: (backend ? 'iframe ' : '' ) + 'footer.modal-footer button:contains(\"Publish\")',\n}];\n};\n\nvar addExistingCourseTag = function (backend = false) {\n\treturn [\n{\n content: 'eLearning: click on Add Tag',\n trigger: (backend ? 'iframe ' : '' ) + 'a.o_wslides_js_channel_tag_add',\n}, {\n content: 'eLearning: click on tag dropdown',\n trigger: (backend ? 'iframe ' : '' ) + 'a.select2-choice:first',\n}, {\n content: 'eLearning: select advanced tag',\n trigger: (backend ? 'iframe ' : '' ) + 'div.select2-result-label:contains(\"Advanced\")',\n in_modal: false,\n}, {\n content: 'eLearning: add existing course tag',\n trigger: (backend ? 'iframe ' : '' ) + 'footer.modal-footer button:contains(\"Add\")'\n}, {\n\tcontent: 'eLearning: check that modal is closed',\n\ttrigger: (backend ? 'iframe ' : '' ) + 'body:not(.modal-open)',\n}];\n};\n\nvar addNewCourseTag = function (courseTagName, backend) {\n\treturn [\n{\n content: 'eLearning: click on Add Tag',\n trigger: (backend ? 'iframe ' : '' ) + 'a.o_wslides_js_channel_tag_add',\n}, {\n content: 'eLearning: click on tag dropdown',\n\ttrigger: (backend ? 'iframe ' : '' ) + 'a.select2-choice:first',\n}, {\n content: 'eLearning: add a new course tag',\n\ttrigger: (backend ? 'iframe ' : '' ) + 'a.select2-choice:first',\n\trun: function () {\n\t\t// directly add new tag since we can assume select2 works correctly\n\t\tlet $jq = $;\n\t\tif (backend) {\n\t\t\t$jq = $('.o_website_preview iframe:not(.o_ignore_in_tour)').contents()[0].defaultView.$;\n\t\t}\n\t\t$jq('#tag_id').select2('data', {id:'123', text: courseTagName, create: true});\n\t\t$jq('#tag_id').trigger('change');\n\t}\n}, {\n content: 'eLearning: click on tag group dropdown',\n\ttrigger: (backend ? 'iframe ' : '' ) + 'a.select2-choice:last',\n}, {\n\tcontent: 'eLearning: select Tags tag group',\n trigger: (backend ? 'iframe ' : '' ) + 'div.select2-result-label:contains(\"Tags\")',\n\tin_modal: false,\n}, {\n content: 'eLearning: add new course tag',\n trigger: (backend ? 'iframe ' : '' ) + 'footer.modal-footer button:contains(\"Add\")'\n}, {\n\tcontent: 'eLearning: check that modal is closed',\n\ttrigger: (backend ? 'iframe ' : '' ) + 'body:not(.modal-open)',\n}];\n};\n\nexport default {\n\taddSection: addSection,\n\taddVideoToSection: addVideoToSection,\n\taddArticleToSection: addArticleToSection,\n\taddExistingCourseTag: addExistingCourseTag,\n\taddNewCourseTag: addNewCourseTag,\n};\n", "/** @odoo-module */\n\nimport tour from \"web_tour.tour\";\n\nconst requestChatSteps = [\n {\n trigger: \".o_livechat_button\",\n run: \"click\",\n },\n {\n trigger: \".o_thread_window\",\n },\n];\n\ntour.register(\"im_livechat_request_chat\", { test: true }, requestChatSteps);\n\ntour.register(\"im_livechat_request_chat_and_send_message\", { test: true }, [\n ...requestChatSteps,\n {\n trigger: \".o_composer_text_field\",\n run: \"text Hello, I need help please !\",\n },\n {\n trigger: '.o_composer_text_field',\n run() {\n $(\".o_composer_text_field\").trigger($.Event(\"keydown\", { which: 13 }));\n },\n },\n {\n trigger: \".o_thread_message:contains('Hello, I need help')\",\n },\n]);\n", "/** @odoo-module */\n\nimport tour from \"web_tour.tour\";\n\nconst commonSteps = [tour.stepUtils.showAppsMenuItem(), {\n trigger: '.o_app[data-menu-xmlid=\"im_livechat.menu_livechat_root\"]',\n}, {\n trigger: 'button[data-menu-xmlid=\"im_livechat.livechat_config\"]',\n}, {\n trigger: 'a[data-menu-xmlid=\"im_livechat.chatbot_config\"]',\n}, {\n trigger: '.o_list_button_add',\n}, {\n trigger: 'input[id=\"title\"]',\n run: 'text Test Chatbot Sequence'\n}, {\n trigger: 'div[name=\"script_step_ids\"] .o_field_x2many_list_row_add a'\n}, {\n trigger: 'textarea#message',\n run: 'text Step 1'\n}, {\n trigger: 'button:contains(\"Save & New\")'\n}, {\n trigger: 'tr:contains(\"Step 1\")',\n in_modal: false,\n run: () => {}\n}, {\n trigger: 'textarea#message',\n run: 'text Step 2'\n}, {\n trigger: 'button:contains(\"Save & New\")'\n}, {\n trigger: 'tr:contains(\"Step 2\")',\n in_modal: false,\n run: () => {}\n}, {\n trigger: 'textarea#message',\n run: 'text Step 3'\n}];\n\n\n/**\n * Simply create a few steps in order to check the sequences.\n */\n tour.register('im_livechat_chatbot_steps_sequence_tour', {\n test: true,\n url: '/web',\n}, [\n ...commonSteps, {\n trigger: 'button:contains(\"Save & Close\")'\n}, {\n trigger: 'body.o_web_client:not(.modal-open)',\n run() {},\n}, ...tour.stepUtils.discardForm()\n]);\n\n/**\n * Same as above, with an extra drag&drop at the end.\n */\ntour.register('im_livechat_chatbot_steps_sequence_with_move_tour', {\n test: true,\n url: '/web',\n}, [\n ...commonSteps, {\n trigger: 'button:contains(\"Save & New\")'\n}, {\n trigger: 'tr:contains(\"Step 3\")',\n in_modal: false,\n run: () => {}\n}, {\n trigger: 'textarea#message',\n run: 'text Step 4'\n}, {\n trigger: 'button:contains(\"Save & New\")'\n}, {\n trigger: 'tr:contains(\"Step 4\")',\n in_modal: false,\n run: () => {}\n}, {\n trigger: 'textarea#message',\n run: 'text Step 5'\n}, {\n trigger: 'button:contains(\"Save & Close\")'\n}, {\n trigger: 'body.o_web_client:not(.modal-open)',\n run: () => {}\n}, {\n trigger: 'tr:contains(\"Step 5\") .o_row_handle',\n run: () => {\n // move 'step 5' between 'step 1' and 'step 2'\n const from = document.querySelector('div[name=\"script_step_ids\"] tr:nth-child(5) .o_row_handle');\n const fromPosition = from.getBoundingClientRect();\n fromPosition.x += from.offsetWidth / 2;\n fromPosition.y += from.offsetHeight / 2;\n\n const to = document.querySelector('div[name=\"script_step_ids\"] tr:nth-child(2) .o_row_handle');\n from.dispatchEvent(new Event(\"mouseenter\", { bubbles: true }));\n from.dispatchEvent(new MouseEvent(\"mousedown\", {\n bubbles: true,\n which: 1,\n button: 0,\n clientX: fromPosition.x,\n clientY: fromPosition.y}));\n from.dispatchEvent(new MouseEvent(\"mousemove\", {\n bubbles: true,\n which: 1,\n button: 0,\n // dragging is only enabled when the mouse have moved from at least 10 pixels from the original position\n clientX: fromPosition.x + 20,\n clientY: fromPosition.y + 20,\n }));\n to.dispatchEvent(new Event(\"mouseenter\", { bubbles: true }));\n from.dispatchEvent(new Event(\"mouseup\", { bubbles: true }));\n }\n}, {\n trigger: 'div[name=\"script_step_ids\"] .o_field_x2many_list_row_add a'\n}, {\n trigger: 'textarea#message',\n run: 'text Step 6'\n}, {\n trigger: 'button:contains(\"Save & Close\")'\n}, {\n trigger: 'body.o_web_client:not(.modal-open)',\n run: () => {}\n}, {\n trigger: 'tr:contains(\"Step 6\")',\n in_modal: false,\n run: () => {}\n}, ...tour.stepUtils.discardForm(),\n]);\n", "odoo.define('survey.tour_test_certification_failure', function (require) {\n'use strict';\n\nvar SurveyFormWidget = require('survey.form');\n/**\n * Speed up fade-in fade-out to avoid useless delay in tests.\n */\nSurveyFormWidget.include({\n _submitForm: function () {\n this.fadeInOutDelay = 0;\n return this._super.apply(this, arguments);\n }\n});\n\n/**\n * This tour will test that, for the demo certification allowing 2 attempts, a user can\n * try and fail twice and will no longer be able to take the certification.\n */\n\nvar tour = require('web_tour.tour');\n\nvar failSteps = [{ // Page-1\n content: \"Clicking on Start Certification\",\n trigger: 'button.btn.btn-primary.btn-lg:contains(\"Start Certification\")',\n}, { // Question: Do we sell Acoustic Bloc Screens?\n content: \"Selecting answer 'No'\",\n trigger: 'div.js_question-wrapper:contains(\"Do we sell Acoustic Bloc Screens\") label:contains(\"No\")',\n}, { // Question: Select all the existing products\n content: \"Ticking answer 'Fanta'\",\n trigger: 'div.js_question-wrapper:contains(\"Select all the existing products\") label:contains(\"Fanta\")'\n}, {\n content: \"Ticking answer 'Drawer'\",\n trigger: 'div.js_question-wrapper:contains(\"Select all the existing products\") label:contains(\"Drawer\")'\n}, {\n content: \"Ticking answer 'Conference chair'\",\n trigger: 'div.js_question-wrapper:contains(\"Select all the existing products\") label:contains(\"Conference chair\")'\n}, { // Question: Select all the available customizations for our Customizable Desk\n content: \"Ticking answer 'Color'\",\n trigger: 'div.js_question-wrapper:contains(\"Select all the available customizations for our Customizable Desk\") label:contains(\"Color\")'\n}, {\n content: \"Ticking answer 'Height'\",\n trigger: 'div.js_question-wrapper:contains(\"Select all the available customizations for our Customizable Desk\") label:contains(\"Height\")'\n}, { // Question: How many versions of the Corner Desk do we have?\n content: \"Selecting answer '2'\",\n trigger: 'div.js_question-wrapper:contains(\"How many versions of the Corner Desk do we have\") label:contains(\"2\")',\n}, { // Question: Do you think we have missing products in our catalog? (not rated)\n content: \"Missing products\",\n trigger: 'div.js_question-wrapper:contains(\"Do you think we have missing products in our catalog\") textarea',\n run: \"text I don't know products enough to be able to answer that\",\n}, { // Page-2 Question: How much do we sell our Cable Management Box?\n content: \"Selecting answer '$80'\",\n trigger: 'div.js_question-wrapper:contains(\"How much do we sell our Cable Management Box\") label:contains(\"$80\")',\n}, { // Question: Select all the products that sell for $100 or more\n content: \"Ticking answer 'Corner Desk Right Sit'\",\n trigger: 'div.js_question-wrapper:contains(\"Select all the products that sell for $100 or more\") label:contains(\"Corner Desk Right Sit\")'\n}, {\n content: \"Ticking answer 'Desk Combination'\",\n trigger: 'div.js_question-wrapper:contains(\"Select all the products that sell for $100 or more\") label:contains(\"Desk Combination\")'\n}, {\n content: \"Ticking answer 'Office Chair Black'\",\n trigger: 'div.js_question-wrapper:contains(\"Select all the products that sell for $100 or more\") label:contains(\"Office Chair Black\")'\n}, { // Question: What do you think about our prices (not rated)?\n trigger: 'div.js_question-wrapper:contains(\"What do you think about our prices\") label:contains(\"Correctly priced\")',\n}, { // Page-3 Question: How many days is our money-back guarantee?\n content: \"Inputting answer '60'\",\n trigger: 'div.js_question-wrapper:contains(\"How many days is our money-back guarantee\") input',\n run: 'text 60'\n}, { // Question: If a customer purchases a product on 6 January 2020, what is the latest day we expect to ship it?\n content: \"Inputting answer '01/06/2020'\",\n trigger: 'div.js_question-wrapper:contains(\"If a customer purchases a product on 6 January 2020, what is the latest day we expect to ship it\") input',\n run: 'text 01/06/2020'\n}, { // Question: If a customer purchases a 1 year warranty on 6 January 2020, when do we expect the warranty to expire?\n content: \"Inputting answer '01/06/2021 00:00:01'\",\n trigger: 'div.js_question-wrapper:contains(\"If a customer purchases a 1 year warranty on 6 January 2020, when do we expect the warranty to expire\") input',\n run: 'text 01/06/2021 00:00:01'\n}, { // Question: What day to you think is best for us to start having an annual sale (not rated)?\n trigger: 'div.js_question-wrapper:contains(\"What day to you think is best for us to start having an annual sale (not rated)\") input',\n}, { // Question: What day and time do you think most customers are most likely to call customer service (not rated)?\n trigger: 'div.js_question-wrapper:contains(\"What day and time do you think most customers are most likely to call customer service (not rated)\") input',\n}, { // Question: How many chairs do you think we should aim to sell in a year (not rated)?\n content: \"Inputting answer '0'\",\n trigger: 'div.js_question-wrapper:contains(\"How many chairs do you think we should aim to sell in a year (not rated)\") input',\n run: 'text 0'\n}, {\n content: \"Finish Survey\",\n trigger: 'button[type=\"submit\"]',\n}];\n\nvar retrySteps = [{\n trigger: 'a:contains(\"Retry\")'\n}];\n\nvar lastSteps = [{\n trigger: 'h1:contains(\"Thank you!\")',\n run: function () {\n if ($('a:contains(\"Retry\")').length === 0) {\n $('h1:contains(\"Thank you!\")').addClass('tour_success');\n }\n }\n}, {\n trigger: 'h1.tour_success',\n}];\n\ntour.register('test_certification_failure', {\n test: true,\n url: '/survey/start/4ead4bc8-b8f2-4760-a682-1fde8daaaaac'\n}, [].concat(failSteps, retrySteps, failSteps, lastSteps));\n\n});\n", "odoo.define('survey.tour_test_certification_success', function (require) {\n'use strict';\n\nvar SurveyFormWidget = require('survey.form');\n/**\n * Speed up fade-in fade-out to avoid useless delay in tests.\n */\nSurveyFormWidget.include({\n _submitForm: function () {\n this.fadeInOutDelay = 0;\n return this._super.apply(this, arguments);\n }\n});\n\nvar tour = require('web_tour.tour');\n\ntour.register('test_certification_success', {\n test: true,\n url: '/survey/start/4ead4bc8-b8f2-4760-a682-1fde8daaaaac'\n},\n[{ // Page-1\n content: \"Clicking on Start Certification\",\n trigger: 'button.btn.btn-primary.btn-lg:contains(\"Start Certification\")',\n }, { // Question: Do we sell Acoustic Bloc Screens?\n content: \"Selecting answer 'Yes'\",\n trigger: 'div.js_question-wrapper:contains(\"Do we sell Acoustic Bloc Screens\") label:contains(\"Yes\")',\n }, { // Question: Select all the existing products\n content: \"Ticking answer 'Chair floor protection'\",\n trigger: 'div.js_question-wrapper:contains(\"Select all the existing products\") label:contains(\"Chair floor protection\")'\n }, {\n content: \"Ticking answer 'Drawer'\",\n trigger: 'div.js_question-wrapper:contains(\"Select all the existing products\") label:contains(\"Drawer\")'\n }, {\n content: \"Ticking answer 'Conference chair'\",\n trigger: 'div.js_question-wrapper:contains(\"Select all the existing products\") label:contains(\"Conference chair\")'\n }, { // Question: Select all the available customizations for our Customizable Desk\n content: \"Ticking answer 'Color'\",\n trigger: 'div.js_question-wrapper:contains(\"Select all the available customizations for our Customizable Desk\") label:contains(\"Color\")'\n }, {\n content: \"Ticking answer 'Legs'\",\n trigger: 'div.js_question-wrapper:contains(\"Select all the available customizations for our Customizable Desk\") label:contains(\"Legs\")'\n }, { // Question: How many versions of the Corner Desk do we have?\n content: \"Selecting answer '2'\",\n trigger: 'div.js_question-wrapper:contains(\"How many versions of the Corner Desk do we have\") label:contains(\"2\")',\n }, { // Question: Do you think we have missing products in our catalog? (not rated)\n content: \"Missing products\",\n trigger: 'div.js_question-wrapper:contains(\"Do you think we have missing products in our catalog\") textarea',\n run: \"text I think we should make more versions of the customizable desk, it's such an amazing product!\",\n }, { // Page-2 Question: How much do we sell our Cable Management Box?\n content: \"Selecting answer '$80' (wrong one)\",\n trigger: 'div.js_question-wrapper:contains(\"How much do we sell our Cable Management Box\") label:contains(\"$80\")',\n }, { // Question: Select all the products that sell for $100 or more\n content: \"Ticking answer 'Corner Desk Right Sit'\",\n trigger: 'div.js_question-wrapper:contains(\"Select all the products that sell for $100 or more\") label:contains(\"Corner Desk Right Sit\")'\n }, {\n content: \"Ticking answer 'Desk Combination'\",\n trigger: 'div.js_question-wrapper:contains(\"Select all the products that sell for $100 or more\") label:contains(\"Desk Combination\")'\n }, {\n content: \"Ticking answer 'Large Desk'\",\n trigger: 'div.js_question-wrapper:contains(\"Select all the products that sell for $100 or more\") label:contains(\"Large Desk\")'\n }, { // Question: What do you think about our prices (not rated)?\n content: \"Selecting answer 'Underpriced'\",\n trigger: 'div.js_question-wrapper:contains(\"What do you think about our prices\") label:contains(\"Underpriced\")',\n }, { // Page-3 Question: How many days is our money-back guarantee?\n content: \"Inputting answer '30'\",\n trigger: 'div.js_question-wrapper:contains(\"How many days is our money-back guarantee\") input',\n run: 'text 30'\n }, { // Question: If a customer purchases a product on 6 January 2020, what is the latest day we expect to ship it?\n content: \"Inputting answer '01/08/2020'\",\n trigger: 'div.js_question-wrapper:contains(\"If a customer purchases a product on 6 January 2020, what is the latest day we expect to ship it\") input',\n run: 'text 01/08/2020'\n }, { // Question: If a customer purchases a 1 year warranty on 6 January 2020, when do we expect the warranty to expire?\n content: \"Inputting answer '01/07/2021 00:00:01'\",\n trigger: 'div.js_question-wrapper:contains(\"If a customer purchases a 1 year warranty on 6 January 2020, when do we expect the warranty to expire\") input',\n run: 'text 01/07/2021 00:00:01'\n }, { // Question: What day to you think is best for us to start having an annual sale (not rated)?\n content: \"Inputting answer '01/01/2021'\",\n trigger: 'div.js_question-wrapper:contains(\"What day to you think is best for us to start having an annual sale (not rated)\") input',\n run: 'text 01/01/2021'\n }, { // Question: What day and time do you think most customers are most likely to call customer service (not rated)?\n content: \"Inputting answer '01/01/2021 13:00:01'\",\n trigger: 'div.js_question-wrapper:contains(\"What day and time do you think most customers are most likely to call customer service (not rated)\") input',\n run: 'text 01/01/2021 13:00:01'\n }, { // Question: How many chairs do you think we should aim to sell in a year (not rated)?\n content: \"Inputting answer '1000'\",\n trigger: 'div.js_question-wrapper:contains(\"How many chairs do you think we should aim to sell in a year (not rated)\") input',\n run: 'text 1000'\n }, {\n content: \"Finish Survey\",\n trigger: 'button[type=\"submit\"]',\n }, {\n content: \"Thank you\",\n trigger: 'h1:contains(\"Thank you!\")',\n }, {\n content: \"test passed\",\n trigger: 'div:contains(\"Congratulations, you have passed the test!\")',\n }\n]);\n\n});\n", "odoo.define('survey.tour_test_survey', function (require) {\n'use strict';\n\nvar tour = require('web_tour.tour');\n\ntour.register('test_survey', {\n test: true,\n url: '/survey/start/b137640d-14d4-4748-9ef6-344caaaaaae',\n}, [\n // Page-1\n {\n content: 'Click on Start',\n trigger: 'button.btn:contains(\"Start\")',\n }, {\n content: 'Answer Where do you live',\n trigger: 'div.js_question-wrapper:contains(\"Where do you live\") input',\n run: 'text Mordor-les-bains',\n }, {\n content: 'Answer Where do you live',\n trigger: 'div.js_question-wrapper:contains(\"When is your date of birth\") input',\n run: 'text 05/05/1980',\n }, {\n content: 'Answer How frequently do you buy products online',\n trigger: 'div.js_question-wrapper:contains(\"How frequently do you buy products online\") label:contains(\"Once a month\")',\n }, {\n content: 'Answer How many times did you order products on our website',\n trigger: 'div.js_question-wrapper:contains(\"How many times did you order products on our website\") input',\n run: 'text 12',\n }, {\n content: 'Submit and go to Next Page',\n trigger: 'button[value=\"next\"]',\n },\n // Page-2\n {\n content: 'Answer Which of the following words would you use to describe our products (High Quality)',\n trigger: 'div.js_question-wrapper:contains(\"Which of the following words would you use to describe our products\") label:contains(\"High quality\")',\n }, {\n content: 'Answer Which of the following words would you use to describe our products (Good value for money)',\n trigger: 'div.js_question-wrapper:contains(\"Which of the following words would you use to describe our products\") label:contains(\"Good value for money\")',\n }, {\n content: 'Answer What do your think about our new eCommerce (The new layout and design is fresh and up-to-date)',\n trigger: 'div.js_question-wrapper:contains(\"What do your think about our new eCommerce\") tr:contains(\"The new layout and design is fresh and up-to-date\") td:first',\n }, {\n content: 'Answer What do your think about our new eCommerce (It is easy to find the product that I want)',\n trigger: 'div.js_question-wrapper:contains(\"What do your think about our new eCommerce\") tr:contains(\"It is easy to find the product that I want\") td:eq(2)',\n }, {\n content: 'Answer What do your think about our new eCommerce (The tool to compare the products is useful to make a choice)',\n trigger: 'div.js_question-wrapper:contains(\"What do your think about our new eCommerce\") tr:contains(\"The tool to compare the products is useful to make a choice\") td:eq(3)',\n }, {\n content: 'Answer What do your think about our new eCommerce (The checkout process is clear and secure)',\n trigger: 'div.js_question-wrapper:contains(\"What do your think about our new eCommerce\") tr:contains(\"The checkout process is clear and secure\") td:eq(2)',\n }, {\n content: 'Answer What do your think about our new eCommerce (I have added products to my wishlist)',\n trigger: 'div.js_question-wrapper:contains(\"What do your think about our new eCommerce\") tr:contains(\"I have added products to my wishlist\") td:last',\n }, {\n content: 'Answer Do you have any other comments, questions, or concerns',\n trigger: 'div.js_question-wrapper:contains(\"Do you have any other comments, questions, or concerns\") textarea',\n run: 'text This is great. Really.',\n }, {\n content: 'Click Submit and finish the survey',\n trigger: 'button[value=\"finish\"]',\n },\n // Final page\n {\n content: 'Thank you',\n trigger: 'h1:contains(\"Thank you!\")',\n }\n]);\n\n});\n", "odoo.define('survey.tour_test_survey_chained_conditional_questions', function (require) {\n'use strict';\n\nconst tour = require('web_tour.tour');\n\ntour.register('test_survey_chained_conditional_questions', {\n test: true,\n url: '/survey/start/3cfadce3-3f7e-41da-920d-10fa0eb19527',\n}, [\n {\n content: 'Click on Start',\n trigger: 'button.btn:contains(\"Start\")',\n }, {\n content: 'Answer Q1 with Answer 2',\n trigger: 'div.js_question-wrapper:contains(\"Q1\") label:contains(\"Answer 2\")',\n }, {\n content: 'Check that Q4 and Q5 are visible',\n trigger: 'div.js_question-wrapper:contains(Q4)',\n extra_trigger: 'div.js_question-wrapper:contains(Q5)',\n run: () => {\n const selector = 'div.js_question-wrapper.d-none';\n if (document.querySelectorAll(selector).length !== 2) {\n throw new Error('Q2 and Q3 should be hidden.');\n }\n },\n }, {\n content: 'Answer Q1 with Answer 1',\n trigger: 'div.js_question-wrapper:contains(\"Q1\") label:contains(\"Answer 1\")',\n }, {\n content: 'Answer Q2 with Answer 1',\n trigger: 'div.js_question-wrapper:contains(\"Q2\") label:contains(\"Answer 1\")',\n run: function (actions) {\n const selector = 'div.js_question-wrapper.d-none';\n if (document.querySelectorAll(selector).length !== 3) {\n throw new Error('Q3, Q4 and Q5 should be hidden.');\n }\n // Select Answer 1, in order to trigger the display of Q3.\n actions.click(this.$anchor);\n }\n }, {\n content: 'Answer Q3 with Answer 1',\n trigger: 'div.js_question-wrapper:contains(\"Q3\") label:contains(\"Answer 1\")',\n }, {\n content: 'Answer Q1 with Answer 3', // This should hide all remaining questions.\n trigger: 'div.js_question-wrapper:contains(\"Q1\") label:contains(\"Answer 3\")',\n }, {\n content: 'Check that only question 1 is now visible',\n trigger: 'div.js_question-wrapper:contains(\"Q1\")',\n run: () => {\n const selector = 'div.js_question-wrapper.d-none';\n if (document.querySelectorAll(selector).length !== 4) {\n throw new Error('Q2, Q3, Q4 and Q5 should have been hidden.');\n }\n }\n }, {\n content: 'Click Submit and finish the survey',\n trigger: 'button[value=\"finish\"]',\n },\n // Final page\n {\n content: 'Thank you',\n trigger: 'h1:contains(\"Thank you!\")',\n }\n\n]);\n\n});\n", "odoo.define('survey.tour_test_survey_prefill', function (require) {\n'use strict';\n\nvar tour = require('web_tour.tour');\n\ntour.register('test_survey_prefill', {\n test: true,\n url: '/survey/start/b137640d-14d4-4748-9ef6-344caaaaaae'\n},\n[{ // Page-1\n trigger: 'button.btn.btn-primary.btn-lg:contains(\"Start Survey\")',\n }, { // Question: Where do you live ?\n trigger: 'div.js_question-wrapper:contains(\"Where do you live ?\") input',\n run: 'text Grand-Rosiere',\n }, { // Question: When is your date of birth ?\n trigger: 'div.js_question-wrapper:contains(\"When is your date of birth ?\") input',\n run: 'text 05/05/1980',\n }, { // Question: How frequently do you buy products online ?\n trigger: 'div.js_question-wrapper:contains(\"How frequently do you buy products online ?\") label:contains(\"Once a week\")',\n }, { // Question: How many times did you order products on our website ?\n trigger: 'div.js_question-wrapper:contains(\"How many times did you order products on our website ?\") input',\n run: 'text 42',\n }, {\n content: 'Click on Next Page',\n trigger: 'button[value=\"next\"]',\n },\n // Page-2\n { // Question: Which of the following words would you use to describe our products ?\n content: 'Answer Which of the following words would you use to describe our products (High Quality)',\n trigger: 'div.js_question-wrapper:contains(\"Which of the following words would you use to describe our products\") label:contains(\"High quality\")',\n }, {\n content: 'Answer Which of the following words would you use to describe our products (Good value for money)',\n trigger: 'div.js_question-wrapper:contains(\"Which of the following words would you use to describe our products\") label:contains(\"Good value for money\")',\n }, {\n content: 'Answer What do your think about our new eCommerce (The new layout and design is fresh and up-to-date)',\n trigger: 'div.js_question-wrapper:contains(\"What do your think about our new eCommerce\") tr:contains(\"The new layout and design is fresh and up-to-date\") td:first',\n }, {\n content: 'Answer What do your think about our new eCommerce (It is easy to find the product that I want)',\n trigger: 'div.js_question-wrapper:contains(\"What do your think about our new eCommerce\") tr:contains(\"It is easy to find the product that I want\") td:eq(2)',\n }, {\n content: 'Answer What do your think about our new eCommerce (The tool to compare the products is useful to make a choice)',\n trigger: 'div.js_question-wrapper:contains(\"What do your think about our new eCommerce\") tr:contains(\"The tool to compare the products is useful to make a choice\") td:eq(3)',\n }, {\n content: 'Answer What do your think about our new eCommerce (The checkout process is clear and secure)',\n trigger: 'div.js_question-wrapper:contains(\"What do your think about our new eCommerce\") tr:contains(\"The checkout process is clear and secure\") td:eq(2)',\n }, {\n content: 'Answer What do your think about our new eCommerce (I have added products to my wishlist)',\n trigger: 'div.js_question-wrapper:contains(\"What do your think about our new eCommerce\") tr:contains(\"I have added products to my wishlist\") td:last',\n }, {\n content: 'Answer Do you have any other comments, questions, or concerns',\n trigger: 'div.js_question-wrapper:contains(\"Do you have any other comments, questions, or concerns\") textarea',\n run: 'text Is the prefill working?',\n }, {\n // Go back to previous page\n content: 'Click on the previous page name in the breadcrumb',\n trigger: 'ol.breadcrumb a:first',\n }, {\n trigger: 'div.js_question-wrapper:contains(\"How many times did you order products on our website ?\") input',\n run: function () {\n var $inputQ3 = $('div.js_question-wrapper:contains(\"How many times did you order products on our website ?\") input');\n if ($inputQ3.val() === '42.0') {\n $('.o_survey_title').addClass('prefilled');\n }\n }\n }, {\n trigger: '.o_survey_title.prefilled',\n run: function () {\n // check that all the answers are prefilled in Page 1\n var $inputQ1 = $('div.js_question-wrapper:contains(\"Where do you live ?\") input');\n if ($inputQ1.val() !== 'Grand-Rosiere') {\n return;\n }\n\n var $inputQ2 = $('div.js_question-wrapper:contains(\"When is your date of birth ?\") input');\n if ($inputQ2.val() !== '05/05/1980') {\n return;\n }\n\n var $inputQ3 = $('div.js_question-wrapper:contains(\"How frequently do you buy products online ?\") label:contains(\"Once a week\") input');\n if (!$inputQ3.is(':checked')) {\n return;\n }\n\n var $inputQ4 = $('div.js_question-wrapper:contains(\"How many times did you order products on our website ?\") input');\n if ($inputQ4.val() !== '42.0') {\n return;\n }\n\n $('.o_survey_title').addClass('tour_success');\n }\n }, {\n trigger: '.o_survey_title.tour_success'\n }, {\n content: 'Click on Next Page',\n trigger: 'button[value=\"next\"]',\n }, {\n trigger: 'div.js_question-wrapper:contains(\"Do you have any other comments, questions, or concerns\") textarea',\n run: function () {\n var $inputQ3 = $('div.js_question-wrapper:contains(\"Do you have any other comments, questions, or concerns\") textarea');\n if ($inputQ3.val() === \"Is the prefill working?\") {\n $('.o_survey_title').addClass('prefilled2');\n }\n }\n }, {\n trigger: '.o_survey_title.prefilled2',\n run: function () {\n // check that all the answers are prefilled in Page 2\n var $input1Q1 = $('div.js_question-wrapper:contains(\"Which of the following words would you use to describe our products\") label:contains(\"High quality\") input');\n if (!$input1Q1.is(':checked')) {\n return;\n }\n\n var $input2Q1 = $('div.js_question-wrapper:contains(\"Which of the following words would you use to describe our products\") label:contains(\"Good value for money\") input');\n if (!$input2Q1.is(':checked')) {\n return;\n }\n\n var $input1Q2 = $('div.js_question-wrapper:contains(\"What do your think about our new eCommerce\") tr:contains(\"The new layout and design is fresh and up-to-date\") input:first');\n if (!$input1Q2.is(':checked')) {\n return;\n }\n\n var $input2Q2 = $('div.js_question-wrapper:contains(\"What do your think about our new eCommerce\") tr:contains(\"It is easy to find the product that I want\") input:eq(2)');\n if (!$input2Q2.is(':checked')) {\n return;\n }\n\n var $input3Q2 = $('div.js_question-wrapper:contains(\"What do your think about our new eCommerce\") tr:contains(\"The tool to compare the products is useful to make a choice\") input:eq(3)');\n if (!$input3Q2.is(':checked')) {\n return;\n }\n\n var $input4Q2 = $('div.js_question-wrapper:contains(\"What do your think about our new eCommerce\") tr:contains(\"The checkout process is clear and secure\") input:eq(2)');\n if (!$input4Q2.is(':checked')) {\n return;\n }\n\n var $input5Q2 = $('div.js_question-wrapper:contains(\"What do your think about our new eCommerce\") tr:contains(\"I have added products to my wishlist\") input:last');\n if (!$input5Q2.is(':checked')) {\n return;\n }\n\n var $inputQ3 = $('div.js_question-wrapper:contains(\"Do you have any other comments, questions, or concerns\") textarea');\n if ($inputQ3.val() !== \"Is the prefill working?\") {\n return;\n }\n\n $('.o_survey_title').addClass('tour_success_2');\n }\n }, {\n trigger: '.o_survey_title.tour_success_2'\n }\n]);\n\n});\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\n\ntour.register('hr_skills_tour', {\n test: true,\n url: '/web',\n}, [\n tour.stepUtils.showAppsMenuItem(),\n {\n content: \"Open Employees app\",\n trigger: \".o_app[data-menu-xmlid='hr.menu_hr_root']\",\n },\n {\n content: \"Create a new employee\",\n trigger: \".o-kanban-button-new\",\n },\n {\n content: \"Pick a name\",\n trigger: \".o_field_widget[name='name'] input\",\n run: \"text Jony McHallyFace\",\n },\n {\n content: \"Save\",\n trigger: \".o_form_button_save\",\n },\n {\n content: \"Add a new Resume experience\",\n trigger: \".o_field_resume_one2many tr.o_resume_group_header button.btn-secondary\",\n },\n {\n content: \"Enter some company name\",\n trigger: \".modal-body .o_field_widget[name='name'] input\",\n run: \"text Mamie Rock\",\n },\n {\n content: \"Set start date\",\n trigger: \".o_field_widget[name='date_start'] input\",\n run: \"text 12/05/2017\",\n },\n {\n content: \"Give some description\",\n trigger: \".o_field_widget[name='description'] textarea\",\n run: \"text Sang some songs and played some music\",\n },\n {\n content: \"Save it\",\n trigger: \".o_form_button_save\",\n in_modal: true,\n run: \"click\",\n },\n {\n content: \"Edit newly created experience\",\n trigger: \".o_resume_line_title:contains('Mamie Rock')\",\n run: \"click\",\n },\n {\n content: \"Change type\",\n trigger: \".o_field_widget[name='line_type_id'] input\",\n run: \"text Experience\",\n },\n {\n content: \"Choose experience\",\n trigger: '.ui-autocomplete .ui-menu-item a:contains(\"Experience\")',\n run: \"click\",\n },\n {\n content: \"Save experience change\",\n trigger: \".o_form_button_save\",\n in_modal: true,\n run: \"click\",\n },\n {\n content: \"Add a new Skill\",\n trigger: \".o_field_skills_one2many button:contains('Create a new entry')\",\n },\n {\n content: \"Select Music\",\n trigger: \".o_field_widget[name='skill_type_id'] label:contains('Best Music')\",\n run: \"click\",\n },\n {\n content: \"Select a song\",\n trigger: \".o_field_widget[name='skill_id'] input\",\n run: \"text Fortun\",\n },\n {\n content: \"Choose the song\",\n trigger: '.ui-autocomplete .ui-menu-item a:contains(\"Fortunate Son\")',\n run: \"click\",\n },\n {\n content: \"Select a level\",\n trigger: \".o_field_widget[name='skill_level_id'] input\",\n run: \"text Level\",\n },\n {\n content: \"Choose the level\",\n trigger: '.ui-autocomplete .ui-menu-item a:contains(\"Level 2\")',\n run: \"click\",\n },\n {\n content: \"Save new skill\",\n trigger: \".o_form_button_save\",\n in_modal: true,\n run: \"click\",\n },\n {\n content: \"Add a new Skill\",\n trigger: \".o_field_skills_one2many button:contains('ADD')\",\n },\n {\n content: \"Select a song\", // \"Music\" should be already selected\n trigger: \".o_field_widget[name='skill_id'] input\",\n run: \"text Mary\",\n },\n {\n content: \"Choose the song\",\n trigger: '.ui-autocomplete .ui-menu-item a:contains(\"Oh Mary\")',\n run: \"click\",\n },\n {\n content: \"Select a level\",\n trigger: \".o_field_widget[name='skill_level_id'] input\",\n run: \"text Level 7\",\n },\n {\n content: \"Choose the level\",\n trigger: '.ui-autocomplete .ui-menu-item a:contains(\"Level 7\")',\n run: \"click\",\n },\n {\n content: \"Save new skill\",\n trigger: \".o_form_button_save\",\n in_modal: true,\n run: \"click\",\n },\n ...tour.stepUtils.saveForm({\n content: \"save Form\",\n extra_trigger: 'td:containsExact(\"Oh Mary\")',\n }),\n {\n content: \"Go back to employees\",\n trigger: 'a[data-menu-xmlid=\"hr.menu_hr_root\"]',\n run: \"click\",\n }\n]);\n", "odoo.define('website_hr_recruitment.tour', function(require) {\n 'use strict';\n\n var tour = require(\"web_tour.tour\");\n const wTourUtils = require(\"website.tour_utils\");\n\n function applyForAJob(jobName, application) {\n return [{\n content: \"Select Job\",\n trigger: `.oe_website_jobs h3 span:contains(${jobName})`,\n }, {\n content: \"Apply\",\n trigger: \".js_hr_recruitment a:contains('Apply')\",\n }, {\n content: \"Complete name\",\n trigger: \"input[name=partner_name]\",\n run: `text ${application.name}`,\n }, {\n content: \"Complete Email\",\n trigger: \"input[name=email_from]\",\n run: `text ${application.email}`,\n }, {\n content: \"Complete phone number\",\n trigger: \"input[name=partner_mobile]\",\n run: `text ${application.phone}`,\n }, {\n content: \"Complete Subject\",\n trigger: \"textarea[name=description]\",\n run: `text ${application.subject}`,\n }, { // TODO: Upload a file ?\n content: \"Send the form\",\n trigger: \".s_website_form_send\",\n }, {\n content: \"Check the form is submitted without errors\",\n trigger: \".oe_structure:has(h1:contains('Congratulations'))\",\n }];\n }\n\n tour.register('website_hr_recruitment_tour', {\n test: true,\n url: '/jobs',\n }, [\n ...applyForAJob('Guru', {\n name: 'John Smith',\n email: 'john@smith.com',\n phone: '118.218',\n subject: '### [GURU] HR RECRUITMENT TEST DATA ###',\n }),\n {\n content: \"Go back to the jobs page\",\n trigger: \"body\",\n run: () => {\n window.location.href = '/jobs';\n },\n },\n ...applyForAJob('Internship', {\n name: 'Jack Doe',\n email: 'jack@doe.com',\n phone: '118.712',\n subject: '### HR [INTERN] RECRUITMENT TEST DATA ###',\n }),\n ]);\n\n wTourUtils.registerWebsitePreviewTour('website_hr_recruitment_tour_edit_form', {\n test: true,\n url: '/jobs',\n }, [{\n content: 'Go to the Guru job page',\n trigger: 'iframe a[href*=\"guru\"]',\n }, {\n content: 'Go to the Guru job form',\n trigger: 'iframe a[href*=\"apply\"]',\n }, {\n content: 'Check if the Guru form is present',\n trigger: 'iframe form'\n },\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n content: 'Add a fake default value for the job_id field',\n trigger: \"body\",\n run: () => {\n // It must be done in this way because the editor does not allow to\n // put a default value on a field with type=\"hidden\".\n document.querySelector('.o_iframe:not(.o_ignore_in_tour)').contentDocument.querySelector('input[name=\"job_id\"]').value = 'FAKE_JOB_ID_DEFAULT_VAL';\n },\n }, {\n content: 'Make the department_id field visible',\n trigger: \"body\",\n run: () => {\n const departmentEl = document.querySelector('.o_iframe:not(.o_ignore_in_tour)').contentDocument.querySelector('input[name=\"department_id\"]');\n departmentEl.type = 'text';\n departmentEl.closest('.s_website_form_field').classList.remove('s_website_form_dnone');\n },\n }, {\n content: 'Focus on department_id field',\n trigger: 'iframe input[name=\"department_id\"]',\n }, {\n content: 'Add a fake default value for the department_id field',\n trigger: 'we-input[data-attribute-name=\"value\"] input',\n run: 'text FAKE_DEPARTMENT_ID_DEFAULT_VAL',\n }, {\n content: 'Save',\n trigger: 'button[data-action=\"save\"]',\n }, {\n content: 'Go back to /jobs page after save',\n trigger: 'iframe body:not(.editor_enable)',\n run: () => {\n window.location.href = wTourUtils.getClientActionUrl('/jobs');\n }\n }, {\n content: 'Go to the Internship job page',\n trigger: 'iframe a[href*=\"internship\"]',\n }, {\n content: 'Go to the Internship job form',\n trigger: 'iframe a[href*=\"apply\"]',\n }, {\n content: 'Check that a job_id has been loaded',\n trigger: 'iframe form',\n run: () => {\n const selector =\n 'input[name=\"job_id\"]:not([value=\"\"]):not([value = \"FAKE_JOB_ID_DEFAULT_VAL\"])';\n if (!document.querySelector('.o_iframe:not(.o_ignore_in_tour)').contentDocument.querySelector(selector)) {\n console.error('The job_id field has a wrong value');\n }\n }\n }, {\n content: 'Check that a department_id has been loaded',\n trigger: 'iframe input[name=\"department_id\"][value=\"FAKE_DEPARTMENT_ID_DEFAULT_VAL\"]',\n run: function () {\n if (this.$anchor.val() === \"FAKE_DEPARTMENT_ID_DEFAULT_VAL\") {\n console.error('The department_id data-for should have been applied');\n }\n }\n },\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n content: 'Verify that the job_id hidden field has lost its default value',\n trigger: \"body\",\n run: () => {\n const doc = document.querySelector(\".o_iframe:not(.o_ignore_in_tour)\").contentDocument;\n const id = doc.querySelector('[data-oe-model=\"hr.job\"]').dataset.oeId;\n if (!doc.querySelector(`input[name=\"job_id\"][value=\"${id}\"]`)) {\n console.error('The hidden field has kept its default value in edit mode instead of data-for');\n }\n }\n },\n {\n content: 'Verify that the department_id shown field has kept its default value',\n trigger: 'iframe input[name=\"department_id\"][value=\"FAKE_DEPARTMENT_ID_DEFAULT_VAL\"]',\n run: function () {\n if (this.$anchor.val() !== \"FAKE_DEPARTMENT_ID_DEFAULT_VAL\") {\n console.error('The department_id field has lost its default value');\n }\n },\n },\n]);\n\n return {};\n});\n", "odoo.define('pos_restaurant.tour.ControlButtons', function (require) {\n 'use strict';\n\n const { TextAreaPopup } = require('point_of_sale.tour.TextAreaPopupTourMethods');\n const { NumberPopup } = require('point_of_sale.tour.NumberPopupTourMethods');\n const { Chrome } = require('pos_restaurant.tour.ChromeTourMethods');\n const { FloorScreen } = require('pos_restaurant.tour.FloorScreenTourMethods');\n const { ProductScreen } = require('pos_restaurant.tour.ProductScreenTourMethods');\n const { SplitBillScreen } = require('pos_restaurant.tour.SplitBillScreenTourMethods');\n const { BillScreen } = require('pos_restaurant.tour.BillScreenTourMethods');\n const { getSteps, startSteps } = require('point_of_sale.tour.utils');\n var Tour = require('web_tour.tour');\n\n // signal to start generating steps\n // when finished, steps can be taken from getSteps\n startSteps();\n\n // Test TransferOrderButton\n FloorScreen.do.clickTable('T2');\n ProductScreen.exec.addOrderline('Water', '5', '2', '10.0');\n ProductScreen.do.clickTransferButton();\n FloorScreen.do.clickTable('T4');\n ProductScreen.do.clickOrderline('Water', '5', '2');\n Chrome.do.backToFloor();\n FloorScreen.do.clickTable('T2');\n ProductScreen.check.orderIsEmpty();\n Chrome.do.backToFloor();\n FloorScreen.do.clickTable('T4');\n ProductScreen.do.clickOrderline('Water', '5', '2');\n\n // Test SplitBillButton\n ProductScreen.do.clickSplitBillButton();\n SplitBillScreen.do.clickBack();\n\n // Test OrderlineNoteButton\n ProductScreen.do.clickNoteButton();\n TextAreaPopup.check.isShown();\n TextAreaPopup.do.inputText('test note');\n TextAreaPopup.do.clickConfirm();\n ProductScreen.check.orderlineHasNote('Water', '5', 'test note');\n ProductScreen.exec.addOrderline('Water', '8', '1', '8.0');\n\n // Test PrintBillButton\n ProductScreen.do.clickPrintBillButton();\n BillScreen.check.isShown();\n BillScreen.do.clickOk();\n\n // Test GuestButton\n ProductScreen.do.clickGuestButton();\n NumberPopup.do.pressNumpad('1 5');\n NumberPopup.check.inputShownIs('15');\n NumberPopup.do.clickConfirm();\n ProductScreen.check.guestNumberIs('15')\n\n ProductScreen.do.clickGuestButton();\n NumberPopup.do.pressNumpad('5');\n NumberPopup.check.inputShownIs('5');\n NumberPopup.do.clickConfirm();\n ProductScreen.check.guestNumberIs('5')\n\n Tour.register('ControlButtonsTour', { test: true, url: '/pos/ui' }, getSteps());\n});\n", "odoo.define('pos_restaurant.tour.FloorScreen', function (require) {\n 'use strict';\n\n const { Chrome } = require('pos_restaurant.tour.ChromeTourMethods');\n const { FloorScreen } = require('pos_restaurant.tour.FloorScreenTourMethods');\n const { TextInputPopup } = require('point_of_sale.tour.TextInputPopupTourMethods');\n const { NumberPopup } = require('point_of_sale.tour.NumberPopupTourMethods');\n const { ProductScreen } = require('pos_restaurant.tour.ProductScreenTourMethods');\n const { getSteps, startSteps } = require('point_of_sale.tour.utils');\n var Tour = require('web_tour.tour');\n\n // signal to start generating steps\n // when finished, steps can be taken from getSteps\n startSteps();\n\n // check floors if they contain their corresponding tables\n FloorScreen.check.selectedFloorIs('Main Floor');\n FloorScreen.check.hasTable('T2');\n FloorScreen.check.hasTable('T4');\n FloorScreen.check.hasTable('T5');\n FloorScreen.do.clickFloor('Second Floor');\n FloorScreen.check.hasTable('T3');\n FloorScreen.check.hasTable('T1');\n\n // clicking table in active mode does not open product screen\n // instead, table is selected\n FloorScreen.do.clickEdit();\n FloorScreen.check.editModeIsActive(true);\n FloorScreen.do.clickTable('T3');\n FloorScreen.check.selectedTableIs('T3');\n FloorScreen.do.clickTable('T1');\n FloorScreen.check.selectedTableIs('T1');\n\n // switching floor in edit mode deactivates edit mode\n FloorScreen.do.clickFloor('Main Floor');\n FloorScreen.check.editModeIsActive(false);\n FloorScreen.do.clickEdit();\n FloorScreen.check.editModeIsActive(true);\n\n // test add table\n FloorScreen.do.clickAddTable();\n FloorScreen.check.selectedTableIs('T1');\n FloorScreen.do.clickRename();\n TextInputPopup.check.isShown();\n TextInputPopup.do.inputText('T100');\n TextInputPopup.do.clickConfirm();\n FloorScreen.check.selectedTableIs('T100');\n\n // test duplicate table\n FloorScreen.do.clickDuplicate();\n // new table is already named T101\n FloorScreen.check.selectedTableIs('T101');\n FloorScreen.do.clickRename();\n TextInputPopup.check.isShown();\n TextInputPopup.do.inputText('T1111');\n TextInputPopup.do.clickConfirm();\n FloorScreen.check.selectedTableIs('T1111');\n\n // switch floor, switch back and check if\n // the new tables are still there\n FloorScreen.do.clickFloor('Second Floor');\n FloorScreen.check.editModeIsActive(false);\n FloorScreen.check.hasTable('T3');\n FloorScreen.check.hasTable('T1');\n\n FloorScreen.do.clickFloor('Main Floor');\n FloorScreen.check.hasTable('T2');\n FloorScreen.check.hasTable('T4');\n FloorScreen.check.hasTable('T5');\n FloorScreen.check.hasTable('T100');\n FloorScreen.check.hasTable('T1111');\n\n // test delete table\n FloorScreen.do.clickEdit();\n FloorScreen.check.editModeIsActive(true);\n FloorScreen.do.clickTable('T2');\n FloorScreen.check.selectedTableIs('T2');\n FloorScreen.do.clickTrash();\n Chrome.do.confirmPopup();\n\n // change number of seats\n FloorScreen.do.clickTable('T4');\n FloorScreen.check.selectedTableIs('T4');\n FloorScreen.do.clickSeats();\n NumberPopup.do.pressNumpad('Backspace 9');\n NumberPopup.check.inputShownIs('9');\n NumberPopup.do.clickConfirm();\n FloorScreen.check.tableSeatIs('T4', '9');\n\n // change number of seat when the input is already selected\n FloorScreen.do.clickTable('T4');\n FloorScreen.check.selectedTableIs('T4');\n FloorScreen.do.clickSeats();\n NumberPopup.do.pressNumpad('1 5');\n NumberPopup.check.inputShownIs('15');\n NumberPopup.do.clickConfirm();\n FloorScreen.check.tableSeatIs('T4', '15');\n\n // change shape\n FloorScreen.do.changeShapeTo('round');\n\n // Opening product screen in main floor should go back to main floor\n FloorScreen.do.clickEdit();\n FloorScreen.check.editModeIsActive(false);\n FloorScreen.check.tableIsNotSelected('T4');\n FloorScreen.do.clickTable('T4');\n ProductScreen.check.isShown();\n Chrome.check.backToFloorTextIs('Main Floor', 'T4');\n Chrome.do.backToFloor();\n\n // Opening product screen in second floor should go back to second floor\n FloorScreen.do.clickFloor('Second Floor');\n FloorScreen.check.hasTable('T3');\n FloorScreen.do.clickTable('T3');\n Chrome.check.backToFloorTextIs('Second Floor', 'T3');\n\n Tour.register('FloorScreenTour', { test: true, url: '/pos/ui' }, getSteps());\n});\n", "odoo.define('pos_restaurant.tour.SplitBillScreen', function (require) {\n 'use strict';\n\n const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');\n const { Chrome } = require('pos_restaurant.tour.ChromeTourMethods');\n const { FloorScreen } = require('pos_restaurant.tour.FloorScreenTourMethods');\n const { ProductScreen } = require('pos_restaurant.tour.ProductScreenTourMethods');\n const { SplitBillScreen } = require('pos_restaurant.tour.SplitBillScreenTourMethods');\n const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');\n const { getSteps, startSteps } = require('point_of_sale.tour.utils');\n var Tour = require('web_tour.tour');\n\n // signal to start generating steps\n // when finished, steps can be taken from getSteps\n startSteps();\n\n FloorScreen.do.clickTable('T2');\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.exec.addOrderline('Water', '5', '2', '10.0');\n ProductScreen.exec.addOrderline('Minute Maid', '3', '2', '6.0');\n ProductScreen.exec.addOrderline('Coca-Cola', '1', '2', '2.0');\n ProductScreen.do.clickSplitBillButton();\n\n // Check if the screen contains all the orderlines\n SplitBillScreen.check.orderlineHas('Water', '5', '0');\n SplitBillScreen.check.orderlineHas('Minute Maid', '3', '0');\n SplitBillScreen.check.orderlineHas('Coca-Cola', '1', '0');\n\n // split 3 water and 1 coca-cola\n SplitBillScreen.do.clickOrderline('Water');\n SplitBillScreen.check.orderlineHas('Water', '5', '1');\n SplitBillScreen.do.clickOrderline('Water');\n SplitBillScreen.do.clickOrderline('Water');\n SplitBillScreen.check.orderlineHas('Water', '5', '3');\n SplitBillScreen.check.subtotalIs('6.0')\n SplitBillScreen.do.clickOrderline('Coca-Cola');\n SplitBillScreen.check.orderlineHas('Coca-Cola', '1', '1');\n SplitBillScreen.check.subtotalIs('8.0')\n\n // click pay to split, go back to check the lines\n SplitBillScreen.do.clickPay();\n PaymentScreen.do.clickBack();\n ProductScreen.do.clickOrderline('Water', '3.0')\n ProductScreen.do.clickOrderline('Coca-Cola', '1.0')\n\n // go back to the original order and see if the order is changed\n Chrome.do.clickTicketButton();\n TicketScreen.do.selectOrder('-0001');\n ProductScreen.check.isShown()\n ProductScreen.do.clickOrderline('Water', '2.0')\n ProductScreen.do.clickOrderline('Minute Maid', '3.0')\n\n Tour.register('SplitBillScreenTour', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n FloorScreen.do.clickTable('T2');\n ProductScreen.exec.addOrderline('Water', '1', '2.0');\n ProductScreen.exec.addOrderline('Minute Maid', '1', '2.0');\n ProductScreen.exec.addOrderline('Coca-Cola', '1', '2.0');\n Chrome.do.backToFloor();\n FloorScreen.do.clickTable('T2');\n ProductScreen.do.clickSplitBillButton();\n\n SplitBillScreen.do.clickOrderline('Water');\n SplitBillScreen.do.clickOrderline('Coca-Cola');\n SplitBillScreen.do.clickPay();\n PaymentScreen.do.clickBack();\n Chrome.do.clickTicketButton();\n TicketScreen.do.selectOrder('-0002');\n ProductScreen.do.clickOrderline('Water', '1.0');\n ProductScreen.do.clickOrderline('Coca-Cola', '1.0');\n ProductScreen.check.totalAmountIs('4.40');\n Chrome.do.clickTicketButton();\n TicketScreen.do.selectOrder('-0001');\n ProductScreen.do.clickOrderline('Minute Maid', '1.0');\n ProductScreen.check.totalAmountIs('2.20');\n\n Tour.register('SplitBillScreenTour2', { test: true, url: '/pos/ui' }, getSteps());\n});\n", "odoo.define('pos_restaurant.tour.TicketScreen', function (require) {\n 'use strict';\n\n const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');\n const { FloorScreen } = require('pos_restaurant.tour.FloorScreenTourMethods');\n const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');\n const { Chrome } = require('pos_restaurant.tour.ChromeTourMethods');\n const { getSteps, startSteps } = require('point_of_sale.tour.utils');\n var Tour = require('web_tour.tour');\n\n startSteps();\n\n // New Ticket button should not be in the ticket screen if no table is selected.\n Chrome.do.clickTicketButton();\n TicketScreen.check.noNewTicketButton();\n TicketScreen.do.clickDiscard();\n\n // Deleting the last order in the table brings back to floorscreen\n FloorScreen.do.clickTable('T4');\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.check.isShown();\n Chrome.do.clickTicketButton();\n TicketScreen.check.nthRowContains(2, '-0001');\n TicketScreen.do.deleteOrder('-0001');\n FloorScreen.check.isShown();\n\n // Create 2 items in a table. From floorscreen, delete 1 item. Then select the other item.\n // Correct order and screen should be displayed and the BackToFloorButton is shown.\n FloorScreen.do.clickTable('T2');\n ProductScreen.exec.addOrderline('Minute Maid', '1', '2');\n ProductScreen.check.totalAmountIs('2.0');\n Chrome.do.clickTicketButton();\n TicketScreen.do.clickNewTicket();\n ProductScreen.exec.addOrderline('Coca-Cola', '2', '2');\n ProductScreen.check.totalAmountIs('4.0');\n Chrome.do.backToFloor();\n FloorScreen.check.orderCountSyncedInTableIs('T2', '2');\n Chrome.do.clickTicketButton();\n TicketScreen.do.deleteOrder('-0003');\n Chrome.do.confirmPopup();\n TicketScreen.do.selectOrder('-0002');\n ProductScreen.check.isShown();\n ProductScreen.check.totalAmountIs('2.0');\n Chrome.check.backToFloorTextIs('Main Floor', 'T2');\n Chrome.do.backToFloor();\n\n // Make sure that order is deleted properly.\n FloorScreen.do.clickTable('T5');\n ProductScreen.exec.addOrderline('Minute Maid', '1', '3');\n ProductScreen.check.totalAmountIs('3.0');\n Chrome.do.backToFloor();\n FloorScreen.check.orderCountSyncedInTableIs('T5', '1');\n Chrome.do.clickTicketButton();\n TicketScreen.do.deleteOrder('-0004');\n Chrome.do.confirmPopup();\n TicketScreen.do.clickDiscard();\n FloorScreen.check.isShown();\n FloorScreen.check.orderCountSyncedInTableIs('T5', '0');\n FloorScreen.do.clickTable('T5');\n ProductScreen.check.orderIsEmpty();\n\n Tour.register('PosResTicketScreenTour', { test: true, url: '/pos/ui' }, getSteps());\n});\n", "odoo.define('pos_restaurant.tour.TipScreen', function (require) {\n 'use strict';\n\n const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');\n const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');\n const { ReceiptScreen } = require('point_of_sale.tour.ReceiptScreenTourMethods');\n const { FloorScreen } = require('pos_restaurant.tour.FloorScreenTourMethods');\n const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');\n const { TipScreen } = require('pos_restaurant.tour.TipScreenTourMethods');\n const { NumberPopup } = require('point_of_sale.tour.NumberPopupTourMethods');\n const { Chrome } = require('pos_restaurant.tour.ChromeTourMethods');\n const { getSteps, startSteps } = require('point_of_sale.tour.utils');\n var Tour = require('web_tour.tour');\n\n startSteps();\n\n // Create order that is synced when draft.\n // order 1\n FloorScreen.do.clickTable('T2');\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.exec.addOrderline('Minute Maid', '1', '2');\n ProductScreen.check.totalAmountIs('2.0');\n Chrome.do.backToFloor();\n FloorScreen.check.orderCountSyncedInTableIs('T2', '1');\n FloorScreen.do.clickTable('T2');\n ProductScreen.check.totalAmountIs('2.0');\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.do.clickValidate();\n TipScreen.check.isShown();\n Chrome.do.clickTicketButton();\n TicketScreen.do.clickNewTicket();\n // order 2\n ProductScreen.exec.addOrderline('Coca-Cola', '2', '2');\n ProductScreen.check.totalAmountIs('4.0');\n Chrome.do.backToFloor();\n FloorScreen.check.orderCountSyncedInTableIs('T2', '1');\n Chrome.do.clickTicketButton();\n TicketScreen.check.nthRowContains('2', 'Tipping');\n TicketScreen.do.clickDiscard();\n\n // Create without syncing the draft.\n // order 3\n FloorScreen.do.clickTable('T5');\n ProductScreen.exec.addOrderline('Minute Maid', '3', '2');\n ProductScreen.check.totalAmountIs('6.0');\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.do.clickValidate();\n TipScreen.check.isShown();\n Chrome.do.clickTicketButton();\n TicketScreen.do.clickNewTicket();\n // order 4\n ProductScreen.exec.addOrderline('Coca-Cola', '4', '2');\n ProductScreen.check.totalAmountIs('8.0');\n Chrome.do.backToFloor();\n FloorScreen.check.orderCountSyncedInTableIs('T5', '1');\n Chrome.do.clickTicketButton();\n TicketScreen.check.nthRowContains('3', 'Tipping');\n\n // Tip 20% on order1\n TicketScreen.do.selectOrder('-0001');\n TipScreen.check.isShown();\n TipScreen.check.totalAmountIs('2.0');\n TipScreen.check.percentAmountIs('15%', '0.30');\n TipScreen.check.percentAmountIs('20%', '0.40');\n TipScreen.check.percentAmountIs('25%', '0.50');\n TipScreen.do.clickPercentTip('20%');\n TipScreen.check.inputAmountIs('0.40')\n Chrome.do.backToFloor();\n FloorScreen.check.isShown();\n Chrome.do.clickTicketButton();\n\n // Tip 25% on order3\n TicketScreen.do.selectOrder('-0003');\n TipScreen.check.isShown();\n TipScreen.check.totalAmountIs('6.0');\n TipScreen.check.percentAmountIs('15%', '0.90');\n TipScreen.check.percentAmountIs('20%', '1.20');\n TipScreen.check.percentAmountIs('25%', '1.50');\n TipScreen.do.clickPercentTip('25%');\n TipScreen.check.inputAmountIs('1.50');\n Chrome.do.backToFloor();\n FloorScreen.check.isShown();\n Chrome.do.clickTicketButton();\n\n // finalize order 4 then tip custom amount\n TicketScreen.do.selectOrder('-0004');\n ProductScreen.check.isShown();\n ProductScreen.check.totalAmountIs('8.0');\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.do.clickValidate();\n TipScreen.check.isShown();\n TipScreen.check.totalAmountIs('8.0');\n TipScreen.check.percentAmountIs('15%', '1.20');\n TipScreen.check.percentAmountIs('20%', '1.60');\n TipScreen.check.percentAmountIs('25%', '2.00');\n TipScreen.do.setCustomTip('1.00');\n TipScreen.check.inputAmountIs('1.00')\n Chrome.do.backToFloor();\n FloorScreen.check.isShown();\n\n // settle tips here\n Chrome.do.clickTicketButton();\n TicketScreen.do.selectFilter('Tipping');\n TicketScreen.check.tipContains('1.00');\n TicketScreen.do.settleTips();\n TicketScreen.do.selectFilter('All active orders');\n TicketScreen.check.nthRowContains(2, 'Ongoing');\n\n // tip order2 during payment\n // tip screen should not show after validating payment screen\n TicketScreen.do.selectOrder('-0002');\n ProductScreen.check.isShown();\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickTipButton();\n NumberPopup.check.isShown();\n NumberPopup.do.pressNumpad('1');\n NumberPopup.check.inputShownIs('1');\n NumberPopup.do.clickConfirm();\n PaymentScreen.do.clickPaymentMethod('Cash');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.isShown();\n\n Tour.register('PosResTipScreenTour', { test: true, url: '/pos/ui' }, getSteps());\n});\n", "odoo.define('pos_restaurant.tour.BillScreenTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n\n class Do {\n clickOk() {\n return [\n {\n content: `go back`,\n trigger: `.receipt-screen .button.next`,\n },\n ];\n }\n clickBillButton() {\n return [\n {\n content: \"click bill button\",\n trigger: '.control-button:contains(\"Bill\")',\n },\n ];\n }\n }\n\n class Check {\n isShown() {\n return [\n {\n content: 'Bill screen is shown',\n trigger: '.receipt-screen h1:contains(\"Bill Printing\")',\n run: () => {},\n },\n ];\n }\n isQRCodeShown() {\n return [\n {\n content: \"QR codes are shown\",\n trigger: '#posqrcode',\n run: () => {},\n },\n ];\n }\n isQRCodeNotShown() {\n return [\n {\n content: \"QR codes are shown\",\n trigger: 'body:not(:has(#posqrcode))',\n run: () => {},\n },\n ];\n }\n }\n\n return createTourMethods('BillScreen', Do, Check);\n});\n", "odoo.define('pos_restaurant.tour.ChromeTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n const { Do } = require('point_of_sale.tour.ChromeTourMethods');\n\n class DoExt extends Do {\n backToFloor() {\n return [\n {\n content: 'back to floor',\n trigger: '.floor-button',\n },\n ];\n }\n }\n\n class Check {\n backToFloorTextIs(floor, table) {\n return [\n {\n content: `back to floor text is '${floor} ( ${table} )'`,\n trigger: `.floor-button span:contains(\"${floor}\") ~ .table-name:contains(\"(${table})\")`,\n run: () => {},\n },\n ];\n }\n }\n\n class Execute {}\n\n return createTourMethods('Chrome', DoExt, Check, Execute);\n});\n", "odoo.define('pos_restaurant.tour.FloorScreenTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n\n class Do {\n clickTable(name) {\n return [\n {\n content: `click table '${name}'`,\n trigger: `.floor-map .table .label:contains(\"${name}\")`,\n },\n ];\n }\n clickFloor(name) {\n return [\n {\n content: `click '${name}' floor`,\n trigger: `.floor-selector .button-floor:contains(\"${name}\")`,\n },\n ];\n }\n clickEdit() {\n return [\n {\n content: `click edit button`,\n trigger: `.floor-map .edit-button`,\n },\n ];\n }\n clickAddTable() {\n return [\n {\n content: 'add table',\n trigger: `.floor-map .edit-button i[aria-label=Add]`,\n },\n ];\n }\n clickDuplicate() {\n return [\n {\n content: 'duplicate table',\n trigger: `.floor-map .edit-button i[aria-label=Duplicate]`,\n },\n ];\n }\n clickRename() {\n return [\n {\n content: 'rename table',\n trigger: `.floor-map .edit-button i[aria-label=Rename]`,\n },\n ];\n }\n clickSeats() {\n return [\n {\n content: 'change number of seats',\n trigger: `.floor-map .edit-button i[aria-label=Seats]`,\n },\n ];\n }\n clickTrash() {\n return [\n {\n content: 'trash table',\n trigger: `.floor-map .edit-button.trash`,\n },\n ];\n }\n changeShapeTo(shape) {\n return [\n {\n content: `change shape to '${shape}'`,\n trigger: `.edit-button .button-option${shape === 'round' ? '.square' : '.round'}`,\n },\n ];\n }\n }\n\n class Check {\n selectedFloorIs(name) {\n return [\n {\n content: `selected floor is '${name}'`,\n trigger: `.floor-selector .button-floor.active:contains(\"${name}\")`,\n run: () => {},\n },\n ];\n }\n selectedTableIs(name) {\n return [\n {\n content: `selected table is '${name}'`,\n trigger: `.floor-map .table.selected .label:contains(\"${name}\")`,\n run: () => {},\n },\n ];\n }\n hasTable(name) {\n return [\n {\n content: `selected floor has '${name}' table`,\n trigger: `.floor-map .tables .table .label:contains(\"${name}\")`,\n run: () => {},\n },\n ];\n }\n editModeIsActive(flag) {\n return [\n {\n content: `check if edit mode is ${flag ? 'active' : 'inactive'}`,\n trigger: `.floor-map .edit-button${flag ? '.active' : ':not(:has(.active))'}`,\n run: () => {},\n },\n ];\n }\n tableSeatIs(table, val) {\n return [\n {\n content: `number of seats in table '${table}' is '${val}'`,\n trigger: `.floor-map .tables .table .label:contains(\"${table}\") ~ .table-seats:contains(\"${val}\")`,\n run: function () {},\n },\n ];\n }\n orderCountSyncedInTableIs(table, count) {\n return [\n {\n trigger: `.floor-map .table .order-count:contains(\"${count}\") ~ .label:contains(\"${table}\")`,\n run: function () {},\n },\n ];\n }\n isShown() {\n return [\n {\n trigger: '.floor-map',\n run: function () {},\n },\n ];\n }\n tableIsNotSelected(name) {\n return [\n {\n content: `table '${name}' is not selected`,\n trigger: `.floor-map .table:not(.selected) .label:contains(\"${name}\")`,\n run: function () {},\n },\n ];\n }\n }\n\n class Execute {}\n\n return createTourMethods('FloorScreen', Do, Check, Execute);\n});\n", "odoo.define('pos_restaurant.tour.ProductScreenTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n const { Do, Check, Execute } = require('point_of_sale.tour.ProductScreenTourMethods');\n\n class DoExt extends Do {\n clickSplitBillButton() {\n return [\n {\n content: 'click split bill button',\n trigger: '.control-buttons .control-button.order-split',\n },\n ];\n }\n clickTransferButton() {\n return [\n {\n content: 'click transfer button',\n trigger: '.control-buttons .control-button span:contains(\"Transfer\")',\n },\n ];\n }\n clickNoteButton() {\n return [\n {\n content: 'click note button',\n trigger: '.control-buttons .control-button span:contains(\"Internal Note\")',\n },\n ];\n }\n clickPrintBillButton() {\n return [\n {\n content: 'click print bill button',\n trigger: '.control-buttons .control-button.order-printbill',\n },\n ];\n }\n clickSubmitButton() {\n return [\n {\n content: 'click print bill button',\n trigger: '.control-buttons .control-button span:contains(\"Order\")',\n },\n ];\n }\n clickGuestButton() {\n return [\n {\n content: 'click guest button',\n trigger: '.control-buttons .control-button span:contains(\"Guests\")'\n }\n ]\n }\n }\n\n class CheckExt extends Check {\n orderlineHasNote(name, quantity, note) {\n return [\n {\n content: `line has ${quantity} quantity`,\n trigger: `.order .orderline .product-name:contains(\"${name}\") ~ .info-list em:contains(\"${quantity}\")`,\n run: function () {}, // it's a check\n },\n {\n content: `line has '${note}' note`,\n trigger: `.order .orderline .info-list .orderline-note:contains(\"${note}\")`,\n run: function () {}, // it's a check\n },\n ];\n }\n guestNumberIs(numberInString) {\n return [\n {\n content: `guest number is ${numberInString}`,\n trigger: `.control-buttons .control-button span.control-button-number:contains(${numberInString})`,\n run: function () {}, // it's a check\n }\n ]\n }\n }\n\n class ExecuteExt extends Execute {}\n\n return createTourMethods('ProductScreen', DoExt, CheckExt, ExecuteExt);\n});\n", "odoo.define('pos_restaurant.tour.SplitBillScreenTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n\n class Do {\n clickOrderline(name, totalQuantity) {\n let trigger = `li.orderline .product-name:contains(\"${name}\")`;\n if (totalQuantity) {\n trigger += ` ~ .info-list .info:contains(\"${totalQuantity}\")`;\n }\n return [\n {\n content: `click '${name}' orderline with total quantity of '${totalQuantity}'`,\n trigger,\n },\n ];\n }\n clickBack() {\n return [\n {\n content: 'click back button',\n trigger: `.splitbill-screen .button.back`,\n },\n ];\n }\n clickPay() {\n return [\n {\n content: 'click pay button',\n trigger: `.splitbill-screen .pay-button .button`\n }\n ]\n }\n }\n\n class Check {\n orderlineHas(name, totalQuantity, splitQuantity) {\n return [\n {\n content: `'${name}' orderline has total quantity of '${totalQuantity}'`,\n trigger: `li.orderline .product-name:contains(\"${name}\") ~ .info-list .info:contains(\"${totalQuantity}\")`,\n run: () => {},\n },\n {\n content: `'${name}' orderline has '${splitQuantity}' quantity to split`,\n trigger: `li.orderline .product-name:contains(\"${name}\") ~ .info-list .info em:contains(\"${splitQuantity}\")`,\n run: () => {},\n },\n ];\n }\n subtotalIs(amount) {\n return [\n {\n content: `total amount of split is '${amount}'`,\n trigger: `.splitbill-screen .order-info .subtotal:contains(\"${amount}\")`,\n },\n ];\n }\n }\n\n class Execute {}\n\n return createTourMethods('SplitBillScreen', Do, Check, Execute);\n});\n", "odoo.define('pos_restaurant.tour.TipScreenTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n\n class Do {\n clickPercentTip(percent) {\n return [\n {\n trigger: `.tip-screen .percentage:contains(\"${percent}\")`,\n },\n ];\n }\n setCustomTip(amount) {\n return [\n {\n trigger: `.tip-screen .custom-amount-form input`,\n run: `text ${amount}`,\n },\n ];\n }\n }\n\n class Check {\n isShown() {\n return [\n {\n trigger: '.pos .tip-screen',\n run: () => {},\n },\n ];\n }\n totalAmountIs(amount) {\n return [\n {\n trigger: `.tip-screen .total-amount:contains(\"${amount}\")`,\n run: () => {},\n },\n ];\n }\n percentAmountIs(percent, amount) {\n return [\n {\n trigger: `.tip-screen .percentage:contains(\"${percent}\") ~ .amount:contains(\"${amount}\")`,\n run: () => {},\n },\n ];\n }\n inputAmountIs(amount) {\n return [\n {\n trigger: `.tip-screen .custom-amount-form input[data-amount=\"${amount}\"]`,\n run: () => {},\n }\n ]\n }\n }\n\n return createTourMethods('TipScreen', Do, Check);\n});\n", "odoo.define('pos_reataurant.tour.synchronized_table_management', function (require) {\n 'use strict';\n\n const { BillScreen } = require('pos_restaurant.tour.BillScreenTourMethods');\n const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');\n const { ReceiptScreen } = require('point_of_sale.tour.ReceiptScreenTourMethods');\n const { Chrome } = require('pos_restaurant.tour.ChromeTourMethods');\n const { FloorScreen } = require('pos_restaurant.tour.FloorScreenTourMethods');\n const { ProductScreen } = require('pos_restaurant.tour.ProductScreenTourMethods');\n const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');\n const { getSteps, startSteps } = require('point_of_sale.tour.utils');\n const Tour = require('web_tour.tour');\n\n startSteps();\n\n FloorScreen.do.clickTable('T5');\n\n // Create first order\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickDisplayedProduct('Coca-Cola');\n ProductScreen.check.selectedOrderlineHas('Coca-Cola');\n ProductScreen.do.clickDisplayedProduct('Water');\n ProductScreen.check.selectedOrderlineHas('Water');\n ProductScreen.check.totalAmountIs('4.40');\n\n // Create 2nd order (paid)\n Chrome.do.clickTicketButton();\n TicketScreen.do.clickNewTicket();\n ProductScreen.do.clickDisplayedProduct('Coca-Cola');\n ProductScreen.check.selectedOrderlineHas('Coca-Cola');\n ProductScreen.do.clickDisplayedProduct('Minute Maid');\n ProductScreen.check.selectedOrderlineHas('Minute Maid');\n ProductScreen.check.totalAmountIs('4.40');\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Cash');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.do.clickNextOrder();\n\n // After clicking next order, floor screen is shown.\n // It should have 1 as number of draft synced order.\n FloorScreen.check.orderCountSyncedInTableIs('T5', '1');\n FloorScreen.do.clickTable('T5');\n ProductScreen.check.totalAmountIs('4.40');\n\n // Create another draft order and go back to floor\n Chrome.do.clickTicketButton();\n TicketScreen.do.clickNewTicket();\n ProductScreen.do.clickDisplayedProduct('Coca-Cola');\n ProductScreen.check.selectedOrderlineHas('Coca-Cola');\n ProductScreen.do.clickDisplayedProduct('Minute Maid');\n ProductScreen.check.selectedOrderlineHas('Minute Maid');\n Chrome.do.backToFloor();\n\n // At floor screen, there should be 2 synced draft orders\n FloorScreen.check.orderCountSyncedInTableIs('T5', '2');\n\n // Delete the first order then go back to floor\n FloorScreen.do.clickTable('T5');\n ProductScreen.check.isShown();\n Chrome.do.clickTicketButton();\n TicketScreen.do.deleteOrder('-0001');\n Chrome.do.confirmPopup();\n TicketScreen.do.selectOrder('-0003');\n Chrome.do.backToFloor();\n\n // There should be 1 synced draft order.\n FloorScreen.check.orderCountSyncedInTableIs('T5', '1');\n\n Tour.register('pos_restaurant_sync', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n /* pos_restaurant_sync_second_login\n *\n * This tour should be run after the first tour is done.\n */\n\n // There is one draft synced order from the previous tour\n FloorScreen.check.orderCountSyncedInTableIs('T5', '1');\n FloorScreen.do.clickTable('T5');\n ProductScreen.check.totalAmountIs('4.40');\n\n // Test transfering an order\n ProductScreen.do.clickTransferButton();\n FloorScreen.do.clickTable('T4');\n\n // Test if products still get merged after transfering the order\n ProductScreen.do.clickDisplayedProduct('Coca-Cola');\n ProductScreen.check.selectedOrderlineHas('Coca-Cola', '2.0');\n ProductScreen.check.totalAmountIs('6.60');\n ProductScreen.do.pressNumpad('1');\n ProductScreen.check.totalAmountIs('4.40');\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Cash');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.do.clickNextOrder();\n // At this point, there are no draft orders.\n\n FloorScreen.do.clickTable('T2');\n ProductScreen.check.isShown();\n ProductScreen.check.orderIsEmpty();\n ProductScreen.do.clickTransferButton();\n FloorScreen.do.clickTable('T4');\n ProductScreen.do.clickDisplayedProduct('Coca-Cola');\n ProductScreen.check.totalAmountIs('2.20');\n Chrome.do.backToFloor();\n FloorScreen.check.orderCountSyncedInTableIs('T4', '1');\n\n Tour.register('pos_restaurant_sync_second_login', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n FloorScreen.do.clickTable(\"5\");\n ProductScreen.do.clickDisplayedProduct(\"Coca-Cola\");\n BillScreen.do.clickBillButton();\n BillScreen.check.isShown();\n BillScreen.check.isQRCodeNotShown();\n BillScreen.do.clickOk();\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod(\"Bank\");\n PaymentScreen.do.clickValidate();\n BillScreen.check.isQRCodeShown();\n\n Tour.register('BillScreenTour', { test: true, url: '/pos/ui' }, getSteps());\n});\n", "odoo.define('pos_restaurant.tour.Refund', function (require) {\n 'use strict';\n\n const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');\n const { FloorScreen } = require('pos_restaurant.tour.FloorScreenTourMethods');\n const { ProductScreen } = require('pos_restaurant.tour.ProductScreenTourMethods');\n const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');\n const { ReceiptScreen } = require('point_of_sale.tour.ReceiptScreenTourMethods');\n const { getSteps, startSteps } = require('point_of_sale.tour.utils');\n var Tour = require('web_tour.tour');\n\n // signal to start generating steps\n // when finished, steps can be taken from getSteps\n startSteps();\n\n // Create first order and pay it\n FloorScreen.do.clickTable(\"T2\");\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickDisplayedProduct(\"Coca-Cola\");\n ProductScreen.check.selectedOrderlineHas(\"Coca-Cola\");\n ProductScreen.do.clickDisplayedProduct(\"Coca-Cola\");\n ProductScreen.check.selectedOrderlineHas(\"Coca-Cola\");\n ProductScreen.do.clickDisplayedProduct(\"Water\");\n ProductScreen.check.selectedOrderlineHas(\"Water\");\n ProductScreen.check.totalAmountIs(\"6.60\");\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod(\"Cash\");\n PaymentScreen.do.clickValidate();\n ReceiptScreen.do.clickNextOrder();\n\n // Go to another table and refund one of the product\n FloorScreen.do.clickTable(\"T4\");\n ProductScreen.check.orderIsEmpty();\n ProductScreen.do.clickRefund();\n TicketScreen.do.selectOrder(\"-0001\");\n TicketScreen.do.clickOrderline(\"Coca-Cola\");\n TicketScreen.do.pressNumpad(\"2\");\n TicketScreen.check.toRefundTextContains(\"To Refund: 2.00\");\n TicketScreen.do.confirmRefund();\n ProductScreen.check.isShown();\n ProductScreen.check.selectedOrderlineHas(\"Coca-Cola\");\n ProductScreen.check.totalAmountIs(\"-4.40\");\n\n Tour.register('RefundStayCurrentTableTour', { test: true, url: '/pos/ui' }, getSteps());\n});\n", "odoo.define('pos_sale.tour.ProductScreenTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n const { Do, Check, Execute } = require('point_of_sale.tour.ProductScreenTourMethods');\n\n class DoExt extends Do {\n clickQuotationButton() {\n return [\n {\n content: 'click quotation button',\n trigger: '.o_sale_order_button',\n }\n ];\n }\n\n selectFirstOrder() {\n return [\n {\n content: `select order`,\n trigger: `.order-row .col.name:first`,\n },\n {\n content: `click on select the order`,\n trigger: `.selection-item:contains('Settle the order')`,\n }\n ];\n }\n\n selectNthOrder(n) {\n return [\n {\n content: `select order`,\n trigger: `.order-list .order-row:nth-child(${n})`,\n },\n {\n content: `click on select the order`,\n trigger: `.selection-item:contains('Settle the order')`,\n }\n ];\n }\n\n downPaymentFirstOrder() {\n return [\n {\n content: `select order`,\n trigger: `.order-row .col.name:first`,\n },\n {\n content: `click on select the order`,\n trigger: `.selection-item:contains('Apply a down payment')`,\n },\n {\n content: `click on +10 button`,\n trigger: `.mode-button.add:contains('+10')`,\n },\n {\n content: `click on ok button`,\n trigger: `.button.confirm`,\n }\n ];\n }\n }\n\n class CheckExt extends Check{\n checkCustomerNotes(note) {\n return [\n {\n content: `check customer notes`,\n trigger: `.orderline-note:contains(${note})`,\n }\n ];\n }\n checkOrdersListEmpty() {\n return [\n {\n content: 'Check that the orders list is empty',\n trigger: '.order-list:not(:has(.order-row))',\n }\n ]\n }\n }\n return createTourMethods('ProductScreen', DoExt, CheckExt, Execute);\n});\n", "odoo.define('pos_sale.tour.ReceiptScreenTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n const { Do, Check, Execute } = require('point_of_sale.tour.ReceiptScreenTourMethods');\n\n class CheckExt extends Check{\n checkCustomerNotes(note) {\n return [\n {\n content: `check customer notes`,\n trigger: `.pos-receipt-customer-note:contains(${note})`,\n }\n ];\n }\n }\n\n return createTourMethods('ReceiptScreen', Do, CheckExt, Execute);\n});\n", "odoo.define('pos_sale.tour', function (require) {\n 'use strict';\n\n const { Chrome } = require('point_of_sale.tour.ChromeTourMethods');\n const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');\n const { ProductScreen } = require('pos_sale.tour.ProductScreenTourMethods');\n const { ReceiptScreen } = require('pos_sale.tour.ReceiptScreenTourMethods');\n const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');\n const { getSteps, startSteps } = require('point_of_sale.tour.utils');\n const Tour = require('web_tour.tour');\n\n // signal to start generating steps\n // when finished, steps can be taken from getSteps\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickQuotationButton();\n ProductScreen.do.selectFirstOrder();\n ProductScreen.check.selectedOrderlineHas('Pizza Chicken', 9);\n ProductScreen.do.pressNumpad('Qty 2'); // Change the quantity of the product to 2\n ProductScreen.check.selectedOrderlineHas('Pizza Chicken', 2);\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.do.clickValidate();\n Chrome.do.clickTicketButton();\n\n Tour.register('PosSettleOrder', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickQuotationButton();\n // The second item in the list is the first sale.order.\n ProductScreen.do.selectNthOrder(2);\n ProductScreen.check.selectedOrderlineHas('product1', 1);\n ProductScreen.check.totalAmountIs(\"10.00\");\n\n ProductScreen.do.clickQuotationButton();\n // The first item in the list is the second sale.order.\n // Selecting the 2nd sale.order should use a new order,\n // therefore, the total amount will change.\n ProductScreen.do.selectNthOrder(1);\n ProductScreen.check.selectedOrderlineHas('product2', 1);\n ProductScreen.check.totalAmountIs(\"11.00\");\n\n Tour.register('PosSettleOrderIncompatiblePartner', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickQuotationButton();\n ProductScreen.do.selectFirstOrder();\n ProductScreen.do.clickOrderline('[A001] Product A', '1');\n ProductScreen.check.selectedOrderlineHas('[A001] Product A', '1.00');\n ProductScreen.do.clickOrderline('[A002] Product B', '1');\n ProductScreen.do.pressNumpad('Qty 0');\n ProductScreen.check.selectedOrderlineHas('[A002] Product B', '0.00');\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.check.remainingIs('0.0');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.isShown();\n\n Tour.register('PosSettleOrder2', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickQuotationButton();\n ProductScreen.do.selectFirstOrder();\n ProductScreen.do.clickOrderline(\"Product A\", \"1\");\n ProductScreen.check.selectedOrderlineHas('Product A', '1.00');\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.check.remainingIs('0.0');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.isShown();\n\n Tour.register('PosSettleOrder3', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickQuotationButton();\n ProductScreen.do.selectFirstOrder();\n ProductScreen.check.totalAmountIs(40);\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.do.clickValidate();\n Chrome.do.clickTicketButton();\n\n Tour.register('PosSettleOrderRealTime', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.clickQuotationButton();\n ProductScreen.do.downPaymentFirstOrder();\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Cash');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.do.clickNextOrder();\n ProductScreen.do.clickRefund();\n // Filter should be automatically 'Paid'.\n TicketScreen.check.filterIs('Paid');\n TicketScreen.do.selectOrder('-0001');\n TicketScreen.do.clickOrderline('Down Payment');\n TicketScreen.do.pressNumpad('1');\n TicketScreen.do.confirmRefund();\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Cash');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.do.clickNextOrder();\n\n Tour.register('PosRefundDownpayment', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickQuotationButton();\n ProductScreen.do.selectFirstOrder();\n ProductScreen.check.totalAmountIs(28.98); // 3.5 * 8 * 1.15 * 90%\n ProductScreen.do.clickOrderline(\"Product A\", '0.5');\n ProductScreen.check.checkOrderlinesNumber(4);\n ProductScreen.check.selectedOrderlineHas('Product A', '0.5', '4.14');\n\n Tour.register('PosSettleOrderNotGroupable', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.clickQuotationButton();\n ProductScreen.do.selectFirstOrder();\n ProductScreen.check.checkCustomerNotes(\"Customer note 2--Customer note 3\");\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.checkCustomerNotes(\"Customer note 2--Customer note 3\");\n ReceiptScreen.do.clickNextOrder();\n\n Tour.register('PosSettleOrderWithNote', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.clickQuotationButton();\n ProductScreen.do.selectFirstOrder();\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.do.clickNextOrder();\n ProductScreen.do.clickQuotationButton();\n ProductScreen.check.checkOrdersListEmpty();\n\n Tour.register('PosOrderDoesNotRemainInList', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n \n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickQuotationButton();\n ProductScreen.do.selectFirstOrder();\n ProductScreen.check.selectedOrderlineHas('product_a', '1', '100');\n ProductScreen.do.clickPartnerButton();\n ProductScreen.do.clickCustomer('partner_a');\n ProductScreen.check.selectedOrderlineHas('product_a', '1', '100');\n\n Tour.register('PosSettleCustomPrice', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickQuotationButton();\n ProductScreen.do.selectFirstOrder();\n ProductScreen.check.selectedOrderlineHas('Test service product', '1.00', '50.00');\n\n Tour.register('PosSettleDraftOrder', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickQuotationButton();\n ProductScreen.do.selectFirstOrder();\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickShipLaterButton()\n PaymentScreen.do.clickPaymentMethod('Bank');\n PaymentScreen.check.remainingIs('0.0');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.check.isShown();\n\n Tour.register('PosSettleOrderShipLater', { test: true, url: '/pos/ui' }, getSteps());\n\n startSteps();\n\n ProductScreen.do.confirmOpeningPopup();\n ProductScreen.do.clickQuotationButton();\n ProductScreen.do.downPaymentFirstOrder();\n ProductScreen.check.selectedOrderlineHas('Down Payment', '1', '10.00');\n ProductScreen.do.clickPayButton();\n PaymentScreen.do.clickPaymentMethod('Cash');\n PaymentScreen.do.clickValidate();\n ReceiptScreen.do.clickNextOrder();\n\n Tour.register('PoSDownPaymentAmount', { test: true, url: '/pos/ui' }, getSteps());\n});\n", "/** @odoo-module **/\n\nimport { PaymentScreen } from 'point_of_sale.tour.PaymentScreenTourMethods';\nimport { ProductScreen } from 'pos_sale.tour.ProductScreenTourMethods';\nimport { ReceiptScreen } from 'point_of_sale.tour.ReceiptScreenTourMethods';\nimport { getSteps, startSteps } from 'point_of_sale.tour.utils';\nimport Tour from 'web_tour.tour';\n\n// First tour should not get any automatic rewards\nstartSteps();\n\nProductScreen.do.confirmOpeningPopup();\nProductScreen.do.clickQuotationButton();\nProductScreen.do.selectFirstOrder();\nProductScreen.do.clickDisplayedProduct('Desk Pad');\nProductScreen.do.clickPayButton();\nPaymentScreen.do.clickPaymentMethod('Bank');\nPaymentScreen.do.clickValidate();\nReceiptScreen.check.isShown();\n\nTour.register('PosSaleLoyaltyTour1', { test: true, url: '/pos/web' }, getSteps());\n", "odoo.define('website_crm.tour', function(require) {\n 'use strict';\n\n const tour = require('web_tour.tour');\n const wTourUtils = require('website.tour_utils');\n\n wTourUtils.registerWebsitePreviewTour('website_crm_pre_tour', {\n test: true,\n url: '/contactus',\n edition: true,\n }, [{\n content: \"Select contact form\",\n trigger: \"iframe #wrap.o_editable section.s_website_form\",\n }, {\n content: \"Open action select\",\n trigger: \"we-select:has(we-button:contains('Create an Opportunity')) we-toggler\",\n extra_trigger: \"#oe_snippets .o_we_customize_snippet_btn.active\",\n }, {\n content: \"Select 'Create an Opportunity' as form action\",\n trigger: \"we-select we-button:contains('Create an Opportunity')\",\n }, {\n content: \"Save the settings\",\n trigger: \"button[data-action=save]\",\n }, {\n content: \"Ensure form model has changed and page reload is done after save\",\n trigger: \"iframe section.s_website_form form[data-model_name='crm.lead']\",\n extra_trigger: \"iframe body:not(.editor_enable)\",\n }]);\n\n tour.register('website_crm_tour', {\n test: true,\n url: '/contactus',\n }, [{\n content: \"Complete name\",\n trigger: \"input[name=contact_name]\",\n run: \"text John Smith\",\n }, {\n content: \"Complete phone number\",\n trigger: \"input[name=phone]\",\n run: \"text +32 485 118.218\"\n }, {\n content: \"Complete Email\",\n trigger: \"input[name=email_from]\",\n run: \"text john@smith.com\"\n }, {\n content: \"Complete Company\",\n trigger: \"input[name=partner_name]\",\n run: \"text Odoo S.A.\"\n }, {\n content: \"Complete Subject\",\n trigger: \"input[name=name]\",\n run: \"text Useless message\"\n }, {\n content: \"Complete Subject\",\n trigger: \"textarea[name=description]\",\n run: \"text ### TOUR DATA ###\"\n }, {\n content: \"Send the form\",\n trigger: \".s_website_form_send\"\n }, {\n content: \"Check we were redirected to the success page\",\n trigger: \"#wrap:has(h1:contains('Thank You!'))\"\n }]);\n\n tour.register('website_crm_catch_logged_partner_info_tour', {\n test: true,\n url: '/contactus',\n }, [{\n content: \"Complete Subject\",\n trigger: \"input[name=name]\",\n run: \"text Useless subject\"\n }, {\n content: \"Complete Subject\",\n trigger: \"textarea[name=description]\",\n run: \"text ### TOUR DATA PREFILL ###\"\n }, {\n content: \"Send the form\",\n trigger: \".s_website_form_send\"\n }, {\n content: \"Check we were redirected to the success page\",\n trigger: \"#wrap:has(h1:contains('Thank You!'))\"\n }]);\n\n return {};\n});\n", "odoo.define('auth_totp.tours', function(require) {\n\"use strict\";\n\nconst tour = require('web_tour.tour');\nconst ajax = require('web.ajax');\n\nfunction openRoot() {\n return [{\n content: \"return to client root to avoid race condition\",\n trigger: 'body',\n run() {\n $('body').addClass('wait');\n window.location = '/web';\n }\n }, {\n content: \"wait for client reload\",\n trigger: 'body:not(.wait)',\n run() {}\n }];\n}\nfunction openUserProfileAtSecurityTab() {\n return [{\n content: 'Open user account menu',\n trigger: '.o_user_menu .oe_topbar_name',\n run: 'click',\n }, {\n content: \"Open preferences / profile screen\",\n trigger: '[data-menu=settings]',\n run: 'click',\n }, {\n content: \"Switch to security tab\",\n trigger: 'a[role=tab]:contains(\"Account Security\")',\n run: 'click',\n }];\n}\n\n/**\n * Checks that the TOTP button is in the specified state (true = enabled =\n * can disable, false = disabled = can enable), then closes the profile dialog\n * if it's one (= hr not installed).\n *\n * If no totp state is provided, just checks that the toggle exists.\n */\nfunction closeProfileDialog({content, totp_state}) {\n let trigger;\n switch (totp_state) {\n case true: trigger = 'button[name=action_totp_disable]'; break;\n case false: trigger = 'button[name=action_totp_enable_wizard]'; break;\n case undefined: trigger = 'button.o_auth_2fa_btn'; break;\n default: throw new Error(`Invalid totp state ${totp_state}`)\n }\n\n return [{\n content,\n trigger,\n run() {\n const $modal = this.$anchor.parents('.o_dialog_container');\n if ($modal.length) {\n $modal.find('button[name=preference_cancel]').click()\n }\n }\n }, {\n trigger: 'body',\n async run() {\n while (document.querySelector('.o_dialog_container .o_dialog')) {\n await Promise.resolve();\n }\n this.$anchor.addClass('dialog-closed');\n },\n }, {\n trigger: 'body.dialog-closed',\n run() {},\n }];\n}\n\ntour.register('totp_tour_setup', {\n test: true,\n url: '/web'\n}, [...openUserProfileAtSecurityTab(), {\n content: \"Open totp wizard\",\n trigger: 'button[name=action_totp_enable_wizard]',\n}, {\n content: \"Check that we have to enter enhanced security mode and input password\",\n extra_trigger: 'div:contains(\"enter your password\")',\n trigger: '[name=password] input',\n run: 'text demo',\n}, {\n content: \"Confirm\",\n trigger: \"button:contains(Confirm Password)\",\n}, {\n content: \"Check the wizard has opened\",\n trigger: 'li:contains(\"When requested to do so\")',\n run() {}\n}, {\n content: \"Get secret from collapsed div\",\n trigger: 'a:contains(\"Cannot scan it?\")',\n async run(helpers) {\n const $secret = this.$anchor.closest('div').find('[name=secret] span:first-child');\n const $copyBtn = $secret.find('button');\n $copyBtn.remove();\n const token = await ajax.jsonRpc('/totphook', 'call', {\n secret: $secret.text()\n });\n helpers.text(token, '[name=code] input');\n helpers.click('button.btn-primary:contains(Activate)');\n $('body').addClass('got-token')\n }\n}, {\n content: 'wait for rpc',\n trigger: 'body.got-token',\n run() {}\n},\n...openRoot(),\n...openUserProfileAtSecurityTab(),\n...closeProfileDialog({\n content: \"Check that the button has changed\",\n totp_state: true,\n}),\n]);\n\ntour.register('totp_login_enabled', {\n test: true,\n url: '/'\n}, [{\n content: \"check that we're on the login page or go to it\",\n trigger: 'input#login, a:contains(Sign in)'\n}, {\n content: \"input login\",\n trigger: 'input#login',\n run: 'text demo',\n}, {\n content: 'input password',\n trigger: 'input#password',\n run: 'text demo',\n}, {\n content: \"click da button\",\n trigger: 'button:contains(\"Log in\")',\n}, {\n content: \"expect totp screen\",\n trigger: 'label:contains(Authentication Code)',\n}, {\n content: \"input code\",\n trigger: 'input[name=totp_token]',\n async run(helpers) {\n // TODO: if tours are ever async-aware the click should get moved out,\n // but currently there's no great way to make the tour wait until\n // we've retrieved and set the token: `:empty()` is aboutthe text\n // content of the HTML element, not the JS value property. We\n // could set a class but that's really no better than\n // procedurally clicking the button after we've set the input.\n const token = await ajax.jsonRpc('/totphook', 'call', {});\n helpers.text(token);\n helpers.click('button:contains(\"Log in\")');\n }\n}, {\n content: \"check we're logged in\",\n trigger: \".o_user_menu .oe_topbar_name\",\n run() {}\n}]);\n\ntour.register('totp_login_device', {\n test: true,\n url: '/'\n}, [{\n content: \"check that we're on the login page or go to it\",\n trigger: 'input#login, a:contains(Sign in)'\n}, {\n content: \"input login\",\n trigger: 'input#login',\n run: 'text demo',\n}, {\n content: 'input password',\n trigger: 'input#password',\n run: 'text demo',\n}, {\n content: \"click da button\",\n trigger: 'button:contains(\"Log in\")',\n}, {\n content: \"expect totp screen\",\n trigger: 'label:contains(Authentication Code)',\n}, {\n content: \"check remember device box\",\n trigger: 'label[for=switch-remember]',\n}, {\n content: \"input code\",\n trigger: 'input[name=totp_token]',\n async run(helpers) {\n const token = await ajax.jsonRpc('/totphook', 'call', {})\n helpers.text(token);\n helpers.click('button:contains(\"Log in\")');\n }\n}, {\n content: \"check we're logged in\",\n trigger: \".o_user_menu .oe_topbar_name\",\n run: 'click',\n}, {\n content: \"click the Log out button\",\n trigger: '.dropdown-item[data-menu=logout]',\n}, {\n content: \"check that we're back on the login page or go to it\",\n trigger: 'input#login, a:contains(Log in)'\n}, {\n content: \"input login again\",\n trigger: 'input#login',\n run: 'text demo',\n}, {\n content: 'input password again',\n trigger: 'input#password',\n run: 'text demo',\n}, {\n content: \"click da button again\",\n trigger: 'button:contains(\"Log in\")',\n}, {\n content: \"check we're logged in without 2FA\",\n trigger: \".o_user_menu .oe_topbar_name\",\n run() {}\n},\n// now go and disable two-factor authentication would be annoying to do in a separate tour\n// because we'd need to login & totp again as HttpCase.authenticate can't\n// succeed w/ totp enabled\n...openUserProfileAtSecurityTab(),\n{\n content: \"Open totp wizard\",\n trigger: 'button[name=action_totp_disable]',\n}, {\n content: \"Check that we have to enter enhanced security mode and input password\",\n extra_trigger: 'div:contains(\"enter your password\")',\n trigger: '[name=password] input',\n run: 'text demo',\n}, {\n content: \"Confirm\",\n trigger: \"button:contains(Confirm Password)\",\n},\n...openRoot(),\n...openUserProfileAtSecurityTab(),\n...closeProfileDialog({\n content: \"Check that the button has changed\",\n totp_state: false\n}),\n]);\n\ntour.register('totp_login_disabled', {\n test: true,\n url: '/'\n}, [{\n content: \"check that we're on the login page or go to it\",\n trigger: 'input#login, a:contains(Sign in)'\n}, {\n content: \"input login\",\n trigger: 'input#login',\n run: 'text demo',\n}, {\n content: 'input password',\n trigger: 'input#password',\n run: 'text demo',\n}, {\n content: \"click da button\",\n trigger: 'button:contains(\"Log in\")',\n},\n// normally we'd end the tour here as it's all we care about but there are a\n// bunch of ongoing queries from the loading of the web client which cause\n// issues, so go and open the preferences / profile screen to make sure\n// everything settles down\n...openUserProfileAtSecurityTab(),\n// close the dialog if that makes sense\n...closeProfileDialog({})\n]);\n\nconst columns = {};\ntour.register('totp_admin_disables', {\n test: true,\n url: '/web'\n}, [tour.stepUtils.showAppsMenuItem(), {\n content: 'Go to settings',\n trigger: '[data-menu-xmlid=\"base.menu_administration\"]'\n}, {\n content: 'Wait for page',\n trigger: '.o_menu_brand:contains(\"Settings\")',\n run() {}\n}, {\n content: \"Open Users menu\",\n trigger: '[data-menu-xmlid=\"base.menu_users\"]'\n}, {\n content: \"Open Users view\",\n trigger: '[data-menu-xmlid=\"base.menu_action_res_users\"]',\n run(helpers) {\n // funny story: the users view we're trying to reach, sometimes we're\n // already there, but if we re-click the next step executes before the\n // action has the time to re-load, the one after that doesn't, and our\n // selection get discarded by the action reloading, so here try to\n // see if we're already on the users action through the breadcrumb and\n // just close the menu if so\n const $crumb = $('.breadcrumb');\n if ($crumb.text().indexOf('Users') === -1) {\n // on general settings page, click menu\n helpers.click();\n } else {\n // else close menu\n helpers.click($('[data-menu-xmlid=\"base.menu_users\"]'));\n }\n }\n}, {\n content: \"Find Demo User\",\n trigger: 'td.o_data_cell:contains(\"demo\")',\n run(helpers) {\n const $titles = this.$anchor.closest('table').find('tr:first th');\n for (let i=0; i<$titles.length; ++i) {\n columns[$titles[i].getAttribute('data-name')] = i;\n }\n const $row = this.$anchor.closest('tr');\n const sel = $row.find('.o_list_record_selector input[type=checkbox]');\n helpers.click(sel);\n }\n}, {\n content: \"Open Actions menu\",\n trigger: 'button.dropdown-toggle:contains(\"Action\")'\n}, {\n content: \"Select totp remover\",\n trigger: 'span.dropdown-item:contains(Disable two-factor authentication)'\n}, { // enhanced security yo\n content: \"Check that we have to enter enhanced security mode & input password\",\n extra_trigger: 'div:contains(\"enter your password\")',\n trigger: '[name=password] input',\n run: 'text admin',\n}, {\n content: \"Confirm\",\n trigger: \"button:contains(Confirm Password)\",\n}, {\n content: \"open the user's form\",\n trigger: \"td.o_data_cell:contains(demo)\",\n}, {\n content: \"go to Account security Tab\",\n trigger: \"a.nav-link:contains(Account Security)\",\n}, ...closeProfileDialog({\n content: \"check that demo user has been de-totp'd\",\n totp_state: false,\n}),\n])\n});\n", "odoo.define('auth_totp_mail.tours', function(require) {\n\"use strict\";\n\nconst tour = require('web_tour.tour');\n\nfunction openAccountSettingsTab() {\n return [{\n content: 'Go to settings',\n trigger: '[data-menu-xmlid=\"base.menu_administration\"]'\n }, {\n content: 'Wait for page',\n trigger: '.o_menu_brand:contains(\"Settings\")',\n run: () => {}\n }, {\n content: \"Open Users menu\",\n trigger: '[data-menu-xmlid=\"base.menu_users\"]'\n }, {\n content: \"Open Users view\",\n trigger: '[data-menu-xmlid=\"base.menu_action_res_users\"]',\n run: function (helpers) {\n // funny story: the users view we're trying to reach, sometimes we're\n // already there, but if we re-click the next step executes before the\n // action has the time to re-load, the one after that doesn't, and our\n // selection get discarded by the action reloading, so here try to\n // see if we're already on the users action through the breadcrumb and\n // just close the menu if so\n const $crumb = $('.breadcrumb');\n if ($crumb.text().indexOf('Users') === -1) {\n // on general settings page, click menu\n helpers.click();\n } else {\n // else close menu\n helpers.click($('[data-menu-xmlid=\"base.menu_users\"]'));\n }\n }\n }];\n}\n\ntour.register('totp_admin_self_invite', {\n test: true,\n url: '/web'\n}, [tour.stepUtils.showAppsMenuItem(), ...openAccountSettingsTab(), {\n content: \"open the user's form\",\n trigger: \"td.o_data_cell:contains(admin)\",\n}, {\n content: \"go to Account security Tab\",\n trigger: \"a.nav-link:contains(Account Security)\",\n}, {\n content: \"check that user cannot invite themselves to use 2FA.\",\n trigger: \"body\",\n run: function () {\n const inviteBtn = $('button:contains(Invite to use 2FA)')[0];\n if (!inviteBtn) {\n $('body').addClass('CannotInviteYourself');\n }\n }\n}, {\n content: \"check that user cannot invite themself.\",\n trigger: \"body.CannotInviteYourself\"\n}]);\n\ntour.register('totp_admin_invite', {\n test: true,\n url: '/web'\n}, [tour.stepUtils.showAppsMenuItem(), ...openAccountSettingsTab(), {\n content: \"open the user's form\",\n trigger: \"td.o_data_cell:contains(demo)\",\n}, {\n content: \"go to Account security Tab\",\n trigger: \"a.nav-link:contains(Account Security)\",\n}, {\n content: \"check that demo user can be invited to use 2FA.\",\n trigger: \"button:contains(Invite to use 2FA)\",\n}]);\n\n});", "odoo.define('auth_totp_portal.tours', function(require) {\n\"use strict\";\n\nconst tour = require('web_tour.tour');\nconst ajax = require('web.ajax');\n\ntour.register('totportal_tour_setup', {\n test: true,\n url: '/my/security'\n}, [{\n content: \"Open totp wizard\",\n trigger: 'button#auth_totp_portal_enable',\n}, {\n content: \"Check that we have to enter enhanced security mode\",\n trigger: 'div:contains(\"enter your password\")',\n run: () => {},\n}, {\n content: \"Input password\",\n trigger: '[name=password]',\n run: 'text portal', // FIXME: better way to do this?\n}, {\n content: \"Confirm\",\n trigger: \"button:contains(Confirm Password)\",\n}, {\n content: \"Check the wizard has opened\",\n trigger: 'li:contains(\"scan the barcode below\")',\n run: () => {}\n}, {\n content: \"Get secret from collapsed div\",\n trigger: 'a:contains(\"Cannot scan it?\")',\n run: async function(helpers) {\n const secret = this.$anchor.closest('div').find('span[name=\"secret\"]').text();\n const token = await ajax.jsonRpc('/totphook', 'call', {\n secret\n });\n helpers._text(helpers._get_action_values('input[name=code]'), token);\n helpers._click(helpers._get_action_values('button.btn-primary:contains(Activate)'));\n }\n}, {\n content: \"Check that the button has changed\",\n trigger: 'button:contains(Disable two-factor authentication)',\n run: () => {}\n}]);\n\ntour.register('totportal_login_enabled', {\n test: true,\n url: '/'\n}, [{\n content: \"check that we're on the login page or go to it\",\n trigger: 'input#login, a:contains(Sign in)'\n}, {\n content: \"input login\",\n trigger: 'input#login',\n run: 'text portal',\n}, {\n content: 'input password',\n trigger: 'input#password',\n run: 'text portal',\n}, {\n content: \"click da button\",\n trigger: 'button:contains(\"Log in\")',\n}, {\n content: \"expect totp screen\",\n trigger: 'label:contains(Authentication Code)',\n}, {\n content: \"input code\",\n trigger: 'input[name=totp_token]',\n run: async function (helpers) {\n const token = await ajax.jsonRpc('/totphook', 'call', {});\n helpers._text(helpers._get_action_values(), token);\n // FIXME: is there a way to put the button as its own step trigger without\n // the tour straight blowing through and not waiting for this?\n helpers._click(helpers._get_action_values('button:contains(\"Log in\")'));\n }\n}, {\n content: \"check we're logged in\",\n trigger: \"h3:contains(Documents)\",\n run: () => {}\n}, {\n content: \"go back to security\",\n trigger: \"a:contains(Security)\",\n},{\n content: \"Open totp wizard\",\n trigger: 'button#auth_totp_portal_disable',\n}, {\n content: \"Check that we have to enter enhanced security mode\",\n trigger: 'div:contains(\"enter your password\")',\n run: () => {},\n}, {\n content: \"Input password\",\n trigger: '[name=password]',\n run: 'text portal', // FIXME: better way to do this?\n}, {\n content: \"Confirm\",\n trigger: \"button:contains(Confirm Password)\",\n}, {\n content: \"Check that the button has changed\",\n trigger: 'button:contains(Enable two-factor authentication)',\n run: () => {}\n}]);\n\ntour.register('totportal_login_disabled', {\n test: true,\n url: '/'\n}, [{\n content: \"check that we're on the login page or go to it\",\n trigger: 'input#login, a:contains(Sign in)'\n}, {\n content: \"input login\",\n trigger: 'input#login',\n run: 'text portal',\n}, {\n content: 'input password',\n trigger: 'input#password',\n run: 'text portal',\n}, {\n content: \"click da button\",\n trigger: 'button:contains(\"Log in\")',\n}, {\n content: \"check we're logged in\",\n trigger: \"h3:contains(Documents)\",\n run: () => {}\n}]);\n});\n", "odoo.define('sale_timesheet.tour', function (require) {\n\"use strict\";\n\nconst {Markup} = require('web.utils');\nconst tour = require('web_tour.tour');\n\ntour.register('sale_timesheet_tour', {\n test: true,\n url: '/web',\n}, [...tour.stepUtils.goToAppSteps(\"sale.sale_menu_root\", 'Go to the Sales App'),\n{\n trigger: 'button.o_list_button_add',\n content: 'Click on CREATE button to create a quotation with service products.',\n}, {\n trigger: 'div[name=\"partner_id\"] input',\n content: 'Add the customer for this quotation (e.g. Brandon Freeman)',\n run: 'text Brandon Freeman',\n}, {\n trigger: 'div[name=\"partner_id\"] ul > li:first-child > a:contains(Freeman)',\n content: 'Select the first item on the autocomplete dropdown',\n},\n{\n trigger: 'td.o_field_x2many_list_row_add > a:first-child',\n content: 'Click on \"Add a product\" to add a new product. We will add a service product.',\n}, {\n trigger: '.o_field_html[name=\"product_id\"], .o_field_widget[name=\"product_template_id\"] input',\n content: Markup('Select a prepaid service product (e.g. Service Product (Prepaid Hours))'),\n run: 'text Service Product (Prepaid Hours)',\n}, {\n trigger: 'ul.ui-autocomplete a:contains(Service Product (Prepaid Hours))',\n content: 'Select the prepaid service product in the autocomplete dropdown',\n}, {\n trigger: 'div[name=\"product_uom_qty\"] input',\n content: \"Add 10 hours as ordered quantity for this product.\",\n run: 'text 10',\n}, {\n trigger: 'div[name=\"name\"] textarea:propValueContains(Service Product)',\n run: () => {}\n}, {\n trigger: 'button[name=\"action_confirm\"]',\n content: 'Click on Confirm button to create a sale order with this quotation.',\n}, tour.stepUtils.toggleHomeMenu(),\n...tour.stepUtils.goToAppSteps(\"project.menu_main_pm\", 'Go to the Project app.'),\n{\n trigger: 'button.o-kanban-button-new',\n content: 'Add a new project.',\n}, {\n trigger: '.o_field_widget.o_project_name input',\n content: 'Select your project name (e.g. Project for Freeman)',\n run: 'text Project for Freeman',\n}, {\n trigger: 'button[name=\"action_view_tasks\"]',\n content: 'Click on Create button to create and enter to this newest project.',\n}, {\n trigger: 'div.o_kanban_header > div:first-child input',\n content: 'Select a name of your kanban column (e.g. To Do)',\n run: 'text To Do',\n}, {\n trigger: 'button.o_kanban_add',\n content: 'Click on Add button to create the column.',\n}, {\n trigger: 'button.o-kanban-button-new',\n content: 'Click on Create button to create a task into your project.',\n}, {\n trigger: 'div[name=\"name\"] > input',\n content: 'Select the name of the task (e.g. Onboarding)',\n run: 'text Onboarding',\n}, {\n trigger: 'button.o_kanban_edit',\n content: 'Click on Edit button to enter to the form view of the task.',\n position: 'bottom',\n}, {\n trigger: 'div[name=\"partner_id\"] input',\n content: Markup('Select the customer of your Sales Order (e.g. Brandon Freeman). Since we have a Sales Order for this customer with a prepaid service product which the remaining hours to deliver is greater than 0, the Sales Order Item in the task should be contain the Sales Order Item containing this prepaid service product.'),\n run: 'text Brandon Freeman',\n}, {\n trigger: 'div[name=\"partner_id\"] ul > li:first-child > a:contains(Freeman)',\n content: 'Select the customer in the autocomplete dropdown.',\n}, {\n trigger: 'a.nav-link:contains(Timesheets)',\n extra_trigger: 'div.o_notebook_headers',\n content: 'Click on Timesheets page to log a timesheet',\n}, {\n trigger: 'div[name=\"timesheet_ids\"] td.o_field_x2many_list_row_add a[role=\"button\"]',\n content: 'Click on Add a line to create a new timesheet into the task.',\n}, {\n trigger: 'div[name=\"unit_amount\"] input',\n content: 'Enter one hour for this timesheet',\n run: 'text 1',\n}, {\n trigger: 'i.o_optional_columns_dropdown_toggle',\n content: 'The so_line field should be hidden by default. We check if it is the case by adding this field in the timesheet list view',\n}, {\n trigger: 'input[name=\"so_line\"]',\n content: 'Check the so_line field to display the column on the list view.',\n run: function (actions) {\n if (!this.$anchor.prop('checked')) {\n actions.click(this.$anchor);\n }\n },\n}, {\n trigger: 'button[name=\"action_view_so\"]',\n content: 'Click on this stat button to see the SO linked to the SOL of the task.',\n}, {\n trigger: 'div[name=\"order_line\"]',\n content: 'Check if the quantity delivered is equal to 1 hour.',\n run: function () {\n const $header = this.$anchor.find('thead > tr');\n if (!$header || $header.length === 0)\n console.error('No Sales Order Item is found in the Sales Order.');\n const tr = $header[0];\n let index = -1;\n for (let i = 0; i < tr.children.length; i++) {\n const th = tr.children.item(i);\n if (th.dataset && th.dataset.name === 'qty_delivered')\n index = i;\n }\n const qtyDelivered = this.$anchor.find(`tbody > tr:first-child > td.o_data_cell:eq(${index})`).text();\n if (qtyDelivered !== \"1.00\")\n console.error('The quantity delivered on this Sales Order Item should be equal to 1.00 hour. qtyDelivered = ' + qtyDelivered);\n },\n}, {\n trigger: 'button[data-menu-xmlid=\"project.menu_project_config\"]',\n content: 'Click on the Configuration menu.',\n}, {\n trigger: '.dropdown-item[data-menu-xmlid=\"project.menu_projects_config\"]',\n content: 'Select Configuration > Projects.',\n}, {\n trigger: 'button.o_list_button_add',\n content: 'Click on Create button to create a new project and see the different configuration available for the project.',\n}, {\n trigger: 'a.nav-link[name=\"settings\"]',\n extra_trigger: 'div.o_notebook_headers',\n content: 'Click on Settings page to check the allow_billable checkbox',\n}, {\n trigger: 'div[name=\"allow_billable\"] input',\n content: 'Check the allow_billable',\n run: function (actions) {\n if (!this.$anchor.prop('checked')) {\n actions.click(this.$anchor);\n }\n }\n}, {\n trigger: 'div[name=\"partner_id\"] input',\n content: Markup('Add the customer for this project to select an SO and SOL for this customer (e.g. Brandon Freeman).'),\n run: 'text Brandon Freeman',\n}, {\n trigger: 'div[name=\"partner_id\"] ul > li:first-child > a:contains(Freeman)',\n content: 'Select the customer in the autocomplete dropdown',\n}, {\n trigger: 'a.nav-link[name=\"billing_employee_rate\"]',\n extra_trigger: 'div.o_notebook_headers',\n content: 'Click on Invoicing tab to configure the invoicing of this project.',\n}, {\n trigger: 'div[name=\"sale_line_id\"] input',\n content: 'Select a Sales Order Item as Default Sales Order Item for each task in this project.',\n run: 'text S',\n}, {\n trigger: '[name=\"sale_line_id\"] ul.ui-autocomplete > li:first-child > a:not(:has(i.fa))',\n content: 'Select the Sales Order Item in the autocomplete dropdown.',\n}, {\n trigger: 'div[name=\"sale_line_employee_ids\"] td.o_field_x2many_list_row_add > a[role=\"button\"]',\n content: 'Click on Add a line on the mapping list view.',\n}, {\n trigger: 'div[name=\"sale_line_employee_ids\"] div[name=\"employee_id\"] input',\n content: 'Select an employee to link a Sales Order Item on his timesheets into this project.',\n run: 'click',\n}, {\n trigger: '[name=\"employee_id\"] ul.ui-autocomplete > li:first-child > a:not(:has(i.fa))',\n content: 'Select the first employee in the autocomplete dropdown',\n}, {\n trigger: 'div[name=\"sale_line_employee_ids\"] div[name=\"sale_line_id\"] input',\n content: 'Select the Sales Order Item to link to the timesheets of this employee.',\n position: 'bottom',\n run: 'text S',\n}, {\n trigger: '[name=sale_line_id] ul.ui-autocomplete > li:first-child > a:not(:has(i.fa))',\n content: 'Select the first Sales Order Item in the autocomplete dropdown.',\n run: 'click'\n}, {\n trigger: 'h1 > div[name=\"name\"] > input',\n content: 'Set Project name',\n run: 'text Project with employee mapping',\n}, {\n trigger: '.dropdown-item[data-menu-xmlid=\"project.menu_main_pm\"]',\n content: 'Select Project main menu',\n}, {\n trigger: '.oe_kanban_global_click :contains(\"Project for Freeman\") button.o_dropdown_kanban',\n content: 'Open the project dropdown',\n}, {\n trigger: '.o_kanban_record:contains(\"Project for Freeman\") .dropdown-menu a:contains(\"Settings\")',\n content: 'Start editing the project',\n // timer: 300,\n}, {\n trigger: 'div[name=\"partner_id\"] input',\n content: Markup('Add the customer for this project to select an SO and SOL for this customer (e.g. Brandon Freeman).'),\n run: 'text Brandon Freeman',\n}, {\n trigger: 'div[name=\"partner_id\"] ul > li:first-child > a:contains(Freeman)',\n content: 'Select the customer in the autocomplete dropdown',\n}, {\n trigger: 'a.nav-link[name=\"billing_employee_rate\"]',\n extra_trigger: 'div.o_notebook_headers',\n content: 'Click on Invoicing tab to configure the invoicing of this project.',\n}, {\n trigger: 'div[name=\"sale_line_id\"] input',\n content: 'Select the first sale order of the list',\n run: 'text Prepaid',\n}, {\n trigger: 'ul.ui-autocomplete > li:first-child > a:not(:has(i.fa))',\n content: 'Select the first item on the autocomplete dropdown',\n}, {\n trigger: '.o_back_button',\n content: 'Go back to the kanban view the project created',\n}, {\n trigger: '.oe_kanban_global_click :contains(\"Project for Freeman\")',\n content: 'Open the project',\n}, {\n trigger: \".o_project_updates_breadcrumb\",\n content: 'Open Updates',\n}, {\n trigger: \".o_rightpanel_section[name='sales'] .o_rightpanel_title:contains('Sales')\",\n content: 'Check the user sees Sales section',\n run: function () {},\n}, {\n trigger: \".o_rightpanel_section[name='sales'] .o_rightpanel_data:contains('Prepaid Hours')\",\n content: 'Check the user sees a line in the Sales section',\n // timer: 300,\n run: function () {},\n}, {\n trigger: \".o_rightpanel_section .oe_button_box .o_stat_text:contains('Sales Orders')\",\n content: 'Check the user sees Sales Orders Stat Button',\n run: function () {},\n}, {\n trigger: \".o_rightpanel_section[name='profitability'] .o_rightpanel_title:contains('Profitability')\",\n content: 'Check the user sees Profitability section',\n run: function () {},\n}, {\n trigger: \".o_rightpanel_section[name='profitability'] .o_rightpanel_data > .o_rightpanel_subsection:eq(0) > table > thead > tr > th:eq(0):contains('Revenues')\",\n content: 'Check the user sees Profitability subsection row',\n run: function () {},\n}, {\n trigger: \".o_rightpanel_section[name='profitability'] .o_rightpanel_data > .o_rightpanel_subsection:eq(1) > table > thead > tr > th:eq(0):contains('Costs')\",\n content: 'Check the user sees Profitability subsection row',\n run: function () {},\n}, {\n trigger: \".o_rightpanel_section[name='profitability'] .o_rightpanel_data > .o_rightpanel_subsection:eq(2) > table > thead > tr > th:eq(0):contains('Margin')\",\n content: 'Check the user sees Profitability subsection row',\n run: function () {},\n}, {\n trigger: \".o_rightpanel_section[name='milestones'] .o_rightpanel_title:contains('Milestones')\",\n content: 'Check the user sees Milestones section',\n}, {\n trigger: \".o_add_milestone a\",\n content: \"Add a first milestone\",\n}, {\n trigger: \"div.o_field_widget[name=name] input\",\n content: \"Edit new Milestone\",\n run: 'text New milestone',\n}, {\n trigger: \"div[name=deadline] .datetimepicker-input\",\n content: \"Edit new Milestone\",\n run: 'text 12/12/2099',\n}, {\n trigger: \".modal-footer button\",\n content: \"Save new Milestone\",\n}, {\n trigger: \".o-kanban-button-new\",\n content: \"Create new Project Update\",\n}, {\n trigger: \"div.o_field_widget[name=name] input\",\n content: \"Give a name to Project Update\",\n run: 'text New update',\n}, {\n trigger: \".o_field_widget[name=description] h3:contains('Sold')\",\n content: \"Sold title must be in description in description\",\n run: function () {},\n }, {\n trigger: \".o_field_widget[name=description] td:contains('Prepaid Hours')\",\n content: \"Prepaid Hours title must be in description\",\n run: function () {},\n}, {\n trigger: \".o_field_widget[name=description] h3:contains('Profitability')\",\n content: \"Profitability title must be in description\",\n run: function () {},\n}, {\n trigger: \".o_field_widget[name=description] h3:contains('Milestones')\",\n content: \"Milestones title must be in description\",\n run: function () {},\n},\n// Those steps are currently needed in order to prevent the following issue:\n// \"Form views in edition mode are automatically saved when the page is closed, which leads to stray network requests and inconsistencies.\"\n{\n trigger: '.o_back_button',\n content: 'Go back to the kanban view and the project update will be added on that view',\n}, {\n trigger: '.o_controller_with_rightpanel',\n content: 'Check the kanban view of project update is rendered to be sure the user leaves the form view and the project update is created',\n run: function() {},\n},\ntour.stepUtils.toggleHomeMenu(),\n...tour.stepUtils.goToAppSteps(\"project.menu_main_pm\", 'Go to the Project app.'),\n]);\n});\n", "odoo.define('point_of_sale.tour.PosHr', function (require) {\n 'use strict';\n\n const { PosHr } = require('pos_hr.tour.PosHrTourMethods');\n const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');\n const { TicketScreen } = require('point_of_sale.tour.TicketScreenTourMethods');\n const { Chrome } = require('point_of_sale.tour.ChromeTourMethods');\n const { ErrorPopup } = require('point_of_sale.tour.ErrorPopupTourMethods');\n const { NumberPopup } = require('point_of_sale.tour.NumberPopupTourMethods');\n const { SelectionPopup } = require('point_of_sale.tour.SelectionPopupTourMethods');\n const { getSteps, startSteps } = require('point_of_sale.tour.utils');\n const Tour = require('web_tour.tour');\n\n startSteps();\n\n PosHr.check.loginScreenIsShown();\n PosHr.do.clickLoginButton();\n SelectionPopup.check.isShown();\n SelectionPopup.check.hasSelectionItem('Pos Employee1');\n SelectionPopup.check.hasSelectionItem('Pos Employee2');\n SelectionPopup.check.hasSelectionItem('Mitchell Admin');\n SelectionPopup.do.clickItem('Pos Employee1');\n NumberPopup.check.isShown();\n NumberPopup.do.pressNumpad('2 5');\n NumberPopup.check.inputShownIs('\u2022\u2022');\n NumberPopup.do.pressNumpad('8 1');\n NumberPopup.check.inputShownIs('\u2022\u2022\u2022\u2022');\n NumberPopup.do.clickConfirm();\n ErrorPopup.check.isShown();\n ErrorPopup.do.clickConfirm();\n PosHr.do.clickLoginButton();\n SelectionPopup.do.clickItem('Pos Employee1');\n NumberPopup.check.isShown();\n NumberPopup.do.pressNumpad('2 5');\n NumberPopup.check.inputShownIs('\u2022\u2022');\n NumberPopup.do.pressNumpad('8 0');\n NumberPopup.check.inputShownIs('\u2022\u2022\u2022\u2022');\n NumberPopup.do.clickConfirm();\n ProductScreen.check.isShown();\n ProductScreen.do.confirmOpeningPopup();\n PosHr.check.cashierNameIs('Pos Employee1');\n PosHr.do.clickCashierName();\n SelectionPopup.do.clickItem('Mitchell Admin');\n PosHr.check.cashierNameIs('Mitchell Admin');\n PosHr.do.clickLockButton();\n PosHr.do.clickLoginButton();\n SelectionPopup.do.clickItem('Pos Employee2');\n NumberPopup.do.pressNumpad('1 2');\n NumberPopup.check.inputShownIs('\u2022\u2022');\n NumberPopup.do.pressNumpad('3 4');\n NumberPopup.check.inputShownIs('\u2022\u2022\u2022\u2022');\n NumberPopup.do.clickConfirm();\n ProductScreen.check.isShown();\n ProductScreen.do.clickHomeCategory();\n\n // Create orders and check if the ticket list has the right employee for each order\n // order for employee 2\n ProductScreen.exec.addOrderline('Desk Pad', '1', '2');\n ProductScreen.check.totalAmountIs('2.0')\n Chrome.do.clickTicketButton();\n TicketScreen.check.nthRowContains(2, 'Pos Employee2');\n\n // order for employee 1\n PosHr.do.clickLockButton();\n PosHr.exec.login('Pos Employee1', '2580');\n TicketScreen.do.clickNewTicket();\n ProductScreen.exec.addOrderline('Desk Pad', '1', '4');\n ProductScreen.check.totalAmountIs('4.0')\n Chrome.do.clickTicketButton();\n TicketScreen.check.nthRowContains(2, 'Pos Employee2');\n TicketScreen.check.nthRowContains(3, 'Pos Employee1');\n\n // order for admin\n PosHr.do.clickCashierName();\n SelectionPopup.do.clickItem('Mitchell Admin');\n PosHr.check.cashierNameIs('Mitchell Admin');\n TicketScreen.do.clickNewTicket();\n ProductScreen.exec.addOrderline('Desk Pad', '1', '8');\n ProductScreen.check.totalAmountIs('8.0')\n Chrome.do.clickTicketButton();\n TicketScreen.check.nthRowContains(4, 'Mitchell Admin');\n\n Tour.register('PosHrTour', { test: true, url: '/pos/ui' }, getSteps());\n});\n", "odoo.define('pos_hr.tour.PosHrTourMethods', function (require) {\n 'use strict';\n\n const { createTourMethods } = require('point_of_sale.tour.utils');\n const { SelectionPopup } = require('point_of_sale.tour.SelectionPopupTourMethods');\n const { NumberPopup } = require('point_of_sale.tour.NumberPopupTourMethods');\n\n class Do {\n clickLoginButton() {\n return [\n {\n content: 'click login button',\n trigger: '.login-overlay .login-button.select-cashier',\n },\n ];\n }\n clickLockButton() {\n return [\n {\n content: 'click lock button',\n trigger: '.header-button .lock-button',\n },\n ];\n }\n clickCashierName() {\n return [\n {\n content: 'click cashier name',\n trigger: '.oe_status .username',\n }\n ]\n }\n }\n class Check {\n loginScreenIsShown() {\n return [\n {\n content: 'login screen is shown',\n trigger: '.login-overlay .screen-login .login-body',\n run: () => {},\n },\n ];\n }\n cashierNameIs(name) {\n return [\n {\n content: `logged cashier is '${name}'`,\n trigger: `.pos .oe_status .username:contains(\"${name}\")`,\n run: () => {},\n },\n ];\n }\n }\n class Execute {\n login(name, pin) {\n const res = this._do.clickLoginButton();\n res.push(...SelectionPopup._do.clickItem(name));\n if (pin) {\n res.push(...NumberPopup._do.pressNumpad(pin.split('').join(' ')));\n res.push(...NumberPopup._do.clickConfirm());\n }\n return res;\n }\n }\n\n return createTourMethods('PosHr', Do, Check, Execute);\n});\n", "/* Copyright 2025 Kencove - Mohamed Alkobrosli\n License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */\nodoo.define(\"sign_oca.sign_tour\", function (require) {\n var tour = require(\"web_tour.tour\");\n\n var steps = [\n {\n trigger: \"a:contains('Your Documents to be Signed')\",\n },\n ];\n\n tour.register(\n \"test_sign_tour\",\n {\n url: \"/my\",\n test: true,\n },\n steps\n );\n return {\n steps: steps,\n };\n});\n\nodoo.define(\"sign_oca.sign_doc_tour\", function (require) {\n var tour = require(\"web_tour.tour\");\n\n var steps = [\n {\n trigger: \"td a\",\n },\n ];\n\n tour.register(\n \"test_sign_doc_tour\",\n {\n url: \"/my/sign_requests\",\n test: true,\n },\n steps\n );\n return {\n steps: steps,\n };\n});\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\n\n/**\n * Makes sure that blog search can be used with the date filtering.\n */\ntour.register('blog_autocomplete_with_date', {\n test: true,\n url: '/blog',\n}, [{\n content: \"Select first month\",\n trigger: 'select[name=archive]',\n run: 'text option 2',\n}, {\n content: \"Enter search term\",\n trigger: '.o_searchbar_form input',\n extra_trigger: '#o_wblog_posts_loop span:has(i.fa-calendar-o):has(a[href=\"/blog\"])',\n run: 'text a',\n}, {\n content: \"Wait for suggestions then click on search icon\",\n extra_trigger: '.o_searchbar_form .o_dropdown_menu .o_search_result_item',\n trigger: '.o_searchbar_form button:has(i.oi-search)',\n}, {\n content: \"Ensure both filters are applied\",\n trigger: '#o_wblog_posts_loop:has(span:has(i.fa-calendar-o):has(a[href=\"/blog?search=a\"])):has(span:has(i.fa-search):has(a[href^=\"/blog?date_begin\"]))',\n run: () => {}, // This is a check.\n}]);\n", "/** @odoo-module **/\n\nimport wTourUtils from 'website.tour_utils';\n\n\n/**\n * Makes sure that blog tags can be created and removed.\n */\nwTourUtils.registerWebsitePreviewTour('blog_tags', {\n test: true,\n url: '/blog',\n}, [{\n content: \"Go to first blog\",\n trigger: \"iframe article[name=blog_post] a\",\n },\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n wTourUtils.clickOnSnippet('#o_wblog_post_top .o_wblog_post_page_cover'),\n {\n content: \"Open tag dropdown\",\n trigger: \"we-customizeblock-option:contains(Tags) .o_we_m2m we-toggler\",\n }, {\n content: \"Enter tag name\",\n trigger: \"we-customizeblock-option:contains(Tags) we-selection-items .o_we_m2o_create input\",\n run: \"text testtag\",\n }, {\n content: \"Click Create\",\n trigger: \"we-customizeblock-option:contains(Tags) we-selection-items .o_we_m2o_create we-button\",\n }, {\n content: \"Verify tag appears in options\",\n trigger: \"we-customizeblock-option:contains(Tags) we-list input[data-name=testtag]\",\n run: () => {}, // it's a check\n },\n ...wTourUtils.clickOnSave(),\n {\n content: \"Verify tag appears in blog post\",\n trigger: \"iframe #o_wblog_post_content .badge:contains(testtag)\",\n run: () => {}, // it's a check\n },\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n wTourUtils.clickOnSnippet('#o_wblog_post_top .o_wblog_post_page_cover'),\n {\n content: \"Remove tag\",\n trigger: \"we-customizeblock-option:contains(Tags) we-list tr:has(input[data-name=testtag]) we-button.fa-minus\",\n }, {\n content: \"Verify tag does not appear in options anymore\",\n trigger: \"we-customizeblock-option:contains(Tags) we-list:not(:has(input[data-name=testtag]))\",\n run: () => {}, // it's a check\n },\n ...wTourUtils.clickOnSave(),\n {\n content: \"Verify tag does not appear in blog post anymore\",\n trigger: \"iframe #o_wblog_post_content div:has(.badge):not(:contains(testtag))\",\n run: () => {}, // it's a check\n }]\n);\n", "odoo.define('website_event_sale.tour.WebsiteEventSaleTourMethods', function (require) {\n 'use strict';\n\n function changePricelist(pricelistName) {\n return [\n {\n content: \"Go to page Shop\",\n trigger: '.nav-link:contains(\"Shop\")',\n },\n {\n content: \"Toggle Pricelist\",\n trigger: '.o_pricelist_dropdown > .dropdown-toggle',\n run: 'click',\n },\n {\n content: `Activate Pricelist ${pricelistName}`,\n trigger: `.dropdown-item:contains(${pricelistName})`,\n run: 'click',\n },\n {\n content: 'Wait for pricelist to load',\n trigger: `.dropdown-toggle:contains(${pricelistName})`,\n run: function () {},\n },\n ];\n }\n function checkPriceEvent(eventName, price) {\n return [\n {\n content: \"Go to page Event\",\n trigger: '.nav-link:contains(\"Event\")',\n },\n {\n content: \"Open the Pycon event\",\n trigger: `.o_wevent_events_list a:contains(${eventName})`,\n },\n {\n content: \"Verify Price\",\n trigger: `.oe_currency_value:contains(${price})`,\n run: function () {}, // it's a check\n },\n ]\n }\n function checkPriceDiscountEvent(eventName, price, discount) {\n return [\n ...checkPriceEvent(eventName, price),\n {\n content: \"Verify Price before discount\",\n trigger: `del:contains(${discount})`,\n run: function () {}, // it's a check\n },\n ]\n }\n function checkPriceCart(price) {\n return [\n {\n content: \"Go to page Cart\",\n trigger: '.fa-shopping-cart',\n },\n {\n content: \"Verify Price\",\n trigger: `[id=order_total] .oe_currency_value:contains(${price})`,\n run: function () {}, // it's a check\n },\n ]\n }\n const getPriceListChecksSteps = function ({pricelistName, eventName, price, priceBeforeDiscount=false}) {\n const checkPriceSteps = priceBeforeDiscount ? checkPriceDiscountEvent(eventName, price, priceBeforeDiscount) : checkPriceEvent(eventName, price);\n return [\n ...changePricelist(pricelistName),\n ...checkPriceSteps,\n ...checkPriceCart(price),\n ]\n }\n return { getPriceListChecksSteps, changePricelist, checkPriceCart }\n});\n", "odoo.define('website_event_sale.tour', function (require) {\n'use strict';\n\nvar tour = require('web_tour.tour');\n\ntour.register('event_buy_tickets', {\n test: true,\n url: '/event',\n},\n [\n {\n content: \"Go to the `Events` page\",\n trigger: 'a[href*=\"/event\"]:contains(\"Conference for Architects TEST\"):first',\n },\n {\n content: \"Select 1 unit of `Standard` ticket type\",\n extra_trigger: '#wrap:not(:has(a[href*=\"/event\"]:contains(\"Conference for Architects\")))',\n trigger: 'select:eq(0)',\n run: 'text 1',\n },\n {\n content: \"Select 2 units of `VIP` ticket type\",\n extra_trigger: 'select:eq(0):has(option:contains(1):propSelected)',\n trigger: 'select:eq(1)',\n run: 'text 2',\n },\n {\n content: \"Click on `Order Now` button\",\n extra_trigger: 'select:eq(1):has(option:contains(2):propSelected)',\n trigger: '.btn-primary:contains(\"Register\")',\n },\n {\n content: \"Fill attendees details\",\n trigger: 'form[id=\"attendee_registration\"] .btn:contains(\"Continue\")',\n run: function () {\n $(\"input[name='1-name']\").val(\"Att1\");\n $(\"input[name='1-phone']\").val(\"111 111\");\n $(\"input[name='1-email']\").val(\"att1@example.com\");\n $(\"input[name='2-name']\").val(\"Att2\");\n $(\"input[name='2-phone']\").val(\"222 222\");\n $(\"input[name='2-email']\").val(\"att2@example.com\");\n $(\"input[name='3-name']\").val(\"Att3\");\n $(\"input[name='3-phone']\").val(\"333 333\");\n $(\"input[name='3-email']\").val(\"att3@example.com\");\n },\n },\n {\n content: \"Validate attendees details\",\n extra_trigger: \"input[name='1-name'], input[name='2-name'], input[name='3-name']\",\n trigger: 'button:contains(\"Continue\")',\n },\n {\n content: \"Check that the cart contains exactly 3 triggers\",\n trigger: 'a:has(.my_cart_quantity:containsExact(3)),.o_extra_menu_items .fa-plus',\n run: function () {}, // it's a check\n },\n {\n content: \"go to cart\",\n trigger: 'a:contains(Return to Cart)',\n },\n {\n content: \"Now click on `Process Checkout`\",\n extra_trigger: 'a:has(.my_cart_quantity):contains(3),#cart_products input.js_quantity[value=\"3\"]',\n trigger: '.btn-primary:contains(\"Process Checkout\")'\n },\n {\n content: \"Check that the subtotal is 4,000.00 USD\", // this test will fail if the currency of the main company is not USD\n trigger: '#order_total_untaxed .oe_currency_value:contains(\"4,000.00\")',\n run: function () {}, // it's a check\n },\n {\n content: \"Select `Wire Transfer` payment method\",\n trigger: '#payment_method label:contains(\"Wire Transfer\")',\n },\n {\n content: \"Pay\",\n //Either there are multiple payment methods, and one is checked, either there is only one, and therefore there are no radio inputs\n // extra_trigger: '#payment_method input:checked,#payment_method:not(:has(\"input:radio:visible\"))',\n trigger: 'button[name=\"o_payment_submit_button\"]:visible:not(:disabled)',\n },\n {\n content: \"Last step\",\n trigger: '.oe_website_sale_tx_status:contains(\"Please use the following transfer details\")',\n timeout: 30000,\n }\n ]\n);\n\n});\n", "odoo.define('website_event_sale.tour.last_ticket', function (require) {\n'use strict';\n\nvar tour = require('web_tour.tour');\n\ntour.register('event_buy_last_ticket', {\n test: true,\n url: '/event',\n},[{\n content: \"Open the Last ticket test event page\",\n trigger: '.o_wevent_events_list a:contains(\"Last ticket test\")',\n },\n {\n content: \"Show available Tickets\",\n trigger: '.btn-primary:contains(\"Register\")',\n },\n {\n content: \"Select 2 units of `VIP` ticket type\",\n extra_trigger: '#wrap:not(:has(a[href*=\"/event\"]:contains(\"Last ticket test\")))',\n trigger: 'select:eq(0)',\n run: 'text 2',\n },\n {\n content: \"Click on `Order Now` button\",\n extra_trigger: 'select:eq(0):has(option:contains(2):propSelected)',\n trigger: '.a-submit:contains(\"Register\")',\n },\n {\n content: \"Fill attendees details\",\n trigger: 'form[id=\"attendee_registration\"] .btn:contains(\"Continue\")',\n run: function () {\n $(\"input[name='1-name']\").val(\"Att1\");\n $(\"input[name='1-phone']\").val(\"111 111\");\n $(\"input[name='1-email']\").val(\"att1@example.com\");\n $(\"input[name='2-name']\").val(\"Att2\");\n $(\"input[name='2-phone']\").val(\"222 222\");\n $(\"input[name='2-email']\").val(\"att2@example.com\");\n },\n },\n {\n content: \"Validate attendees details\",\n extra_trigger: \"input[name='1-name'], input[name='2-name']\",\n trigger: 'button:contains(\"Continue\")',\n },\n {\n content: \"Fill address\",\n trigger: 'form.checkout_autoformat',\n run: function () {\n $(\"input[name='name']\").val(\"test1\");\n $(\"input[name='email']\").val(\"test@example.com\");\n $(\"input[name='phone']\").val(\"111 111\");\n $(\"input[name='street']\").val(\"street test 1\");\n $(\"input[name='city']\").val(\"testCity\");\n $(\"input[name='zip']\").val(\"123\");\n $('#country_id option:eq(1)').attr('selected', true);\n },\n },\n {\n content: \"Validate address\",\n trigger: '.btn-primary:contains(\"Next\")',\n },\n {\n // if the seats_available checking logic is not correct,\n // the shopping cart will be cleared when selling the last ticket\n // the tour test will be failed here\n content: \"Select `Wire Transfer` payment method\",\n trigger: '#payment_method label:contains(\"Wire Transfer\")',\n },\n // following steps are based on the website_sale_buy.js\n {\n content: \"Pay\",\n //Either there are multiple payment methods, and one is checked, either there is only one, and therefore there are no radio inputs\n extra_trigger: '#payment_method label:contains(\"Wire Transfer\") input:checked,#payment_method:not(:has(\"input:radio:visible\"))',\n trigger: 'button[name=\"o_payment_submit_button\"]:visible:not(:disabled)',\n },\n {\n content: \"payment finish\",\n trigger: '.oe_website_sale:contains(\"Please use the following transfer details\")',\n // Leave /shop/confirmation to prevent RPC loop to /shop/payment/get_status.\n // The RPC could be handled in python while the tour is killed (and the session), leading to crashes\n run: function () {\n window.location.href = '/contactus'; // Redirect in JS to avoid the RPC loop (20x1sec)\n },\n timeout: 30000,\n },\n {\n content: \"wait page loaded\",\n trigger: 'h1:contains(\"Contact us\")',\n run: function () {}, // it's a check\n },\n]);\n});\n", "odoo.define('website_event_sale.tour.event_sale_pricelists_different_currencies', function (require) {\n 'use strict';\n\n const tour = require('web_tour.tour');\n const { getPriceListChecksSteps } = require('website_event_sale.tour.WebsiteEventSaleTourMethods');\n\n tour.register('event_sale_pricelists_different_currencies', {\n test: true,\n url: '/event',\n },[\n // Register for tickets\n {\n content: \"Open the Pycon event\",\n trigger: '.o_wevent_events_list a:contains(\"Pycon\")',\n },\n {\n content: \"Register\",\n trigger: '.btn-primary:contains(\"Register\")',\n },\n {\n content: \"Fill attendees details\",\n trigger: 'form[id=\"attendee_registration\"] .btn:contains(\"Continue\")',\n run: function () {\n $(\"input[name='1-name']\").val(\"Great Name\");\n $(\"input[name='1-phone']\").val(\"111 111\");\n $(\"input[name='1-email']\").val(\"great@name.com\");\n },\n },\n {\n content: \"Validate attendees details\",\n extra_trigger: \"input[name='1-name'], input[name='2-name']\",\n trigger: 'button:contains(\"Continue\")',\n },\n ...getPriceListChecksSteps({\n pricelistName: \"EUR With Discount Included\",\n eventName: \"Pycon\",\n price: \"90.00\",\n }),\n ...getPriceListChecksSteps({\n pricelistName: \"EUR Without Discount Included\",\n eventName: \"Pycon\",\n price: \"90.00\",\n priceBeforeDiscount: \"100.00\",\n }),\n ...getPriceListChecksSteps({\n pricelistName: \"EX With Discount Included\",\n eventName: \"Pycon\",\n price: \"900.00\",\n }),\n ...getPriceListChecksSteps({\n pricelistName: \"EX Without Discount Included\",\n eventName: \"Pycon\",\n price: \"900.00\",\n priceBeforeDiscount: \"1,000.00\",\n }),\n ]);\n});\n", "odoo.define('website_event_questions.tour_test_tickets_questions', function (require) {\n'use strict';\n\nvar tour = require('web_tour.tour');\ntour.register('test_tickets_questions', {\n test: true,\n url: '/event'\n}, [{\n content: \"Click on the Design Fair event\",\n trigger: 'article:contains(\"Design Fair New York\")',\n}, {\n content: \"Select 2 'Free' tickets to buy\",\n trigger: 'div.row:contains(\"Free\") select.form-select',\n run: 'text 2'\n}, {\n content: \"Click on Register tickets button\",\n trigger: 'button:contains(\"Register\")',\n run: 'click'\n}, {\n trigger: 'div:contains(\"Ticket #1\").modal-body input[name*=\"name\"]',\n run: 'text Attendee A'\n}, {\n trigger: 'div:contains(\"Ticket #1\").modal-body input[name*=\"email\"]',\n run: 'text attendee-a@gmail.com'\n}, {\n trigger: 'div:contains(\"Ticket #1\").modal-body input[name*=\"phone\"]',\n run: 'text +32499123456'\n}, {\n trigger: 'div:contains(\"Ticket #1\").modal-body select[name*=\"question_answer\"]',\n run: 'text Vegetarian'\n}, {\n trigger: 'div:contains(\"Ticket #1\").modal-body textarea[name*=\"question_answer\"]',\n run: 'text Fish and Nuts'\n}, {\n trigger: 'div:contains(\"Ticket #2\").modal-body input[name*=\"name\"]',\n run: 'text Attendee B'\n}, {\n trigger: 'div:contains(\"Ticket #2\").modal-body input[name*=\"email\"]',\n run: 'text attendee-b@gmail.com'\n}, {\n trigger: 'div:contains(\"Ticket #2\").modal-body select[name*=\"question_answer\"]',\n run: 'text Pastafarian'\n}, {\n trigger: 'div.o_wevent_registration_question_global select[name*=\"question_answer\"]',\n run: 'text A friend'\n}, {\n trigger: 'button:contains(\"Continue\")',\n run: 'click'\n}, {\n // The tour stops too early and the registration fails if we don't wait the confirmation.\n content: 'Wait for confirmation',\n trigger: '.o_wereg_confirmed, .oe_cart',\n auto: true\n}]);\n\n});\n", "odoo.define('website_links.website_links_tour', function (require) {\n'use strict';\n\nvar tour = require(\"web_tour.tour\");\n\ntour.register('website_links_tour', {\n test: true,\n url: '/r',\n},\n [\n // 1. Create a tracked URL\n {\n content: \"check that existing links are shown\",\n trigger: '#o_website_links_recent_links .btn_shorten_url_clipboard',\n run: function () {}, // it's a check\n },\n {\n content: \"fill the form and submit it\",\n trigger: '#o_website_links_link_tracker_form input#url',\n run: function () {\n var url = window.location.host + '/contactus';\n $('#o_website_links_link_tracker_form input#url').val(url);\n const campaignId = Object.entries($('#s2id_campaign-select')[0]).find(([key, value]) => value.select2)[1].select2.opts.data.find(d => d.text === \"Sale\").id\n $('.o_website_links_utm_forms input#campaign-select').val(campaignId).change();\n const channelId = Object.entries($('#s2id_channel-select')[0]).find(([key, value]) => value.select2)[1].select2.opts.data.find(d => d.text === \"Website\").id\n $('.o_website_links_utm_forms input#channel-select').val(channelId).change();\n const sourceId = Object.entries($('#s2id_source-select')[0]).find(([key, value]) => value.select2)[1].select2.opts.data.find(d => d.text === \"Search engine\").id\n $('.o_website_links_utm_forms input#source-select').val(sourceId).change();\n $('#btn_shorten_url').click();\n },\n },\n // 2. Visit it\n {\n content: \"check that link was created and visit it\",\n extra_trigger: '#o_website_links_recent_links .truncate_text:first():contains(\"Contact Us\")',\n trigger: '#o_website_links_link_tracker_form #generated_tracked_link:contains(\"/r/\")',\n run: function () {\n window.location.href = $('#generated_tracked_link').text();\n },\n },\n {\n content: \"check that we landed on correct page with correct query strings\",\n trigger: '.s_title h1:containsExact(\"Contact us\")',\n run: function () {\n var expectedUrl = \"/contactus?utm_campaign=Sale&utm_source=Search+engine&utm_medium=Website\";\n if (window.location.pathname + window.location.search !== expectedUrl) {\n console.error(\"The link was not correctly created. \" + window.location.search);\n }\n window.location.href = '/r';\n },\n },\n // 3. Check that counter got incremented and charts are correctly displayed\n {\n content: \"filter recently used links\",\n trigger: '#filter-recently-used-links',\n },\n {\n content: \"visit link stats page\",\n trigger: '#o_website_links_recent_links a:containsExact(\"Stats\"):first',\n },\n {\n content: \"check click number and ensure graphs are initialized\",\n extra_trigger: '.website_links_click_chart .title:contains(\"1 clicks\")',\n trigger: 'canvas',\n run: function () {}, // it's a check\n },\n {\n content: \"click on Last Month tab\",\n trigger: '.o_website_links_chart .graph-tabs a:contains(\"Last Month\")',\n },\n {\n content: \"ensure tab is correctly resized\",\n trigger: '#last_month_charts #last_month_clicks_chart',\n run: function () {\n var width = $('#last_month_charts #last_month_clicks_chart').width();\n if (width < 50) {\n console.error(\"The graphs are probably not resized on tab change.\");\n }\n },\n },\n ]\n);\n\n});\n", "/** @odoo-module */\n\nimport wTourUtils from 'website.tour_utils';\n\nwTourUtils.registerWebsitePreviewTour('newsletter_block_edition', {\n test: true,\n url: '/',\n edition: true,\n}, [\n // Put a Newsletter block.\n wTourUtils.dragNDrop({\n id: 's_newsletter_block',\n name: 'Newsletter Block',\n }),\n {\n content: 'Wait for the list id to be set.',\n trigger: 'iframe .s_newsletter_block[data-list-id]:not([data-list-id=\"0\"]) .s_newsletter_subscribe_form',\n run: () => null, // it's a check\n },\n ...wTourUtils.clickOnSave(),\n // Subscribe to the newsletter.\n {\n content: 'Wait for the email to be loaded in the newsletter input',\n trigger: 'iframe .s_newsletter_block .js_subscribe_btn',\n extra_trigger: 'iframe .s_newsletter_block input:propValue(admin@yourcompany.example.com)',\n },\n // Change the link style.\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n content: 'Click on the Subscribe button',\n trigger: 'iframe .s_newsletter_block .js_subscribe_btn',\n },\n {\n content: 'Toggle the option to display the Thanks button',\n trigger: 'we-button[data-toggle-thanks-button] we-checkbox',\n },\n {\n content: 'Click on the Thanks button',\n trigger: 'iframe .s_newsletter_block .js_subscribed_btn',\n },\n {\n content: 'Click on the link style button',\n trigger: '[data-bs-original-title=\"Link Style\"]',\n },\n {\n content: 'Click on the primary style button',\n trigger: '[data-value=\"primary\"]',\n },\n {\n content: 'Change the shape of the button',\n trigger: '[data-bs-original-title=\"Link Shape\"]',\n },\n {\n content: 'Click on the flat shape button',\n trigger: '[data-value=\"flat\"]',\n },\n ...wTourUtils.clickOnSave(),\n // Check if the button style is correct (make sure that the 'btn-success'\n // class which is not suggested as a valid style in the editor panel did not\n // prevent to edit the button).\n {\n content: 'Check that the link style is correct',\n trigger: 'iframe .s_newsletter_block .js_subscribed_btn.btn.btn-primary.flat:not(.btn-success)',\n },\n]);\n", "odoo.define(\"website_mass_mailing.tour.newsletter_popup_edition\", function (require) {\n\"use strict\";\n\nconst wTourUtils = require('website.tour_utils');\nconst newsletterPopupUseTour = require('website_mass_mailing.tour.newsletter_popup_use');\n\nwTourUtils.registerWebsitePreviewTour(\"newsletter_popup_edition\", {\n test: true,\n url: \"/\",\n edition: true,\n}, [\n wTourUtils.dragNDrop({\n id: 's_newsletter_subscribe_popup',\n name: 'Newsletter Popup',\n }),\n {\n content: \"Check the modal is opened for edition\",\n trigger: 'iframe .o_newsletter_popup .modal:visible',\n in_modal: false,\n run: () => null,\n },\n ...wTourUtils.clickOnSave(),\n {\n content: \"Check the modal has been saved, closed\",\n trigger: 'iframe body:has(.o_newsletter_popup)',\n extra_trigger: 'iframe body:not(.editor_enable)',\n run: newsletterPopupUseTour.ensurePopupNotVisible,\n }\n]);\n});\n\nodoo.define(\"website_mass_mailing.tour.newsletter_popup_use\", function (require) {\n\"use strict\";\n\nconst tour = require('web_tour.tour');\n\nfunction ensurePopupNotVisible() {\n const $modal = this.$anchor.find('.o_newsletter_popup .modal');\n if ($modal.length !== 1) {\n // Avoid the tour to succeed if the modal can't be found while\n // it should. Indeed, if the selector ever becomes wrong and the\n // expected element is actually not found anymore, the test\n // won't be testing anything anymore as the visible check will\n // always be truthy on empty jQuery element.\n console.error(\"Modal couldn't be found in the DOM. The tour is not working as expected.\");\n }\n if ($modal.is(':visible')) {\n console.error('Modal should not be opened.');\n }\n}\n\ntour.register('newsletter_popup_use', {\n test: true,\n url: '/',\n}, [\n {\n content: \"Check the modal is not yet opened and force it opened\",\n trigger: 'body:has(.o_newsletter_popup)',\n run: ensurePopupNotVisible,\n },\n {\n content: \"Check the modal is now opened and enter text in the subscribe input\",\n trigger: '.o_newsletter_popup .modal input',\n in_modal: false,\n run: 'text hello@world.com',\n },\n {\n content: \"Subscribe\",\n trigger: '.modal-dialog .btn-primary',\n },\n {\n content: \"Check the modal is now closed\",\n trigger: 'body:has(.o_newsletter_popup)',\n run: ensurePopupNotVisible,\n }\n]);\n\nreturn {\n ensurePopupNotVisible: ensurePopupNotVisible,\n};\n});\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\n\ntour.register('website_sale_stock_multilang', {\n test: true,\n url: '/fr/shop?search=unavailable',\n},\n[{\n content: \"Open unavailable product page\",\n trigger: 'a[content=\"unavailable_product\"]',\n}, {\n content: \"Check out of stock message\",\n trigger: '#out_of_stock_message:contains(\"Hors-stock\")',\n run: () => {}, // This is a check.\n}, {\n content: \"Check price\",\n trigger: 'span:contains(\"123,45\")',\n run: () => {}, // This is a check.\n}, {\n content: \"Open language selector\",\n trigger: '.js_language_selector button',\n}, {\n content: \"Switch to English\",\n trigger: '.js_change_lang[data-url_code=\"en\"]',\n}, {\n content: \"Check out of stock message\",\n trigger: '#out_of_stock_message:contains(\"Out of stock\")',\n run: () => {}, // This is a check.\n}, {\n content: \"Check price\",\n trigger: 'span:contains(\"123.45\")',\n run: () => {}, // This is a check.\n}, {\n content: \"Open language selector\",\n trigger: '.js_language_selector button',\n}, {\n content: \"Switch to French\",\n trigger: '.js_change_lang[data-url_code=\"fr\"]',\n}, {\n content: \"Check out of stock message\",\n trigger: '#out_of_stock_message:contains(\"Hors-stock\")',\n run: () => {}, // This is a check.\n}, {\n content: \"Check price\",\n trigger: 'span:contains(\"123,45\")',\n run: () => {}, // This is a check.\n}]);\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\n\ntour.register('back_in_stock_notification_product', {\n test: true,\n url: '/shop?search=Macbook%20Pro',\n },\n [\n {\n content: \"Open product page\",\n trigger: 'a:contains(\"Macbook Pro\")',\n },\n {\n content: \"Click on 'Be notified when back in stock'\",\n trigger: '#product_stock_notification_message',\n },\n {\n content: \"Fill email form\",\n trigger: 'div[id=\"stock_notification_form\"] input[name=\"email\"]',\n run: 'text test@test.test',\n },\n {\n content: \"Click on the button\",\n trigger: '#product_stock_notification_form_submit_button',\n },\n {\n content: \"Success Message\",\n trigger: '#stock_notification_success_message',\n },\n ],\n);\n", "odoo.define('website_sale_delivery.tour', function (require) {\n'use strict';\n\nvar tour = require(\"web_tour.tour\");\nconst tourUtils = require('website_sale.tour_utils');\n\ntour.register('check_free_delivery', {\n test: true,\n url: '/shop?search=office chair black',\n},\n [\n // Part 1: Check free delivery\n {\n content: \"select office chair black\",\n trigger: '.oe_product_cart a:contains(\"Office Chair Black TEST\")',\n },\n {\n content: \"click on add to cart\",\n trigger: '#product_details #add_to_cart',\n },\n tourUtils.goToCart(),\n {\n content: \"go to checkout\",\n extra_trigger: '#cart_products input.js_quantity:propValue(1)',\n trigger: 'a[href*=\"/shop/checkout\"]',\n },\n {\n content: \"Check Free Delivery value to be zero\",\n extra_trigger: '#delivery_carrier label:containsExact(\"Delivery Now Free Over 10\")',\n trigger: \"#delivery_carrier span:contains('0.0')\"\n },\n // Part 2: check multiple delivery & price loaded asynchronously\n {\n content: \"Ensure price was loaded asynchronously\",\n extra_trigger: '#delivery_carrier input[name=\"delivery_type\"]:checked',\n trigger: '#delivery_method .o_delivery_carrier_select:contains(\"20.0\"):contains(\"The Poste\")',\n run: function () {}, // it's a check\n },\n {\n content: \"Select `Wire Transfer` payment method\",\n trigger: '#payment_method label:contains(\"Wire Transfer\")',\n },\n {\n content: \"Click on Pay Now\",\n trigger: 'button[name=\"o_payment_submit_button\"]:visible:not(:disabled)',\n },\n {\n content: \"Confirmation page should be shown\",\n trigger: '#oe_structure_website_sale_confirmation_1',\n run: function () {}, // it's a check\n }\n ]);\n});\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\nimport tourUtils from 'website_sale.tour_utils';\n\ntour.register('shop_sale_gift_card', {\n test: true,\n url: '/shop?search=Small%20Drawer'\n},\n [\n // Add a small drawer to the order (50$)\n {\n content: 'select Small Drawer',\n extra_trigger: '.oe_search_found',\n trigger: '.oe_product_cart a:contains(\"TEST - Small Drawer\")',\n },\n {\n content: 'Add Small Drawer into cart',\n trigger: 'a:contains(ADD TO CART)',\n },\n tourUtils.goToCart(),\n {\n content: 'Click on \"I have a promo code\"',\n extra_trigger: '#cart_products',\n trigger: '.show_coupon',\n },\n {\n content: 'insert gift card code',\n trigger: 'form[name=\"coupon_code\"] input[name=\"promo\"]',\n run: 'text GIFT_CARD'\n },\n {\n content: 'validate the gift card',\n trigger: 'form[name=\"coupon_code\"] .a-submit',\n },\n {\n content: 'check gift card line',\n trigger: '.td-product_name:contains(\"PAY WITH GIFT CARD\")',\n },\n {\n content: 'Click on \"I have a promo code\"',\n trigger: '.show_coupon',\n },\n {\n content: 'insert gift card code',\n extra_trigger: 'form[name=\"coupon_code\"]',\n trigger: 'form[name=\"coupon_code\"] input[name=\"promo\"]',\n run: 'text 10PERCENT'\n },\n {\n content: 'validate the gift card',\n trigger: 'form[name=\"coupon_code\"] .a-submit',\n },\n {\n content: 'check gift card amount',\n trigger: '.oe_currency_value:contains(\"-45.00\")',\n trigger: '.oe_website_sale .oe_cart',\n run: function () {}, // it's a check\n },\n {\n content: 'go to shop',\n trigger: 'a:contains(\"Shop\")',\n },\n {\n content: \"type Gift Card in search\",\n trigger: 'form input[name=\"search\"]',\n run: \"text Gift Card\",\n },\n {\n content: \"start search\",\n trigger: 'form:has(input[name=\"search\"]) .oe_search_button',\n },\n {\n content: \"select Gift Card\",\n extra_trigger: '.oe_search_found', // Wait to be on search results or it sometimes throws concurent error (sent search form + click on product on /shop)\n trigger: '.oe_product_cart a:containsExact(\"TEST - Gift Card\")',\n },\n {\n content: \"click on 'Add to Cart' button\",\n trigger: \"a:contains(ADD TO CART)\",\n },\n tourUtils.goToCart({quantity: 2}),\n {\n content: 'check gift card amount',\n trigger: '.oe_currency_value:contains(\"-45.00\")',\n trigger: '.oe_website_sale .oe_cart',\n run: function () {}, // it's a check\n },\n ],\n);\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\nimport ajax from 'web.ajax';\nimport tourUtils from 'website_sale.tour_utils';\n\ntour.register('shop_sale_loyalty', {\n test: true,\n url: '/shop?search=Small%20Cabinet',\n},\n [\n /* 1. Buy 1 Small Cabinet, enable coupon code & insert 10% code */\n {\n content: \"select Small Cabinet\",\n extra_trigger: '.oe_search_found',\n trigger: '.oe_product_cart a:contains(\"Small Cabinet\")',\n },\n {\n content: \"add 2 Small Cabinet into cart\",\n trigger: '#product_details input[name=\"add_qty\"]',\n run: \"text 2\",\n },\n {\n content: \"click on 'Add to Cart' button\",\n trigger: \"a:contains(ADD TO CART)\",\n },\n tourUtils.goToCart({quantity: 2}),\n {\n content: \"click on 'I have a promo code'\",\n extra_trigger: '.show_coupon',\n trigger: '.show_coupon',\n },\n {\n content: \"insert promo code 'testcode'\",\n extra_trigger: 'form[name=\"coupon_code\"]',\n trigger: 'form[name=\"coupon_code\"] input[name=\"promo\"]',\n run: \"text testcode\",\n },\n {\n content: \"validate the coupon\",\n trigger: 'form[name=\"coupon_code\"] .a-submit',\n },\n {\n content: \"check reward product\",\n trigger: '.td-product_name:contains(\"10.0% discount on total amount\")',\n run: function () {}, // it's a check\n },\n {\n content: \"check loyalty points\",\n trigger: '.oe_website_sale_gift_card span:contains(\"372.03 Points\")',\n run: function () {}, // it's a check\n },\n /* 2. Add some cabinet to get a free one, play with quantity */\n {\n content: \"go to shop\",\n trigger: '.td-product_name:contains(\"10.0% discount on total amount\")',\n run: function () {\n ajax.jsonRpc('/web/dataset/call_kw', 'call', {\n model: 'account.tax',\n method: 'create',\n args: [{\n 'name':'15% tax incl ' + _.now(),\n 'amount': 15,\n }],\n kwargs: {},\n }).then(function (tax_id) {\n ajax.jsonRpc('/web/dataset/call_kw', 'call', {\n model: 'product.template',\n method: 'create',\n args: [{\n 'name': 'Taxed Product',\n 'taxes_id': [([6, false, [tax_id]])],\n 'list_price': 100,\n 'website_published': true,\n }],\n kwargs: {},\n }).then(function (data) {\n location.href = '/shop';\n });\n });\n },\n },\n {\n content: \"type Taxed Product in search\",\n trigger: 'form input[name=\"search\"]',\n run: \"text Taxed Product\",\n },\n {\n content: \"start search\",\n trigger: 'form:has(input[name=\"search\"]) .oe_search_button',\n },\n {\n content: \"select Taxed Product\",\n extra_trigger: '.oe_search_found', // Wait to be on search results or it sometimes throws concurent error (sent search form + click on product on /shop)\n trigger: '.oe_product_cart a:containsExact(\"Taxed Product\")',\n },\n {\n content: \"click on 'Add to Cart' button\",\n trigger: \"a:contains(ADD TO CART)\",\n },\n tourUtils.goToCart({quantity: 3}),\n {\n content: \"check reduction amount got recomputed and merged both discount lines into one only\",\n extra_trigger: '.oe_currency_value:contains(\"-\ufeff74.00\"):not(#cart_total .oe_currency_value:contains(\"-\ufeff74.00\"))',\n trigger: '.oe_website_sale .oe_cart',\n run: function () {}, // it's a check\n },\n /* 3. Add some cabinet to get a free one, play with quantity */\n {\n content: \"add one Small Cabinet\",\n trigger: '#cart_products input.js_quantity',\n run: \"text 3\",\n },\n {\n content: \"check reduction amount got recomputed when changing qty\",\n trigger: '.oe_currency_value:contains(\"-\ufeff106.00\")',\n run: function () {}, // it's a check\n },\n {\n content: \"add more Small Cabinet into cart\",\n trigger: '#cart_products input.js_quantity',\n run: \"text 4\",\n },\n {\n content: \"check free product is added\",\n trigger: '#wrap:has(.td-product_name:contains(\"Free Product - Small Cabinet\"))',\n run: function () {}, // it's a check\n },\n {\n content: \"remove one cabinet from cart\",\n trigger: '#cart_products input.js_quantity[value=\"4\"]',\n run: \"text 3\",\n },\n {\n content: \"check free product is removed\",\n trigger: '#wrap:not(:has(.td-product_name:contains(\"Free Product - Small Cabinet\")))',\n run: function () {}, // it's a check\n },\n /* 4. Check /shop/payment does not break the `merged discount lines split per tax` (eg: with _compute_tax_id) */\n {\n content: \"go to checkout\",\n trigger: 'a[href=\"/shop/checkout?express=1\"]',\n },\n {\n content: \"check total is unchanged once we land on payment page\",\n extra_trigger: '#payment_method h3:contains(\"Pay with\")',\n trigger: 'tr#order_total .oe_currency_value:contains(\"967.50\")',\n run: function () {}, // it's a check\n },\n ]\n);\n", "/** @odoo-module alias=website_sale_loyalty_giftcard.test **/\n\nimport tour from 'web_tour.tour';\nimport wsTourUtils from \"website_sale.tour_utils\";\n\ntour.register('shop_sale_loyalty_delivery', {\n test: true,\n url: '/shop?search=Accoustic',\n },\n [\n {\n content: \"select Small Cabinet\",\n trigger: '.oe_product a:contains(\"Acoustic Bloc Screens\")',\n },\n {\n content: \"add 1 Small Cabinet into cart\",\n trigger: '#product_details input[name=\"add_qty\"]',\n run: \"text 1\",\n },\n {\n content: \"click on 'Add to Cart' button\",\n trigger: \"a:contains(ADD TO CART)\",\n },\n wsTourUtils.goToCart(1),\n {\n content: \"go to checkout\",\n trigger: 'a[href=\"/shop/checkout?express=1\"]',\n run: 'click'\n },\n {\n content: \"select delivery method 1\",\n trigger: \"li label:contains(delivery1)\",\n run: 'click'\n },\n {\n content: \"click on 'Pay with gift card'\",\n trigger: '.show_coupon',\n run: 'click'\n },\n {\n content: \"Enter gift card code\",\n trigger: \"form[name='coupon_code'] input[name='promo']\",\n run: 'text 123456'\n },\n {\n content: \"click on 'Pay'\",\n trigger: \"a[role='button'].a-submit:contains(Apply)\",\n run: 'click'\n },\n {\n content: \"check if delivery price is correct'\",\n trigger: \"#order_delivery .oe_currency_value:contains(5.00)\",\n run: () => {} // this is a check\n },\n {\n content: \"check if total price is correct\",\n trigger: \"tr#order_total span.oe_currency_value:contains(0.00)\",\n run: () => {} // this is a check\n },\n ]\n);\n\ntour.register('check_shipping_discount', {\n test: true,\n url: '/shop?search=Plumbus',\n },\n [\n {\n content: \"select Plumbus\",\n trigger: '.oe_product a:contains(\"Plumbus\")',\n },\n {\n content: \"add 3 Plumbus into cart\",\n trigger: '#product_details input[name=\"add_qty\"]',\n run: \"text 3\",\n },\n {\n content: \"click on 'Add to Cart' button\",\n trigger: \"a:contains(ADD TO CART)\",\n },\n wsTourUtils.goToCart({quantity: 3}),\n {\n content: \"go to checkout\",\n trigger: 'a[href=\"/shop/checkout?express=1\"]',\n run: 'click'\n },\n {\n content: \"select delivery with rule\",\n trigger: \"li label:contains(rule)\",\n run: 'click'\n },\n {\n content: \"check if delivery price is correct'\",\n trigger: 'label:contains(\"delivery with rule\") + span.o_wsale_delivery_badge_price:contains(100.00)',\n run: () => {} // this is a check\n },\n {\n content: \"check if delivery price is correct'\",\n trigger: \"#order_delivery .oe_currency_value:contains(25.00)\",\n run: () => {} // this is a check\n },\n {\n content: \"check if delivery price is correct'\",\n trigger: \"[data-reward-type='shipping']:contains(-\ufeff75.00)\",\n run: () => {} // this is a check\n },\n ]\n );\n", "odoo.define(\"website_sale_product_configurator.website_sale_tour\", function (require) {\n\"use strict\";\n/**\n * Add custom steps to handle the optional products modal introduced\n * by the product configurator module.\n */\nvar tour = require('web_tour.tour');\nrequire('website_sale.tour');\n\nvar addCartStepIndex = _.findIndex(tour.tours.shop_buy_product.steps, function (step) {\n return (step.id === 'add_cart_step');\n});\n\ntour.tours.shop_buy_product.steps.splice(addCartStepIndex + 1, 1, {\n content: \"click in modal on 'Proceed to checkout' button\",\n trigger: 'button:contains(\"Proceed to Checkout\")',\n});\n\n});\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\n\ntour.register('website_sale_product_configurator_optional_products_tour', {\n test: true,\n}, [{\n name: 'Click Aluminium Option',\n trigger: 'ul.js_add_cart_variants span:contains(\"Aluminium\")',\n extra_trigger: 'ul.js_add_cart_variants span:contains(\"Aluminium\") ~ span.badge:contains(\"50.40\")',\n}, {\n name: 'Add to cart',\n trigger: '#add_to_cart',\n}, {\n name: 'Check that modal was opened with the correct variant price',\n trigger: 'main.oe_advanced_configurator_modal',\n extra_trigger: 'main.oe_advanced_configurator_modal span:contains(\"800.40\")',\n run: () => {},\n}]);\n", "odoo.define(\"website_sale_product_TESTurator.tour_shop_custom_attribute_value\", function (require) {\n\"use strict\";\n\nvar tour = require('web_tour.tour');\nvar optionVariantImage;\n\ntour.register(\"a_shop_custom_attribute_value\", {\n url: \"/shop?search=Customizable Desk\",\n test: true,\n}, [{\n content: \"click on Customizable Desk\",\n trigger: '.oe_product_cart a:contains(\"Customizable Desk (TEST)\")',\n}, {\n trigger: 'a.js_add_cart_json:has(i.fa-plus)',\n run: 'click',\n}, {\n trigger: 'span.text-danger span:contains(750)',\n run: function (){}, // check\n}, {\n trigger: 'span.oe_price span:contains(600)',\n run: function (){}, // check\n}, {\n id: 'add_cart_step',\n trigger: 'a:contains(ADD TO CART)',\n run: 'click',\n}, {\n trigger: '.oe_advanced_configurator_modal .js_product:eq(1) div:contains(\"Conference Chair (TEST) (Steel)\")',\n run: function () {\n optionVariantImage = $('.oe_advanced_configurator_modal .js_product:eq(1) img.variant_image').attr('src');\n }\n}, {\n trigger: '.oe_advanced_configurator_modal .js_product:eq(1) input[data-value_name=\"Aluminium\"]',\n}, {\n trigger: '.oe_advanced_configurator_modal .js_product:eq(1) div:contains(\"Conference Chair (TEST) (Aluminium)\")',\n run: function () {\n var newVariantImage = $('.oe_advanced_configurator_modal .js_product:eq(1) img.variant_image').attr('src');\n if (newVariantImage !== optionVariantImage) {\n $('

').text('image variant option src changed').insertAfter('.oe_advanced_configurator_modal .js_product:eq(1) .product-name');\n }\n }\n}, {\n extra_trigger: '.oe_advanced_configurator_modal .js_product:eq(1) div:contains(\"image variant option src changed\")',\n trigger: '.oe_advanced_configurator_modal .js_product:eq(1) input[data-value_name=\"Steel\"]',\n}, {\n trigger: '.oe_price span:contains(22.90)',\n run: function (){}, // check\n}, {\n trigger: '.oe_advanced_configurator_modal .js_product:has(strong:contains(Conference Chair)) .js_add',\n extra_trigger: '.oe_advanced_configurator_modal .js_product:has(strong:contains(Conference Chair))',\n run: 'click'\n}, {\n trigger: '.oe_advanced_configurator_modal .js_product:has(strong:contains(Chair floor protection)) .js_add',\n extra_trigger: '.oe_advanced_configurator_modal .js_product:has(strong:contains(Chair floor protection))',\n run: 'click'\n}, {\n trigger: 'span:contains(1,257.00)',\n run: function (){}, // check\n}, {\n trigger: 'button:has(span:contains(Proceed to Checkout))',\n run: 'click',\n}]);\n\n});\n", "odoo.define('website_sale.tour_variants_modal_window', function (require) {\n 'use strict';\n\n var tour = require('web_tour.tour');\n\n // This tour relies on a data created from the python test.\n tour.register('tour_variants_modal_window', {\n test: true,\n url: '/shop?search=Short (TEST)',\n },\n [\n {\n content: \"Select the Short (TEST) product\",\n trigger: '.oe_product_cart a:containsExact(\"Short (TEST)\")',\n },\n {\n content: \"Click on the always variant\",\n trigger: 'input[data-attribute_name=\"Always attribute size\"][data-value_name=\"M always\"]',\n },\n {\n content: \"Click on the dynamic variant\",\n trigger: 'input[data-attribute_name=\"Dynamic attribute size\"][data-value_name=\"M dynamic\"]',\n },\n {\n content: \"Click on the never variant\",\n trigger: 'input[data-attribute_name=\"Never attribute size\"][data-value_name=\"M never\"]',\n },\n {\n content: \"Click on the never custom variant\",\n trigger: 'input[data-attribute_name=\"Never attribute size custom\"][data-value_name=\"Yes never custom\"]',\n },\n {\n trigger: 'input.variant_custom_value',\n run: 'text TEST',\n },\n {\n content: \"Click add to cart\",\n trigger: '#add_to_cart',\n },\n {\n content: \"Go through the modal window of the product configurator\",\n extra_trigger: '.oe_advanced_configurator_modal',\n trigger: 'button span:contains(Proceed to Checkout)',\n run: 'click'\n },\n {\n content: \"Check the product is in the cart\",\n trigger: 'td.td-product_name:contains(Short (TEST))',\n },\n {\n content: \"Check always variant\",\n trigger: 'td.td-product_name:contains(M always)',\n },\n {\n content: \"Check dynamic variant\",\n trigger: 'td.td-product_name:contains(M dynamic)',\n },\n {\n content: \"Check never variant\",\n trigger: 'td.td-product_name:contains(Never attribute size: M never)',\n },\n {\n content: \"Check never custom variant\",\n trigger: 'td.td-product_name:contains(Never attribute size custom: Yes never custom: TEST)',\n }\n ]);\n});\n", "odoo.define('website_sale.website_sale_stock_message_after_close_configurator_modal', function (require) {\n 'use strict';\n\n var tour = require('web_tour.tour');\n\n // This tour relies on a data created from the python test.\n tour.register('website_sale_stock_message_after_close_onfigurator_modal_with_optional_products', {\n test: true,\n url: '/shop?search=Product With Optional (TEST)',\n },\n [\n {\n content: \"Select Customizable Desk\",\n trigger: '.oe_product_cart a:contains(\"Product With Optional (TEST)\")',\n }, {\n content: \"Check that the stock quantity is displayed and correct\",\n trigger: '#threshold_message:contains(\"30\")',\n timout: 1000,\n run: function () { },\n }, {\n content: \"Add to cart\",\n trigger: '#add_to_cart',\n }, {\n content: \"Continue shoppping\",\n extra_trigger: '.oe_advanced_configurator_modal',\n trigger: 'button span:contains(Continue Shopping)',\n run: 'click'\n }, {\n content: \"Check that the stock quantity is displayed and correct after adding to cart\",\n trigger: '#threshold_message:contains(\"29\")',\n timout: 1000,\n run: function () { },\n }\n ]);\n\n tour.register('website_sale_stock_message_after_close_onfigurator_modal_without_optional_products', {\n test: true,\n url: '/shop?search=Product Without Optional (TEST)',\n },\n [\n {\n content: \"Select Office Lamp\",\n trigger: '.oe_product_cart a:contains(\"Product Without Optional (TEST)\")',\n }, {\n content: \"Check that the stock quantity is displayed and correct\",\n trigger: '#threshold_message:contains(\"30\")',\n timout: 1000,\n run: function () { },\n }, {\n content: \"Add to cart\",\n trigger: '#add_to_cart',\n }, {\n content: \"Check that the stock quantity is displayed and correct after adding to cart\",\n trigger: '#threshold_message:contains(\"29\")',\n timout: 1000,\n run: function () { },\n }\n ]);\n});\n", "odoo.define('website_sale_wishlist.tour', function (require) {\n'use strict';\n\nvar rpc = require('web.rpc');\nvar tour = require(\"web_tour.tour\");\n\ntour.register('shop_wishlist', {\n test: true,\n url: '/shop?search=Customizable Desk',\n},\n [\n {\n content: \"click on add to wishlist\",\n trigger: '.o_add_wishlist',\n },\n {\n content: \"go to wishlist\",\n extra_trigger: 'a[href=\"/shop/wishlist\"] .badge:contains(1)',\n trigger: 'a[href=\"/shop/wishlist\"]',\n },\n {\n content: \"remove first item in whishlist\",\n trigger: '.o_wish_rm:first',\n },\n {\n content: \"go back to the store\",\n trigger: \"a[href='/shop']\"\n },\n {\n content: \"click on add to wishlist\",\n trigger: '.o_add_wishlist',\n },\n {\n content: \"check value of wishlist and go to login\",\n extra_trigger: \".my_wish_quantity:contains(1)\",\n trigger: 'a[href=\"/web/login\"]',\n },\n {\n content: \"submit login\",\n trigger: \".oe_login_form\",\n run: function (){\n $('.oe_login_form input[name=\"login\"]').val(\"admin\");\n $('.oe_login_form input[name=\"password\"]').val(\"admin\");\n $('.oe_login_form input[name=\"redirect\"]').val(\"/shop?search=Customizable Desk\");\n $('.oe_login_form').submit();\n },\n },\n {\n content: \"check that logged in\",\n trigger: \"li span:contains('Mitchell Admin')\",\n run: function () {},\n },\n {\n content: \"click on Customizable Desk (TEST)\",\n trigger: '.oe_product_cart a:contains(\"Customizable Desk\")',\n },\n {\n content: \"check the first variant is already in wishlist\",\n trigger: '#product_detail .o_add_wishlist_dyn:disabled',\n run: function () {},\n },\n {\n content: \"change variant\",\n extra_trigger: '#product_detail label:contains(Aluminium) input',\n trigger: 'label:contains(Aluminium) input',\n },\n {\n content: \"wait button enable and click on add to wishlist\",\n extra_trigger: '#product_detail .o_add_wishlist_dyn:not(:disabled)',\n trigger: '#product_detail .o_add_wishlist_dyn',\n },\n {\n content: \"check that wishlist contains 2 items and go to wishlist\",\n extra_trigger: 'a[href=\"/shop/wishlist\"] .badge:contains(2)',\n trigger: 'a[href=\"/shop/wishlist\"]',\n },\n {\n content: \"remove Customizable Desk (TEST)\",\n trigger: 'tr:contains(\"Customizable Desk\") .o_wish_rm:first',\n },\n {\n content: \"check that wishlist contains 1 item\",\n trigger: \".my_wish_quantity:contains(1)\",\n run: function() {},\n },\n {\n content: \"check B2B wishlist mode\",\n trigger: \"input#b2b_wish\",\n },\n {\n content: \"add item to cart\",\n trigger: '.o_wish_add:eq(1)',\n },\n {\n content: \"check that cart contains 1 item\",\n trigger: \".my_cart_quantity:contains(1)\",\n run: function() {},\n },\n {\n content: \"check that wishlist contains 1 item\",\n trigger: \".my_wish_quantity:contains(1)\",\n run: function() {},\n },\n {\n content: \"remove B2B wishlist mode\",\n trigger: \"input#b2b_wish\",\n },\n {\n content: \"add last item to cart\",\n trigger: '.o_wish_add:eq(1)',\n },\n {\n content: \"check that user is redirect - wishlist is empty\",\n trigger: \"#wrap #cart_products\",\n run: function() {},\n },\n {\n content: \"check that cart contains 2 items\",\n trigger: \".my_cart_quantity:contains(2)\",\n run: function() {},\n },\n {\n content: \"check that wishlist is empty and no more visible\",\n trigger: \":not(:has(.my_wish_quantity:visible))\",\n run: function() {},\n },\n // Test dynamic attributes\n {\n content: \"Create a product with dynamic attribute and its values.\",\n trigger: 'body',\n run: function () {\n rpc.query({\n model: 'product.attribute',\n method: 'create',\n args: [{\n 'name': \"color\",\n 'display_type': 'color',\n 'create_variant': 'dynamic'\n }],\n }).then(function (attributeId) {\n return rpc.query({\n model: 'product.template',\n method: 'create',\n args: [{\n 'name': \"Bottle\",\n 'is_published': true,\n 'attribute_line_ids': [[0, 0, {\n 'attribute_id': attributeId,\n 'value_ids': [\n [0, 0, {\n 'name': \"red\",\n 'attribute_id': attributeId,\n }],\n [0, 0, {\n 'name': \"blue\",\n 'attribute_id': attributeId,\n }],\n [0, 0, {\n 'name': \"black\",\n 'attribute_id': attributeId,\n }],\n ]\n }]],\n }],\n });\n }).then(function () {\n window.location.href = '/web/session/logout?redirect=/shop?search=Bottle';\n });\n },\n },\n {\n content: \"Add Bottle to wishlist from /shop\",\n extra_trigger: '.oe_product_cart:contains(\"Bottle\")',\n trigger: '.oe_product_cart:contains(\"Bottle\") .o_add_wishlist',\n },\n {\n content: \"Check that wishlist contains 1 item\",\n trigger: '.my_wish_quantity:contains(1)',\n run: function () {},\n },\n {\n content: \"Click on product\",\n extra_trigger: '.oe_product_cart:contains(\"Bottle\") .o_add_wishlist.disabled',\n trigger: '.oe_product_cart a:contains(\"Bottle\")',\n },\n {\n content: \"Select Bottle with second variant from /product\",\n trigger: '.js_variant_change[data-value_name=\"blue\"]',\n },\n {\n content: \"Add product in wishlist\",\n extra_trigger: '#product_detail .o_add_wishlist_dyn:not(\".disabled\")',\n trigger: '#product_detail .o_add_wishlist_dyn',\n },\n {\n content: \"Select Bottle with third variant from /product\",\n trigger: '.js_variant_change[data-value_name=\"black\"]',\n },\n {\n content: \"Add product in wishlist\",\n extra_trigger: '#product_detail .o_add_wishlist_dyn:not(\".disabled\")',\n trigger: '#product_detail .o_add_wishlist_dyn',\n },\n {\n content: \"Check that wishlist contains 3 items and go to wishlist\",\n trigger: '.my_wish_quantity:contains(3)',\n run: function () {\n window.location.href = '/shop/wishlist';\n },\n },\n {\n content: \"Check wishlist contains first variant\",\n trigger: '#o_comparelist_table tr:contains(\"red\")',\n run: function () {},\n },\n {\n content: \"Check wishlist contains second variant\",\n trigger: '#o_comparelist_table tr:contains(\"blue\")',\n run: function () {},\n },\n {\n content: \"Check wishlist contains third variant, then go to login\",\n trigger: '#o_comparelist_table tr:contains(\"black\")',\n run: function () {\n window.location.href = \"/web/login\";\n },\n },\n {\n content: \"Submit login as admin\",\n trigger: '.oe_login_form',\n run: function () {\n $('.oe_login_form input[name=\"login\"]').val(\"admin\");\n $('.oe_login_form input[name=\"password\"]').val(\"admin\");\n $('.oe_login_form input[name=\"redirect\"]').val(\"/\");\n $('.oe_login_form').submit();\n },\n },\n // Test one impossible combination while other combinations are possible\n {\n content: \"Archive the first variant\",\n trigger: '#top_menu:contains(\"Mitchell Admin\")',\n run: function () {\n rpc.query({\n model: 'product.product',\n method: 'search',\n args: [[['name', '=', \"Bottle\"]]],\n })\n .then(function (productIds) {\n return rpc.query({\n model: 'product.product',\n method: 'write',\n args: [productIds[0], {active: false}],\n });\n })\n .then(function () {\n window.location.href = '/web/session/logout?redirect=/shop?search=Bottle';\n });\n },\n },\n {\n content: \"Check there is wishlist button on product from /shop\",\n extra_trigger: '.js_sale',\n trigger: '.oe_product_cart:contains(\"Bottle\") .o_add_wishlist',\n run: function () {},\n },\n {\n content: \"Click on product\",\n trigger: '.oe_product_cart a:contains(\"Bottle\")',\n },\n {\n content: \"Select Bottle with first variant (red) from /product\",\n trigger: '.js_variant_change[data-value_name=\"red\"]',\n },\n {\n content: \"Check there is no wishlist button when selecting impossible variant\",\n trigger: '#product_detail:not(:has(.o_add_wishlist))',\n run: function () {},\n },\n {\n content: \"Select Bottle with second variant (blue) from /product\",\n trigger: '.js_variant_change[data-value_name=\"blue\"]',\n },\n {\n content: \"Click on wishlist when selecting a possible variant from /product\",\n trigger: '#product_detail .o_add_wishlist_dyn:not(.disabled)',\n },\n {\n content: \"Check product added to wishlist and go to login\",\n trigger: '.my_wish_quantity:contains(1)',\n run: function () {\n window.location.href = \"/web/login\";\n },\n },\n {\n content: \"Submit login\",\n trigger: '.oe_login_form',\n run: function () {\n $('.oe_login_form input[name=\"login\"]').val(\"admin\");\n $('.oe_login_form input[name=\"password\"]').val(\"admin\");\n $('.oe_login_form input[name=\"redirect\"]').val(\"/\");\n $('.oe_login_form').submit();\n },\n },\n // test when all combinations are impossible\n {\n content: \"Archive all variants\",\n trigger: '#top_menu:contains(\"Mitchell Admin\")',\n run: function () {\n rpc.query({\n model: 'product.product',\n method: 'search',\n args: [[['name', '=', \"Bottle\"]]],\n })\n .then(function (productIds) {\n return rpc.query({\n model: 'product.product',\n method: 'write',\n args: [productIds, {active: false}],\n });\n })\n .then(function () {\n window.location.href = '/web/session/logout?redirect=/shop?search=Bottle';\n });\n }\n },\n {\n content: \"Check that there is no wishlist button from /shop\",\n extra_trigger: '.js_sale',\n trigger: '.oe_product_cart:contains(\"Bottle\"):not(:has(.o_add_wishlist))',\n run: function () {},\n },\n {\n content: \"Click on product\",\n trigger: '.oe_product_cart a:contains(\"Bottle\")',\n },\n {\n content: \"Check that there is no wishlist button from /product\",\n trigger: '#product_detail:not(:has(.o_add_wishlist_dyn))',\n run: function () {},\n },\n // Test if the wishlist button is active or not in /shop\n {\n content: \"Go to '/shop?search=Customizable Desk'\",\n trigger: 'body',\n run: function () {\n window.location.href = '/shop?search=Customizable Desk '\n },\n },\n {\n content: \"Click on the product\",\n trigger: '.oe_product_image_link img',\n },\n {\n content: \"Add the product in the wishlist\",\n trigger: '#product_option_block .o_add_wishlist_dyn',\n },\n {\n content: \"Added into the wishlist\",\n trigger: '.my_wish_quantity.text-bg-primary:contains(1)',\n run: function () {},\n },\n {\n content: \"Go to '/shop\",\n trigger: '#top_menu_collapse a[href=\"/shop\"]',\n },\n {\n content: \"Search the product Customizable Desk'\",\n trigger: 'form.o_wsale_products_searchbar_form input',\n run: function () {\n $('form.o_wsale_products_searchbar_form input[name=\"search\"]').val(\"Customizable Desk\");\n $('form.o_wsale_products_searchbar_form button').click();\n },\n },\n {\n content: \"The product is in the wishlist\",\n trigger: '.oe_product_cart .o_wsale_product_information:has(.o_add_wishlist[disabled])',\n run: function () {},\n },\n {\n content: \"Go to the wishlist\",\n trigger: 'a[href=\"/shop/wishlist\"]',\n },\n {\n content: \"Remove the product from the wishlist\",\n trigger: '.o_wish_rm',\n },\n {\n content: \"Go to '/shop\",\n trigger: '#top_menu_collapse a[href=\"/shop\"]',\n },\n {\n content: \"Search the product Customizable Desk'\",\n trigger: 'form.o_wsale_products_searchbar_form input',\n run: function () {\n $('form.o_wsale_products_searchbar_form input[name=\"search\"]').val(\"Customizable Desk\");\n $('form.o_wsale_products_searchbar_form button').click();\n },\n },\n {\n content: \"The product is not in the wishlist\",\n trigger: '.oe_product_cart .o_wsale_product_information:not(:has(.o_add_wishlist[disabled]))',\n run: function () {},\n },\n ]\n);\n\n});\n", "odoo.define('website_sale_wishlist_admin.tour', function (require) {\n'use strict';\n\nconst wTourUtils = require(\"website.tour_utils\");\n\nwTourUtils.registerWebsitePreviewTour('shop_wishlist_admin', {\n url: '/shop?search=Rock',\n test: true,\n},\n [\n {\n content: \"Go to Rock shop page\",\n trigger: 'iframe a:contains(\"Rock\"):first',\n },\n {\n content: \"check list view of variants is disabled initially (when on /product page)\",\n trigger: 'iframe body:not(:has(.js_product_change))',\n extra_trigger: 'iframe #product_details',\n },\n ...wTourUtils.clickOnEditAndWaitEditMode(),\n {\n content: \"open customize tab\",\n trigger: '.o_we_customize_snippet_btn',\n },\n {\n content: \"open 'Variants' selector\",\n extra_trigger: '#oe_snippets .o_we_customize_panel',\n trigger: '[data-name=\"variants_opt\"] we-toggler',\n },\n {\n content: \"click on 'List View of Variants'\",\n trigger: 'we-button[data-name=\"variants_products_list_opt\"]',\n },\n ...wTourUtils.clickOnSave(),\n {\n content: \"check page loaded after list of variant customization enabled\",\n trigger: 'iframe .js_product_change',\n },\n {\n content: \"Add red product in wishlist\",\n trigger: 'iframe #product_detail .o_add_wishlist_dyn:not(\".disabled\")',\n },\n {\n content: \"Check that wishlist contains 1 items\",\n trigger: 'iframe .my_wish_quantity:contains(1)',\n run: function () {\n window.location.href = '/@/shop/wishlist';\n }\n },\n {\n content: \"Check wishlist contains first variant\",\n trigger: 'iframe #o_comparelist_table tr:contains(\"red\")',\n run: function () {\n window.location.href = '/@/shop?search=Rock';\n }\n },\n {\n content: \"Go to Rock shop page\",\n trigger: 'iframe a:contains(\"Rock\"):first',\n },\n {\n content: \"Switch to black Rock\",\n trigger: 'iframe .js_product span:contains(\"black\")',\n },\n {\n content: \"Add black rock to wishlist\",\n trigger: 'iframe #product_detail .o_add_wishlist_dyn:not(\".disabled\")',\n },\n {\n content: \"Check that black product was added\",\n trigger: 'iframe .my_wish_quantity:contains(2)',\n run: function () {\n window.location.href = '/@/shop/wishlist';\n }\n },\n {\n content: \"Check wishlist contains both variants\",\n extra_trigger: 'iframe #o_comparelist_table tr:contains(\"red\")',\n trigger: 'iframe #o_comparelist_table tr:contains(\"black\")',\n run: function () {}, // This is a check\n },\n ]\n);\n\n});\n", "/** @odoo-module **/\n\nimport tour from 'web_tour.tour';\n\ntour.register('stock_notification_wishlist', {\n test: true,\n url: '/shop/wishlist',\n },\n [\n {\n content: \"Click on 'Be notified when back in stock'\",\n trigger: '#wishlist_stock_notification_message',\n },\n {\n content: \"Fill email form\",\n trigger: 'div[id=\"stock_notification_form\"] input[name=\"email\"]',\n run: 'text test@test.test',\n },\n {\n content: \"Click on the button\",\n trigger: '#wishlist_stock_notification_form_submit_button',\n },\n {\n content: \"Success Message\",\n trigger: '#stock_notification_success_message',\n },\n ],\n);\n", "odoo.define('website_forum.tour_forum_question', function (require) {\n 'use strict';\n\n var tour = require(\"web_tour.tour\");\n\n tour.register('forum_question', {\n test: true,\n url: '/forum/help-1',\n }, [{\n content: \"Ask the question in this forum by clicking on the button.\",\n trigger: '.o_forum_ask_btn',\n }, {\n content: \"Give your question content.\",\n trigger: 'input[name=post_name]',\n run: 'text First Question Title',\n }, {\n content: \"Put your question here.\",\n extra_trigger: \"#wrap:not(:has(input[name=post_name]:propValue('')))\",\n trigger: '.note-editable p',\n run: 'text First Question

code here

',\n }, {\n content: \"Insert tags related to your question.\",\n extra_trigger: '.note-editable:not(:has(br))',\n trigger: '.select2-choices',\n run: 'text Tag',\n }, {\n content: \"Click to post your question.\",\n extra_trigger: \"#wrap:not(:has(input[id=s2id_autogen2]:propValue('')))\",\n trigger: 'button:contains(\"Post\")',\n }, {\n content: \"This page contain new created question.\",\n trigger: '#wrap:has(\".fa-star\")',\n run: function() {}, //it's a check that page has been reloaded,\n }, {\n content: \"Close modal once modal animation is done.\",\n extra_trigger: 'div.modal.modal_shown',\n trigger: \".modal-header button.btn-close\",\n }, {\n content: \"Check that the code still exists as it was written.\",\n trigger: 'div.o_wforum_post_content:contains(\"First Question

code here

\")',\n }, {\n content: \"Open dropdown to edit the post\",\n trigger: 'div.dropdown a#dropdownMenuLink',\n }, {\n content: \"Click on edit\",\n trigger: 'form button:contains(\"Edit\")',\n }, {\n content: \"Check that the content is the same\",\n trigger: 'div.odoo-editor-editable p:contains(\"First Question

code here

\")',\n run: function () {}, //it's a check\n }, {\n content: \"Save changes\",\n trigger: 'button:contains(\"Save Changes\")',\n }, {\n trigger: \"a:contains(\\\"Answer\\\").collapsed\",\n content: \"Click to answer.\",\n position: \"bottom\",\n },\n {\n content: \"Put your answer here.\",\n trigger: '.note-editable p',\n run: 'text First Answer',\n }, {\n content: \"Click to post your answer.\",\n extra_trigger: '.note-editable:not(:has(br))',\n trigger: 'button:contains(\"Post Answer\")',\n }, {\n content: \"Close modal once modal animation is done.\",\n extra_trigger: 'div.modal.modal_shown',\n trigger: \".modal-header button.btn-close\",\n }, {\n content: \"Click here to accept this answer.\",\n extra_trigger: '#wrap:has(\".o_wforum_validate_toggler\")',\n trigger: '.o_wforum_validate_toggler[data-karma]:first',\n }, {\n content: \"Congratulations! You just created and post your first question and answer.\",\n trigger: '#wrap:has(\".o_wforum_answer_correct\")',\n }]);\n});\n"], "file": "/web/assets/3551797-c5a2fa2/web.assets_tests.js", "sourceRoot": "../../../"}