)]}'
{"version": 3, "sources": ["/base/static/tests/test_ir_model_fields_translation.js", "/web/static/tests/legacy/helpers/cleanup.js", "/web/static/tests/legacy/helpers/utils.js", "/web/static/tests/legacy/utils.js", "/web/static/tests/tours/user_switch_tour.js", "/mail/static/tests/tours/activity_date_format_tour.js", "/mail/static/tests/tours/discuss_channel_as_guest_tour.js", "/mail/static/tests/tours/discuss_channel_call_public_tour.js", "/mail/static/tests/tours/discuss_channel_public_tour.js", "/mail/static/tests/tours/discuss_configuration_tour.js", "/mail/static/tests/tours/discuss_go_back_to_thread_from_breadcrumbs_tour.js", "/mail/static/tests/tours/discuss_sidebar_in_public_page_tour.js", "/mail/static/tests/tours/discuss_sub_channel_search_tour.js", "/mail/static/tests/tours/mail_activity_schedule_from_chatter.js", "/mail/static/tests/tours/mail_composer_test_tour.js", "/mail/static/tests/tours/mail_message_load_order_tour.js", "/mail/static/tests/tours/mail_template_dynamic_placeholder_tour.js", "/mail/static/tests/tours/user_modify_own_profile_tour.js", "/portal/static/tests/tours/portal.js", "/portal/static/tests/tours/skip_to_content.js", "/account/static/tests/tours/tax_group_tests.js", "/account/static/tests/tours/tour_tests_shared_js_python.js", "/sale/static/tests/tours/product_attribute_value_tour.js", "/sale/static/tests/tours/sale_combo_configurator.js", "/sale/static/tests/tours/sale_signature.js", "/sale/static/src/js/tours/combo_configurator_tour_utils.js", "/sale/static/src/js/tours/product_configurator_tour_utils.js", "/sale/static/src/js/tours/tour_utils.js", "/stock/static/tests/tours/stock_picking_tour.js", "/stock/static/tests/tours/stock_report_tests.js", "/web_enterprise/static/tests/tours/web_enterprise_tours.js", "/web_studio/static/tests/tours/tour_helpers.js", "/web_studio/static/tests/tours/web_studio.js", "/web_studio/static/tests/tours/web_studio_main_tours.js", "/web_studio/static/tests/tours/web_studio_test_ui_unit_report_tours.js", "/web_studio/static/tests/tours/web_studio_test_ui_unit_tours.js", "/stock_barcode/static/tests/tours/running_tour_action_helper.js", "/stock_barcode/static/tests/tours/tour_helper_stock_barcode.js", "/stock_barcode/static/tests/tours/tour_step_utils.js", "/stock_barcode/static/tests/tours/tour_test_barcode_flows_inventory.js", "/stock_barcode/static/tests/tours/tour_test_barcode_flows_picking.js", "/stock_barcode/static/tests/tours/tour_test_barcode_gs1.js", "/mail_enterprise/static/tests/tours/discuss_channel_expand_test_tour.js", "/account_accountant/static/tests/tours/test_tour_bank_rec_rainbowman_reset.js", "/account_accountant/static/tests/tours/test_tour_bank_rec_statements.js", "/account_accountant/static/tests/tours/test_tour_bank_rec_ui.js", "/account_accountant/static/tests/tours/test_tour_bank_rec_widget.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_pdf_quote_builder/static/tests/tours/custom_content_kanban_like_tests.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;;;;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;;;;;;;;AClHA;;;;;;;;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;AC9mCA;;;;;;;;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;;;;;;;;AC7qBA;;;;;;;;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;;;;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;;;;ACxBA;;;;;;;;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChNA;;;;;;;;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;;;;AClFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACXA;;;;;;;;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;;;;ACxCA;;;;;;;;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;;;;AC7FA;;;;;;;;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;;;;AC/SA;;;;;;;;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtKA;;;;;;;;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;;;;ACjCA;;;;;;;;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;;;;AClBA;;;;;;;;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;;;;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;;;;ACpDA;;;;;;;;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;;;;AC7KA;;;;;;;;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;;;;AC9OA;;;;;;;;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxvBA;;;;;;;;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;;;;ACjBA;;;;;;;;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;;;;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnxEA;;;;;;;;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACp9CA;;;;;;;;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC70DA;;;;;;;;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChVA;;;;;;;;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7mDA;;;;;;;;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjvKA;;;;;;;;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACp6CA;;;;;;;;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;;;;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;;;;AC/FA;;;;;;;;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;;;;ACj8BA;;;;;;;;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;;;;AC5OA;;;;;;;;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;;;;AC7WA;;;;;;;;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;;;;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", "sourcesContent": ["/** @odoo-module **/\n\nimport { registry } from \"@web/core/registry\";\nimport { stepUtils } from \"@web_tour/tour_service/tour_utils\";\nfunction checkLoginColumn(translation) {\n    return [\n        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\nregistry.category(\"web_tour.tours\").add('ir_model_fields_translation_en_tour', {\n    url: '/odoo',\n    steps: () => checkLoginColumn('Login')\n});\n\nregistry.category(\"web_tour.tours\").add('ir_model_fields_translation_en_tour2', {\n    url: '/odoo',\n    steps: () => checkLoginColumn('Login2')\n});\n\nregistry.category(\"web_tour.tours\").add('ir_model_fields_translation_fr_tour', {\n    url: '/odoo',\n    steps: () => checkLoginColumn('Identifiant')\n});\n\nregistry.category(\"web_tour.tours\").add('ir_model_fields_translation_fr_tour2', {\n    url: '/odoo',\n    steps: () => checkLoginColumn('Identifiant2')\n});\n", "/** @odoo-module alias=@web/../tests/helpers/cleanup default=false */\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        { tagName: \"DIV\", attr: \"class\", value: \"tooltip fade bs-tooltip-auto show\" },\n        { tagName: \"DIV\", attr: \"class\", value: \"tooltip tooltip-field-info fade bs-tooltip-auto\" },\n        {\n            tagName: \"DIV\",\n            attr: \"class\",\n            value: \"tooltip tooltip-field-info fade bs-tooltip-auto show\",\n        },\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 alias=@web/../tests/helpers/utils default=false */\n\nimport { getTemplate } from \"@web/core/templates\";\nimport { browser } from \"@web/core/browser/browser\";\nimport { isMacOS } from \"@web/core/browser/feature_detection\";\nimport { download } from \"@web/core/network/download\";\nimport { getPopoverForTarget } from \"@web/core/popover/popover\";\nimport { Deferred } from \"@web/core/utils/concurrency\";\nimport { patch } from \"@web/core/utils/patch\";\nimport { isVisible } from \"@web/core/utils/ui\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { registerCleanup } from \"./cleanup\";\n\nimport {\n    App,\n    onError,\n    onMounted,\n    onPatched,\n    onRendered,\n    onWillDestroy,\n    onWillPatch,\n    onWillRender,\n    onWillStart,\n    onWillUnmount,\n    onWillUpdateProps,\n    useComponent,\n} from \"@odoo/owl\";\n\n/**\n * @typedef {keyof HTMLElementEventMap | keyof WindowEventMap} EventType\n *\n * @typedef {Side | `${Side}-${Side}` | { x?: number, y?: number }} Position\n *\n * @typedef {\"bottom\" | \"left\" | \"right\" | \"top\"} Side\n *\n * @typedef TriggerEventOptions\n * @property {boolean} [skipVisibilityCheck=false]\n * @property {boolean} [sync=false]\n */\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 * @param {number} [ms=0]\n */\nexport function patchDate(year, month, day, hours, minutes, seconds, ms = 0) {\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, ms);\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    patchWithCleanup(luxon.Settings, { defaultZone: luxon.FixedOffsetZone.instance(offset) });\n}\n\n/**\n *\n * @param {Object} obj object to patch\n * @param {Object} patchValue the actual patch description\n */\nexport function patchWithCleanup(obj, patchValue) {\n    const unpatch = patch(obj, patchValue);\n    registerCleanup(() => {\n        unpatch();\n    });\n}\n\n/**\n * @returns {Element}\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.getElementById(\"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\n//-----------------------------------------------------------------------------\n// Event init attributes mappers\n//-----------------------------------------------------------------------------\n\n/** @param {EventInit} [args] */\nconst mapBubblingEvent = (args) => ({ ...args, bubbles: true });\n\n/** @param {EventInit} [args] */\nconst mapNonBubblingEvent = (args) => ({ ...args, bubbles: false });\n\n/** @param {EventInit} [args={}] */\nconst mapBubblingPointerEvent = (args = {}) => ({\n    clientX: args.pageX,\n    clientY: args.pageY,\n    ...args,\n    bubbles: true,\n    cancelable: true,\n    view: window,\n});\n\n/** @param {EventInit} [args] */\nconst mapNonBubblingPointerEvent = (args) => ({\n    ...mapBubblingPointerEvent(args),\n    bubbles: false,\n    cancelable: false,\n});\n\n/** @param {EventInit} [args={}] */\nconst mapCancelableTouchEvent = (args = {}) => ({\n    ...args,\n    bubbles: true,\n    cancelable: true,\n    composed: true,\n    rotation: 0.0,\n    touches: args.touches ? [...args.touches.map((e) => new Touch(e))] : undefined,\n    view: window,\n    zoom: 1.0,\n});\n\n/** @param {EventInit} [args] */\nconst mapNonCancelableTouchEvent = (args) => ({\n    ...mapCancelableTouchEvent(args),\n    cancelable: false,\n});\n\n/** @param {EventInit} [args] */\nconst mapKeyboardEvent = (args) => ({\n    ...args,\n    bubbles: true,\n    cancelable: true,\n});\n\n/**\n * @template {typeof Event} T\n * @param {EventType} eventType\n * @returns {[T, (attrs: EventInit) => EventInit]}\n */\nconst getEventConstructor = (eventType) => {\n    switch (eventType) {\n        // Mouse events\n        case \"auxclick\":\n        case \"click\":\n        case \"contextmenu\":\n        case \"dblclick\":\n        case \"mousedown\":\n        case \"mouseup\":\n        case \"mousemove\":\n        case \"mouseover\":\n        case \"mouseout\": {\n            return [MouseEvent, mapBubblingPointerEvent];\n        }\n        case \"mouseenter\":\n        case \"mouseleave\": {\n            return [MouseEvent, mapNonBubblingPointerEvent];\n        }\n        // Pointer events\n        case \"pointerdown\":\n        case \"pointerup\":\n        case \"pointermove\":\n        case \"pointerover\":\n        case \"pointerout\": {\n            return [PointerEvent, mapBubblingPointerEvent];\n        }\n        case \"pointerenter\":\n        case \"pointerleave\": {\n            return [PointerEvent, mapNonBubblingPointerEvent];\n        }\n        // Focus events\n        case \"focusin\": {\n            return [FocusEvent, mapBubblingEvent];\n        }\n        case \"focus\":\n        case \"blur\": {\n            return [FocusEvent, mapNonBubblingEvent];\n        }\n        // Clipboard events\n        case \"cut\":\n        case \"copy\":\n        case \"paste\": {\n            return [ClipboardEvent, mapBubblingEvent];\n        }\n        // Keyboard events\n        case \"keydown\":\n        case \"keypress\":\n        case \"keyup\": {\n            return [KeyboardEvent, mapKeyboardEvent];\n        }\n        // Drag events\n        case \"drag\":\n        case \"dragend\":\n        case \"dragenter\":\n        case \"dragstart\":\n        case \"dragleave\":\n        case \"dragover\":\n        case \"drop\": {\n            return [DragEvent, mapBubblingEvent];\n        }\n        // Input events\n        case \"input\": {\n            return [InputEvent, mapBubblingEvent];\n        }\n        // Composition events\n        case \"compositionstart\":\n        case \"compositionend\": {\n            return [CompositionEvent, mapBubblingEvent];\n        }\n        // UI events\n        case \"scroll\": {\n            return [UIEvent, mapNonBubblingEvent];\n        }\n        // Touch events\n        case \"touchstart\":\n        case \"touchend\":\n        case \"touchmove\": {\n            return [TouchEvent, mapCancelableTouchEvent];\n        }\n        case \"touchcancel\": {\n            return [TouchEvent, mapNonCancelableTouchEvent];\n        }\n        // Default: base Event constructor\n        default: {\n            return [Event, mapBubblingEvent];\n        }\n    }\n};\n\n/**\n * @template {EventType} T\n * @param {Element} el\n * @param {string | null | undefined | false} selector\n * @param {T} eventType\n * @param {EventInit} [eventInit]\n * @param {TriggerEventOptions} [options={}]\n * @returns {GlobalEventHandlersEventMap[T] | Promise<GlobalEventHandlersEventMap[T]>}\n */\nexport function triggerEvent(el, selector, eventType, eventInit, options = {}) {\n    const errors = [];\n    const target = findElement(el, selector);\n\n    // Error handling\n    if (typeof eventType !== \"string\") {\n        errors.push(\"event type must be a string\");\n    }\n    if (!target) {\n        errors.push(\"cannot find target\");\n    } else if (!options.skipVisibilityCheck && !isVisible(target)) {\n        errors.push(\"target is not visible\");\n    }\n    if (errors.length) {\n        throw new Error(\n            `Cannot trigger event${eventType ? ` \"${eventType}\"` : \"\"}${\n                selector ? ` (with selector \"${selector}\")` : \"\"\n            }: ${errors.join(\" and \")}`\n        );\n    }\n\n    // Actual dispatch\n    const [Constructor, processParams] = getEventConstructor(eventType);\n    const event = new Constructor(eventType, processParams(eventInit));\n    target.dispatchEvent(event);\n\n    if (window.QUnit && QUnit.config.debug) {\n        const group = `%c[${event.type.toUpperCase()}]`;\n        console.groupCollapsed(group, \"color: #b52c9b\");\n        console.log(target, event);\n        console.groupEnd(group, \"color: #b52c9b\");\n    }\n\n    if (options.sync) {\n        return event;\n    } else {\n        return nextTick().then(() => event);\n    }\n}\n\n/**\n * @param {Element} el\n * @param {string | null | undefined | false} selector\n * @param {(EventType | [EventType, EventInit])[]} [eventDefs]\n * @param {TriggerEventOptions} [options={}]\n */\nexport function triggerEvents(el, selector, eventDefs, options = {}) {\n    const events = [...eventDefs].map((eventDef) => {\n        const [eventType, eventInit] = Array.isArray(eventDef) ? eventDef : [eventDef, {}];\n        return triggerEvent(el, selector, eventType, eventInit, options);\n    });\n    if (options.sync) {\n        return events;\n    } else {\n        return nextTick().then(() => events);\n    }\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 {Element} 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        await triggerEvent(target, null, \"scroll\");\n        if (!canPropagate || !Object.entries(coordinates).length) {\n            return;\n        }\n    }\n    target.parentElement\n        ? triggerScroll(target.parentElement, coordinates)\n        : triggerEvent(window, null, \"scroll\");\n    await nextTick();\n}\n\nexport function click(\n    el,\n    selector,\n    { mouseEventInit = {}, skipDisabledCheck = false, skipVisibilityCheck = false } = {}\n) {\n    if (!skipDisabledCheck && el.disabled) {\n        throw new Error(\"Can't click on a disabled button\");\n    }\n    return triggerEvents(\n        el,\n        selector,\n        [\n            \"pointerdown\",\n            \"mousedown\",\n            \"focus\",\n            \"focusin\",\n            \"pointerup\",\n            \"mouseup\",\n            [\"click\", mouseEventInit],\n        ],\n        { skipVisibilityCheck }\n    );\n}\n\nexport function clickCreate(htmlElement) {\n    if (\n        htmlElement.querySelectorAll(\n            \".o_control_panel_main_buttons .o_form_button_create\"\n        ).length\n    ) {\n        return click(\n            htmlElement,\n            \".o_control_panel_main_buttons .o_form_button_create\"\n        );\n    } else if (\n        htmlElement.querySelectorAll(\n            \".o_control_panel_main_buttons .o_list_button_create\"\n        ).length\n    ) {\n        return click(\n            htmlElement,\n            \".o_control_panel_main_buttons .o_list_button_create\"\n        );\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    }\n    const listSaveButtons = htmlElement.querySelectorAll(\".o_list_button_save\");\n    if (listSaveButtons.length) {\n        return listSaveButtons.length >= 2 ? click(listSaveButtons[1]) : click(listSaveButtons[0]);\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).find(\".o_list_button_discard:visible\").length) {\n        return click($(htmlElement).find(\".o_list_button_discard:visible\").get(0));\n    } else {\n        throw new Error(\"No discard button found to be clicked.\");\n    }\n}\n\n/**\n * Trigger pointerenter and mouseenter events 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 {Element} 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 triggerEvents(target, null, [\"pointerenter\", \"mouseenter\"], atPos);\n}\n\n/**\n * Trigger pointerleave and mouseleave events on the given target.\n *\n * @param {Element} el\n * @param {string} selector\n */\nexport async function mouseLeave(el, selector) {\n    const target = el.querySelector(selector) || el;\n    return triggerEvents(target, null, [\"pointerleave\", \"mouseleave\"]);\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\nexport async function editSelectMenu(el, selector, value) {\n    const dropdown = el.querySelector(selector);\n    await click(dropdown.querySelector(\".dropdown-toggle\"));\n    for (const item of Array.from(el.querySelectorAll(\".o_select_menu_menu .dropdown-item\"))) {\n        if (item.textContent === value) {\n            return click(item);\n        }\n    }\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 */\nexport async 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, keyupEvent] = await triggerEvents(\n        document.activeElement,\n        null,\n        [\n            [\"keydown\", eventAttrs],\n            [\"keyup\", eventAttrs],\n        ],\n        { skipVisibilityCheck: true }\n    );\n\n    return { keydownEvent, keyupEvent };\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 currentTime = 0;\n    let id = 1;\n    patchWithCleanup(browser, {\n        requestAnimationFrame(fn) {\n            callbacks.set(id, { fn, scheduledFor: 16 + currentTime, id });\n            return id++;\n        },\n        cancelAnimationFrame(id) {\n            callbacks.delete(id);\n        },\n        performance: { now: () => currentTime },\n    });\n    return {\n        execRegisteredAnimationFrames() {\n            for (const { fn } of callbacks.values()) {\n                fn(currentTime);\n            }\n            callbacks.clear();\n        },\n        async advanceFrame(count = 1) {\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 += 16 * count;\n            for (const { fn, scheduledFor, id } of callbacks.values()) {\n                if (scheduledFor <= currentTime) {\n                    fn(currentTime);\n                    callbacks.delete(id);\n                }\n            }\n            // wait here to make sure owl can update the UI\n            await nextTick();\n        },\n    };\n}\n\nexport async function mount(Comp, target, config = {}) {\n    let { props, env } = config;\n    env = env || {};\n    const configuration = {\n        env,\n        getTemplate,\n        test: true,\n        props,\n    };\n    if (env.services && \"localization\" in env.services) {\n        configuration.translateFn = _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\nexport function useLogLifeCycle(logFn, name = \"\") {\n    const component = useComponent();\n    let loggedName = `${component.constructor.name}`;\n    if (name) {\n        loggedName = `${component.constructor.name} ${name}`;\n    }\n    onError(() => {\n        logFn(`onError ${loggedName}`);\n    });\n    onMounted(() => {\n        logFn(`onMounted ${loggedName}`);\n    });\n    onPatched(() => {\n        logFn(`onPatched ${loggedName}`);\n    });\n    onRendered(() => {\n        logFn(`onRendered ${loggedName}`);\n    });\n    onWillDestroy(() => {\n        logFn(`onWillDestroy ${loggedName}`);\n    });\n    onWillPatch(() => {\n        logFn(`onWillPatch ${loggedName}`);\n    });\n    onWillRender(() => {\n        logFn(`onWillRender ${loggedName}`);\n    });\n    onWillStart(() => {\n        logFn(`onWillStart ${loggedName}`);\n    });\n    onWillUnmount(() => {\n        logFn(`onWillUnmount ${loggedName}`);\n    });\n    onWillUpdateProps(() => {\n        logFn(`onWillUpdateProps ${loggedName}`);\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 * - 'from' is used to determine the element on which the drag will start;\n * - 'target' will determine the element on which the first one will be 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 {Position} [position]\n */\nexport async function dragAndDrop(from, to, position) {\n    const { drop } = await drag(from);\n    await drop(to, position);\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 'target' selector will determine the element on which the dragged element will be\n * moved.\n *\n * Returns a drop function\n *\n * @param {Element | string} from\n */\nexport async function drag(from, pointerType = \"mouse\") {\n    const assertIsDragging = (fn, endDrag) => {\n        return {\n            async [fn.name](...args) {\n                if (dragEndReason) {\n                    throw new Error(\n                        `Cannot execute drag helper '${fn.name}': drag sequence has been ended by '${dragEndReason}'.`\n                    );\n                }\n                await fn(...args);\n                if (endDrag) {\n                    dragEndReason = fn.name;\n                }\n            },\n        }[fn.name];\n    };\n\n    const cancel = assertIsDragging(async function cancel() {\n        await triggerEvent(window, null, \"keydown\", { key: \"Escape\" });\n    }, true);\n\n    /**\n     * @param {Element | string} [to]\n     * @param {Position} [position]\n     */\n    const drop = assertIsDragging(async function drop(to, position) {\n        if (to) {\n            await moveTo(to, position);\n        }\n        await triggerEvent(target || source, null, \"pointerup\", targetPosition);\n    }, true);\n\n    /**\n     * @param {Element | string} selector\n     */\n    const getEl = (selector) =>\n        selector instanceof Element ? selector : fixture.querySelector(selector);\n\n    /**\n     * @param {Position} [position]\n     */\n    const getTargetPosition = (position) => {\n        const tRect = target.getBoundingClientRect();\n        const tPos = {\n            clientX: Math.floor(tRect.x),\n            clientY: Math.floor(tRect.y),\n        };\n        if (position && typeof position === \"object\") {\n            // x and y coordinates start from the element's initial coordinates\n            tPos.clientX += position.x || 0;\n            tPos.clientY += position.y || 0;\n        } else {\n            const positions = typeof position === \"string\" ? position.split(\"-\") : [];\n\n            // X position\n            if (positions.includes(\"left\")) {\n                tPos.clientX -= 1;\n            } else if (positions.includes(\"right\")) {\n                tPos.clientX += Math.ceil(tRect.width) + 1;\n            } else {\n                tPos.clientX += Math.floor(tRect.width / 2);\n            }\n\n            // Y position\n            if (positions.includes(\"top\")) {\n                tPos.clientY -= 1;\n            } else if (positions.includes(\"bottom\")) {\n                tPos.clientY += Math.ceil(tRect.height) + 1;\n            } else {\n                tPos.clientY += Math.floor(tRect.height / 2);\n            }\n        }\n        return tPos;\n    };\n\n    /**\n     * @param {Element | string} [to]\n     * @param {Position} [position]\n     */\n    const moveTo = assertIsDragging(async function moveTo(to, position) {\n        target = getEl(to);\n        if (!target) {\n            return;\n        }\n\n        // Recompute target position\n        targetPosition = getTargetPosition(position);\n\n        // Move, enter and drop the element on the target\n        await triggerEvent(source, null, \"pointermove\", { ...targetPosition, button: -1 });\n\n        // \"pointerenter\" is fired on every parent of `target` that do not contain\n        // `from` (typically: different parent lists).\n        for (const parent of getDifferentParents(source, target)) {\n            triggerEvent(parent, null, \"pointerenter\", targetPosition);\n        }\n        await nextTick();\n\n        return dragHelpers;\n    }, false);\n\n    const dragHelpers = { cancel, drop, moveTo };\n    const fixture = getFixture();\n\n    const source = getEl(from instanceof Element ? from : fixture.querySelector(from));\n    const sourceRect = source.getBoundingClientRect();\n\n    let dragEndReason = null;\n    let target;\n    let targetPosition;\n\n    // Pointer down on main target\n    await triggerEvent(source, null, \"pointerdown\", {\n        pointerType,\n        clientX: sourceRect.x + sourceRect.width / 2,\n        clientY: sourceRect.y + sourceRect.height / 2,\n    });\n\n    return dragHelpers;\n}\n\n/**\n * Returns the dropdown menu for a specific toggler.\n *\n * @param {HTMLElement} target\n * @param {String|HTMLElement} togglerSelector\n * @returns {HTMLElement|undefined}\n */\nexport function getDropdownMenu(target, togglerSelector) {\n    if (!(target instanceof HTMLElement)) {\n        throw new Error(`getDropdownMenu: target is not an HTMLElement.`);\n    }\n    let el =\n        togglerSelector instanceof HTMLElement\n            ? togglerSelector\n            : target.querySelector(togglerSelector);\n\n    if (el && !el.classList.contains(\"o-dropdown\")) {\n        el = el.querySelector(\".o-dropdown\");\n    }\n    if (!el) {\n        throw new Error(`getDropdownMenu: Could not find element \"${togglerSelector}\".`);\n    }\n    return getPopoverForTarget(el);\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]);\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 alias=@web/../tests/utils default=false */\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 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 {boolean} [shadowRoot] if provided, targets the shadowRoot of the found elements.\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 ??= 1;\n        this.options.targetParam = this.options.target;\n        this.options.target ??= getFixture();\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(this.options.target, {\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?.length === this.options.count || crashOnFail) {\n            // clean before doing anything else to avoid infinite loop due to side effects\n            this.observer?.disconnect();\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?.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                this.def?.reject(new Error(message));\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            el.dispatchEvent(new window.InputEvent(\"change\"));\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        this.def?.resolve();\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            .map((el) => (this.options.shadowRoot ? el.shadowRoot : el))\n            .filter((el) => el);\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], { ...param[1], target: el });\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                })?.[0];\n                if (\n                    !afterEl ||\n                    !(el.compareDocumentPosition(afterEl) & 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                })?.[0];\n                if (\n                    !beforeEl ||\n                    !(el.compareDocumentPosition(beforeEl) & 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            return this.parentContains.runOnce(`as parent`, { executeOnSuccess: false })?.[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\nconst stepState = {\n    expectedSteps: null,\n    deferred: null,\n    timeout: null,\n    currentSteps: [],\n\n    clear() {\n        clearTimeout(this.timeout);\n        this.timeout = null;\n        this.deferred = null;\n        this.currentSteps = [];\n        this.expectedSteps = null;\n    },\n\n    check({ crashOnFail = false } = {}) {\n        const success =\n            this.expectedSteps.length === this.currentSteps.length &&\n            this.expectedSteps.every((s, i) => s === this.currentSteps[i]);\n        if (!success && !crashOnFail) {\n            return;\n        }\n        QUnit.config.current.assert.verifySteps(this.expectedSteps);\n        if (success) {\n            this.deferred.resolve();\n        } else {\n            this.deferred.reject(new Error(\"Steps do not match.\"));\n        }\n        this.clear();\n    },\n};\n\nif (window.QUnit) {\n    QUnit.testStart(() =>\n        registerCleanup(() => {\n            if (stepState.expectedSteps) {\n                stepState.check({ crashOnFail: true });\n            } else {\n                stepState.clear();\n            }\n        })\n    );\n}\n\n/**\n * Indicate the completion of a test step. This step must then be verified by\n * calling `assertSteps`.\n *\n * @param {string} step\n */\nexport function step(step) {\n    stepState.currentSteps.push(step);\n    QUnit.config.current.assert.step(step);\n    if (stepState.expectedSteps) {\n        stepState.check();\n    }\n}\n\n/**\n * Wait for the given steps to be executed or for the timeout to be reached.\n *\n * @param {string[]} steps\n */\nexport function assertSteps(steps) {\n    if (stepState.expectedSteps) {\n        stepState.check({ crashOnFail: true });\n    }\n    stepState.expectedSteps = steps;\n    stepState.deferred = makeDeferred();\n    stepState.timeout = setTimeout(() => stepState.check({ crashOnFail: true }), 2000);\n    stepState.check();\n    return stepState.deferred;\n}\n", "import { registry } from \"@web/core/registry\";\n\nfunction logout() {\n    return [\n        {\n            content: \"check we're logged in\",\n            trigger: \".o_user_menu .dropdown-toggle\",\n            run: \"click\",\n        },\n        {\n            content: \"click the Log out button\",\n            trigger: \".dropdown-item[data-menu=logout]\",\n            run: \"click\",\n        },\n    ];\n}\n\nregistry.category(\"web_tour.tours\").add(\"test_user_switch\", {\n    url: \"/odoo\",\n    steps: () => [\n        ...logout(),\n        {\n            content: \"check if the login input is empty\",\n            trigger: \"input#login:empty\",\n        },\n        {\n            content: \"check if the password input is empty\",\n            trigger: \"input#password:empty\",\n        },\n        {\n            content: \"Should contains the user switch button\",\n            trigger: \".oe_login_form .o_user_switch_btn\",\n            run: \"click\",\n        },\n        {\n            content: \"Click on Marc Demo on the quick login page\",\n            trigger:\n                \".o_user_switch:not(:has(.list-group-item:nth-child(2))) .list-group-item:contains('Marc Demo')\",\n            run: \"click\",\n        },\n        {\n            content: \"Check user choice button to back on the quick login page\",\n            trigger: \".oe_login_form .o_user_switch_btn\",\n            run: \"click\",\n        },\n        {\n            content: \"Display the login form\",\n            trigger: \".o_user_switch .fa-user-circle-o\",\n            run: \"click\",\n        },\n        {\n            content: \"fill the login\",\n            trigger: \"input#login\",\n            run: \"edit admin\",\n        },\n        {\n            content: \"fill the password\",\n            trigger: \"input#password\",\n            run: \"edit admin\",\n        },\n        {\n            content: \"click on login button\",\n            trigger: 'button:contains(\"Log in\")',\n            run: \"click\",\n        },\n        ...logout(),\n        {\n            content: \"Check if there is Mitchell Admin in user list selection\",\n            trigger: \".o_user_switch .list-group-item:nth-child(1):contains('Mitchell Admin')\",\n        },\n        {\n            content: \"Check if there is Marc Demo in user list selection\",\n            trigger: \".o_user_switch .list-group-item:nth-child(2):contains('Marc Demo')\",\n        },\n        {\n            content: \"Choice demo\",\n            trigger: \".o_user_switch .list-group-item:contains('Marc Demo')\",\n            run: \"click\",\n        },\n        {\n            content: \"check the login for demo\",\n            trigger: \"input#login:value('demo')\",\n        },\n        {\n            content: \"fill the password\",\n            trigger: \"input#password\",\n            run: \"edit demo\",\n        },\n        {\n            content: \"Check back button to back on the quick login page\",\n            trigger: \".oe_login_form .o_user_switch_btn\",\n            run: \"click\",\n        },\n        {\n            content: \"Check have 2 users\",\n            trigger: \".o_user_switch .list-group-item:nth-child(2)\",\n        },\n        {\n            content: \"Click on Mitchell Admin\",\n            trigger: \".o_user_switch .list-group-item:nth-child(1):contains('Mitchell Admin')\",\n            run: \"click\",\n        },\n        {\n            content: \"check the login for admin\",\n            trigger: \"input#login:value('admin')\",\n        },\n        {\n            content: \"fill the password\",\n            trigger: \"input#password\",\n            run: \"edit admin\",\n        },\n        {\n            content: \"Check back button to back on the quick login page\",\n            trigger: \".oe_login_form .o_user_switch_btn\",\n            run: \"click\",\n        },\n        {\n            content: \"Display the login form\",\n            trigger: \".o_user_switch .fa-user-circle-o\",\n            run: \"click\",\n        },\n        {\n            content: \"the login form is display\",\n            trigger: \"form.oe_login_form:not(.d-none)\",\n        },\n        {\n            content: \"check if the login input is empty\",\n            trigger: \"input#login:empty\",\n        },\n        {\n            content: \"check if the password input is empty\",\n            trigger: \"input#password:empty\",\n        },\n        {\n            content: \"Back to user switch\",\n            trigger: \".oe_login_form .o_user_switch_btn\",\n            run: \"click\",\n        },\n        {\n            content: \"Remove the admin user from page\",\n            trigger: \".o_user_switch .d-flex:first-child .fa-times\",\n            run: \"click\",\n        },\n        {\n            content: \"only one user is left on quick login\",\n            trigger:\n                \".o_user_switch:not(:has(.list-group-item:nth-child(2))) .list-group-item:contains('Marc Demo')\",\n        },\n    ],\n});\n", "import { registry } from \"@web/core/registry\";\n\nregistry.category(\"web_tour.tours\").add(\"mail_activity_date_format\", {\n    steps: () => [\n        {\n            trigger: \"button:contains('Activities')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"input[id*='activity_type_id']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".dropdown-item:contains('To-Do')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"div[name='summary'] input\",\n            run: \"edit Go Party\",\n        },\n        {\n            trigger: \"button:contains('Schedule')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-mail-Activity:contains('Go Party')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-mail-Activity-info i\",\n            run: \"click\",\n        },\n        // Format expected from the server for 9 AM at the first day of 2024 is date_format = \"%d/%b/%y\", time_format = \"%I:%M:%S %p\".\n        {\n            trigger:\n                \".o-mail-Activity-details tr:contains('Created') td:contains('01/Jan/24 09:00:00 AM')\",\n        },\n        {\n            // Default due date is 5 days after creation date.\n            trigger: \".o-mail-Activity-details tr:contains('Due on') td:contains('06/Jan/24')\",\n        },\n    ],\n});\n", "import { registry } from \"@web/core/registry\";\n\nregistry.category(\"web_tour.tours\").add(\"discuss_channel_as_guest_tour.js\", {\n    steps: () => [\n        {\n            content: \"Channel secret token has been hidden on welcome page\",\n            trigger: \".o-mail-WelcomePage\",\n            run() {\n                if (!window.location.pathname.startsWith(\"/discuss/channel\")) {\n                    console.error(\"Channel secret token is still present in URL.\");\n                }\n            },\n        },\n        {\n            content: \"Click join\",\n            trigger: \"button[title='Join Channel']\",\n            run: \"click\",\n        },\n        {\n            content: \"Check that we are on channel page\",\n            trigger: \".o-mail-Thread\",\n        },\n    ],\n});\n", "import { registry } from \"@web/core/registry\";\n\nregistry.category(\"web_tour.tours\").add(\"discuss_channel_call_public_tour.js\", {\n    steps: () => [\n        {\n            content: \"The call does not start on the welcome page\",\n            trigger: \".o-mail-WelcomePage\",\n            async run() {\n                await new Promise((r) => setTimeout(r, 250));\n                const rtcService = odoo.__WOWL_DEBUG__.root.env.services[\"discuss.rtc\"];\n                if (rtcService?.selfSession || rtcService?.state.hasPendingRequest) {\n                    console.error(\"The call should not have started.\");\n                }\n            },\n        },\n        {\n            content: \"Click join\",\n            trigger: \"button[title='Join Channel']\",\n            run: \"click\",\n        },\n        {\n            content: \"Check that the call has started\",\n            trigger: \".o-discuss-Call\",\n        },\n        {\n            content: \"Check that current user is in call ('disconnect' button visible)\",\n            trigger: \"button[title='Disconnect']\",\n        },\n    ],\n});\n", "import { registry } from \"@web/core/registry\";\nimport { click, contains, inputFiles } from \"@web/../tests/utils\";\n\nregistry.category(\"web_tour.tours\").add(\"discuss_channel_public_tour.js\", {\n    steps: () => [\n        {\n            trigger: \".o-mail-Discuss\",\n        },\n        {\n            content: \"Check that we are on channel page\",\n            trigger: \".o-mail-Thread\",\n            run() {\n                if (!window.location.pathname.startsWith(\"/discuss/channel\")) {\n                    console.error(\"Channel secret token is still present in URL.\");\n                }\n                const errors = odoo.loader.findErrors();\n                if (Object.keys(errors).length) {\n                    console.error(\"Couldn't load all JS modules.\", errors);\n                }\n                document.body.classList.add(\"o_discuss_channel_public_modules_loaded\");\n                if (\n                    document.title !== document.querySelector(\".o-mail-Discuss-threadName\")?.value\n                ) {\n                    console.error(\"Tab title should match conversation name.\");\n                }\n            },\n        },\n        {\n            content: \"Wait for all modules loaded check in previous step\",\n            trigger: \".o_discuss_channel_public_modules_loaded\",\n        },\n        {\n            content: \"Write something in composer\",\n            trigger: \".o-mail-Composer-input\",\n            run: \"edit cheese\",\n        },\n        {\n            content: \"Add a text file in composer\",\n            trigger: \".o-mail-Composer button[aria-label='Attach files']\",\n            async run() {\n                const text = new File([\"hello, world\"], \"text.txt\", { type: \"text/plain\" });\n                await inputFiles(\".o-mail-Composer-coreMain .o_input_file\", [text]);\n            },\n        },\n        {\n            trigger: \".o-mail-AttachmentCard:not(.o-isUploading)\", // waiting the attachment to be uploaded\n        },\n        {\n            content: \"Check the text attachment is listed\",\n            trigger: '.o-mail-AttachmentCard[title=\"text.txt\"]',\n        },\n        {\n            content: \"Add an image file in composer\",\n            trigger: \".o-mail-Composer button[aria-label='Attach files']\",\n            async run() {\n                await inputFiles(\".o-mail-Composer-coreMain .o_input_file\", [\n                    new File(\n                        [\n                            await (\n                                await fetch(\n                                    \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2P4v5ThPwAG7wKklwQ/bwAAAABJRU5ErkJggg==\"\n                                )\n                            ).blob(),\n                        ],\n                        \"image.png\",\n                        { type: \"image/png\" }\n                    ),\n                ]);\n            },\n        },\n        {\n            trigger: \".o-mail-AttachmentImage:not(.o-isUploading)\",\n        },\n        {\n            content: \"Check the image attachment is listed\",\n            trigger: '.o-mail-AttachmentImage[title=\"image.png\"]',\n            async run() {\n                const store = odoo.__WOWL_DEBUG__.root.env.services[\"mail.store\"];\n                if (store.self.type === \"guest\") {\n                    const src = this.anchor.querySelector(\"img\").src;\n                    const token = store.Attachment.get(\n                        (src.match(\"/web/image/([0-9]+)\") || []).at(-1)\n                    )?.access_token;\n                    if (!(token && src.includes(`access_token=${token}`))) {\n                        throw new Error(\"Access token of the attachment isn't correct.\");\n                    }\n                }\n            },\n        },\n        {\n            content: \"Send message\",\n            trigger: \".o-mail-Composer-send:enabled\",\n            run: \"click\",\n        },\n        {\n            content: \"Check message is shown\",\n            trigger: '.o-mail-Message-body:contains(\"cheese\")',\n        },\n        {\n            content: \"Check message contains the attachment\",\n            trigger: '.o-mail-Message .o-mail-AttachmentCard:contains(\"text.txt\")',\n        },\n        {\n            trigger: \".o-mail-Message-textContent:contains(cheese)\",\n            run: \"hover && click .o-mail-Message [title='Add a Reaction']\",\n        },\n        {\n            trigger: \".o-EmojiPicker .o-Emoji:contains('\ud83d\ude42')\",\n            run: \"click\",\n        },\n        {\n            content: \"Reload page (fetch reactions)\",\n            trigger: \".o-mail-Message\",\n            run() {\n                location.reload();\n            },\n        },\n        {\n            content: \"Remove reaction\",\n            trigger: \".o-mail-MessageReaction:contains('\ud83d\ude42')\",\n            run: \"click\",\n        },\n        {\n            content: \"Reload page (fetch reactions)\",\n            trigger: \".o-mail-Message\",\n            run() {\n                location.reload();\n            },\n        },\n        {\n            trigger: \".o-mail-Message:not(:has(.o-mail-MessageReaction))\",\n        },\n        {\n            content: \"Click on more menu\",\n            trigger: \".o-mail-Message-textContent:contains(cheese)\",\n            run: \"hover && click .o-mail-Message [title='Expand']\",\n        },\n        {\n            content: \"Click on edit\",\n            trigger: \".o-mail-Message-moreMenu [title='Edit'], .o-mail-Message [title='Edit']\",\n            run: \"click\",\n        },\n        {\n            content: \"Edit message\",\n            trigger: \".o-mail-Message .o-mail-Composer-input\",\n            run: \"edit vegetables\",\n        },\n        {\n            content: \"Add one more file in composer\",\n            trigger: \".o-mail-Message button[aria-label='Attach files']\",\n            async run() {\n                const extratxt = new File([\"hello 2\"], \"extra.txt\", { type: \"text/plain\" });\n                await inputFiles(\".o-mail-Message .o_input_file\", [extratxt]);\n            },\n        },\n        {\n            trigger: \".o-mail-Message .o-mail-Composer .o-mail-AttachmentCard:not(.o-isUploading)\", // waiting the attachment to be uploaded\n        },\n        {\n            content: \"Check the earlier provided extra attachment is listed\",\n            trigger: '.o-mail-Message .o-mail-Composer .o-mail-AttachmentCard[title=\"extra.txt\"]',\n        },\n        {\n            content: \"Save edited message\",\n            trigger: \".o-mail-Message a:contains(save)\",\n            run: \"click\",\n        },\n        {\n            content: \"Check message is edited\",\n            trigger: '.o-mail-Message-body:contains(\"vegetables\")',\n        },\n        {\n            content: \"Check edited message contains the first attachment\",\n            trigger: '.o-mail-Message .o-mail-AttachmentCard:contains(\"text.txt\")',\n        },\n        {\n            content: \"Check edited message contains the extra attachment\",\n            trigger: '.o-mail-Message .o-mail-AttachmentCard:contains(\"extra.txt\")',\n            async run() {\n                await click(\".o-mail-AttachmentCard-unlink\", {\n                    parent: [\".o-mail-AttachmentCard\", { text: \"extra.txt\" }],\n                });\n                await click(\".btn\", { text: \"Ok\", parent: [\".modal\", { text: \"Confirmation\" }] });\n                await contains(\".o-mail-AttachmentCard\", { text: \"extra.txt\", count: 0 });\n            },\n        },\n        {\n            content: \"Open search panel\",\n            trigger: \"button[title='Search Messages']\",\n            run: \"click\",\n        },\n        {\n            content: \"Search for the attachment name\",\n            trigger: \".o_searchview_input\",\n            run: \"edit text.txt\",\n        },\n        {\n            content: \"Trigger the search\",\n            trigger: \"button[aria-label='Search button']\",\n            run: \"click\",\n        },\n        {\n            content: \"Check that searched message contains the attachment\",\n            trigger:\n                '.o-mail-SearchMessagesPanel .o-mail-Message .o-mail-AttachmentCard:contains(\"text.txt\")',\n        },\n    ],\n});\n", "import { registry } from \"@web/core/registry\";\nimport { stepUtils } from \"@web_tour/tour_service/tour_utils\";\n\nregistry.category(\"web_tour.tours\").add(\"discuss_configuration_tour\", {\n    url: \"/odoo\",\n    steps: () => [\n        stepUtils.showAppsMenuItem(),\n        {\n            trigger: '.o_app[data-menu-xmlid=\"mail.menu_root_discuss\"]',\n            run: \"click\",\n        },\n        {\n            trigger: \".o_main_navbar button:contains('Configuration')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".dropdown-menu a:contains('Notification')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-mail-DiscussNotificationSettings label:contains('Mute')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"button:contains('All Messages')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"button:contains('Mentions Only')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"button:contains('Nothing')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-header button[aria-label='Close']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_main_navbar button:contains('Configuration')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".dropdown-menu a:contains('Voice & Video')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"select[name='inputDevice']\",\n        },\n        {\n            trigger: \"button:contains('Voice Detection')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"label:contains('Voice detection threshold')\",\n        },\n        {\n            trigger: \"button:contains('Push to Talk')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"label:contains('Push-to-talk key')\",\n        },\n        {\n            trigger: \"label:contains('Delay after releasing push-to-talk')\",\n        },\n        {\n            trigger: \"input[aria-label='Show video participants only']\",\n        },\n        {\n            trigger: \"input[aria-label='Blur video background']\",\n            run: \"click\",\n        },\n        {\n            trigger: \"label:contains('Background blur intensity')\",\n        },\n        {\n            trigger: \"label:contains('Edge blur intensity')\",\n        },\n    ],\n});\n", "import { registry } from \"@web/core/registry\";\n\nregistry.category(\"web_tour.tours\").add(\"discuss_go_back_to_thread_from_breadcrumbs.js\", {\n    steps: () => [\n        { trigger: \".o-mail-Discuss-threadName[title='Inbox']\" },\n        { trigger: \".o-mail-DiscussSidebar-item:contains(Starred)\", run: \"click\" },\n        { trigger: \"button[title='View or join channels']\", run: \"click\" },\n        { trigger: \".breadcrumb-item:contains(Starred)\", run: \"click\" },\n        { trigger: \".o-mail-Discuss-threadName[title='Starred']\" },\n    ],\n});\n", "import { registry } from \"@web/core/registry\";\n\nregistry.category(\"web_tour.tours\").add(\"sidebar_in_public_page_tour\", {\n    steps: () => [\n        {\n            trigger: \".o-mail-Discuss-header [title='Channel 1']\",\n        },\n        {\n            trigger: \".o-mail-DiscussSidebarChannel:contains(Channel 1).o-active\",\n        },\n        {\n            trigger: \".o-mail-DiscussSidebarChannel:contains(Channel 2)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-mail-Discuss-header [title='Channel 2']\",\n        },\n        {\n            trigger: \".o-mail-DiscussSidebarChannel:contains(Channel 2).o-active\",\n            run() {\n                history.back();\n            },\n        },\n        {\n            trigger: \".o-mail-Discuss-header [title='Channel 1']\",\n        },\n        {\n            trigger: \".o-mail-DiscussSidebarChannel:contains(Channel 1).o-active\",\n            run() {\n                history.forward();\n            },\n        },\n        {\n            trigger: \".o-mail-Discuss-header [title='Channel 2']\",\n        },\n        {\n            trigger: \".o-mail-DiscussSidebarChannel:contains(Channel 2).o-active\",\n        },\n    ],\n});\n", "import { contains, scroll } from \"@web/../tests/utils\";\nimport { registry } from \"@web/core/registry\";\n\nregistry.category(\"web_tour.tours\").add(\"test_discuss_sub_channel_search\", {\n    steps: () => [\n        {\n            trigger: \"button[title='Threads']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-mail-SubChannelList\",\n            async run() {\n                // 30 newest sub channels are loaded initially.\n                for (let i = 99; i > 69; i--) {\n                    await contains(\".o-mail-SubChannelList-thread\", {\n                        text: `Sub Channel ${i}`,\n                    });\n                    await contains(\".o-mail-SubChannelList-thread\", { count: 30 });\n                }\n            },\n        },\n        {\n            trigger: \".o-mail-ActionPanel:has(.o-mail-SubChannelList) .o_searchview_input\",\n            run: \"edit Sub Channel 10\",\n        },\n        {\n            trigger:\n                \".o-mail-ActionPanel:has(.o-mail-SubChannelList) button[aria-label='Search button']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-mail-SubChannelList-thread:contains(Sub Channel 10)\",\n            async run() {\n                await contains(\".o-mail-SubChannelList-thread\", { count: 1 });\n            },\n        },\n        {\n            trigger: \".o_searchview_input\",\n            run: \"clear\",\n        },\n        {\n            trigger: \".o-mail-SubChannelList-thread:contains(Sub Channel 99)\",\n            async run() {\n                await contains(\".o-mail-SubChannelList-thread\", { count: 31 });\n                // Already fetched sub channels are shown in addition to the one\n                // that was fetched during the search.\n                for (let i = 99; i > 69; i--) {\n                    await contains(\".o-mail-SubChannelList-thread\", {\n                        text: `Sub Channel ${i}`,\n                    });\n                }\n                await contains(\".o-mail-SubChannelList-thread\", { text: `Sub Channel 10` });\n                // Ensure lazy loading is still working after a search.\n                await scroll(\".o-mail-ActionPanel:has(.o-mail-SubChannelList)\", \"bottom\");\n            },\n        },\n        {\n            trigger: \".o-mail-SubChannelList-thread:contains(Sub Channel 40)\",\n            async run() {\n                await contains(\".o-mail-SubChannelList-thread\", { count: 61 });\n                for (let i = 99; i > 39; i--) {\n                    await contains(\".o-mail-SubChannelList-thread\", {\n                        text: `Sub Channel ${i}`,\n                    });\n                }\n                await scroll(\".o-mail-ActionPanel:has(.o-mail-SubChannelList)\", \"bottom\");\n            },\n        },\n        {\n            trigger: \".o-mail-SubChannelList-thread:contains(Sub Channel 11)\",\n            async run() {\n                await contains(\".o-mail-SubChannelList-thread\", { count: 90 });\n                for (let i = 99; i > 9; i--) {\n                    await contains(\".o-mail-SubChannelList-thread\", {\n                        text: `Sub Channel ${i}`,\n                    });\n                }\n                await scroll(\".o-mail-ActionPanel:has(.o-mail-SubChannelList)\", \"bottom\");\n            },\n        },\n        {\n            trigger: \".o-mail-SubChannelList-thread:contains(Sub Channel 0)\",\n            async run() {\n                await contains(\".o-mail-SubChannelList-thread\", { count: 100 });\n                for (let i = 99; i > 0; i--) {\n                    await contains(\".o-mail-SubChannelList-thread\", {\n                        text: `Sub Channel ${i}`,\n                    });\n                }\n            },\n        },\n    ],\n});\n", "import { registry } from \"@web/core/registry\";\n\nregistry.category(\"web_tour.tours\").add(\"mail_activity_schedule_from_chatter\", {\n    steps: () => [\n        {\n            trigger: \"button:contains('Activities')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"input[id*='activity_type_id']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".dropdown-item:contains('Call')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"input[id*='activity_type_id']:value('Call')\",\n        },\n        {\n            trigger: \"button:contains('Schedule')\",\n        },\n        {\n            trigger: \"input[id*='activity_type_id']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".dropdown-item:contains('To-Do')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"div[name='summary'] input\",\n            run: \"edit Play Mario Party\",\n        },\n        {\n            trigger: \"button:contains('Schedule')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-mail-Activity:contains('Play Mario Party')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"button:contains('Activities')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"div[name='summary'] input\",\n            run: \"edit Play Mario Kart\",\n        },\n        {\n            trigger: \"button:contains('Mark as Done')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-mail-Message:contains('Play Mario Kart')\",\n        },\n    ],\n});\n", "import { registry } from \"@web/core/registry\";\nimport { contains, dragenterFiles, dropFiles, inputFiles } from \"@web/../tests/utils\";\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_composer.py\n */\nregistry.category(\"web_tour.tours\").add(\"mail/static/tests/tours/mail_composer_test_tour.js\", {\n    steps: () => [\n        {\n            content: \"Wait for the chatter to be fully loaded\",\n            trigger: \".o-mail-Chatter\",\n            async run() {\n                await contains(\".o-mail-Message\", { count: 1 });\n            },\n        },\n        {\n            content: \"Click on Send Message\",\n            trigger: \"button:contains(Send message)\",\n            run: \"click\",\n        },\n        {\n            content: \"Write something in composer\",\n            trigger: \".o-mail-Composer-input\",\n            run: \"edit blahblah @Not && click body\",\n        },\n        {\n            content: \"Mention a partner\",\n            trigger: \".o-mail-Composer-suggestion:contains(Not A Demo User)\",\n            run: \"click\",\n        },\n        {\n            content: \"Add one file in composer\",\n            trigger: \".o-mail-Composer button[aria-label='Attach files']\",\n            async run() {\n                const files = [new File([\"hello, world\"], \"file1.txt\", { type: \"text/plain\" })];\n                await inputFiles(\".o-mail-Composer-coreMain .o_input_file\", files);\n            },\n        },\n        {\n            trigger: \".o-mail-AttachmentCard:not(.o-isUploading)\", // waiting the attachment to be uploaded\n        },\n        {\n            content: \"Open full composer\",\n            trigger: \"button[aria-label='Full composer']\",\n            run: \"click\",\n        },\n        {\n            content: \"Check composer keeps open after pushing Escape\",\n            trigger: \".o_mail_composer_form_view\",\n            run: \"press Escape\",\n        },\n        {\n            content: \"Check the earlier provided attachment is listed\",\n            trigger: \".o_field_mail_composer_attachment_list a:contains(file1.txt)\",\n        },\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 !== \"Jane\") {\n                    console.error(\n                        `Full composer should have \"Jane\" in subject input (actual: ${subjectValue})`\n                    );\n                }\n            },\n        },\n        {\n            content: \"Check composer content is kept and contains the user's signature\",\n            trigger: '.o_field_html[name=\"body\"]',\n            run() {\n                const bodyContent = document.querySelector(\n                    '.o_field_html[name=\"body\"]'\n                ).textContent;\n                if (!bodyContent.includes(\"blahblah @Not A Demo User\")) {\n                    console.error(\n                        `Full composer should contain text from small composer (\"blahblah @Not A Demo User\") in body input (actual: ${bodyContent})`\n                    );\n                }\n                const mentionLink = document.querySelector(\n                    '.o_field_html[name=\"body\"] a'\n                ).textContent;\n                if (!mentionLink.includes(\"@Not A Demo User\")) {\n                    console.error(\n                        `Full composer should contain mention link from small composer (\"@Not A Demo User\") in body input)`\n                    );\n                }\n                /** When opening the full composer for the first time, the system\n                 * should add the user's signature to the end of the message so\n                 * that the user can edit it. After adding the signature to\n                 * the editor, the server shouldn't automatically add the\n                 * signature to the message (see: Python tests). */\n                if ((bodyContent.match(/--\\nErnest/g) || []).length !== 1) {\n                    console.error(\"Full composer should contain the user's signature once.\");\n                }\n            },\n        },\n        {\n            content: \"Drop a file on the full composer\",\n            trigger: \".o_mail_composer_form_view\",\n            async run() {\n                const files = [new File([\"hi there\"], \"file2.txt\", { type: \"text/plain\" })];\n                await dragenterFiles(\".o_mail_composer_form_view .o_form_renderer\", files);\n                await dropFiles(\".o-Dropzone\", files);\n            }\n        },\n        {\n            content: \"Check the attachment is listed\",\n            trigger: \".o_field_mail_composer_attachment_list a:contains(file2.txt)\",\n        },\n        {\n            content: \"Click on the mail template selector\",\n            trigger: \".mail-composer-template-dropdown-btn\",\n            run: \"click\",\n        },\n        {\n            content: \"Check a template is listed\",\n            trigger:\n                '.mail-composer-template-dropdown.popover .o-dropdown-item:contains(\"Test template\")',\n        },\n        {\n            content: \"Send message from full composer\",\n            trigger: \".o_mail_send\",\n            run: \"click\",\n        },\n        {\n            content: \"Check message is shown\",\n            trigger: '.o-mail-Message-body:contains(\"blahblah @Not A Demo User\")',\n            run: \"click\",\n        },\n        {\n            content: \"Click on envelope to see recipients of message\",\n            trigger:\n                '.o-mail-Message:has(.o-mail-Message-body:contains(\"blahblah @Not A Demo User\")) .o-mail-Message-notification',\n            run: \"click\",\n        },\n        {\n            content: \"Check message has correct recipients\",\n            trigger: \".o-mail-MessageNotificationPopover:contains('Not A Demo User\\nJane')\",\n        },\n        {\n            content: \"Check message contains the first attachment\",\n            trigger: '.o-mail-Message .o-mail-AttachmentCard:contains(\"file1.txt\")',\n        },\n        {\n            content: \"Check message contains the second attachment\",\n            trigger: '.o-mail-Message .o-mail-AttachmentCard:contains(\"file2.txt\")',\n        },\n        // Test the full composer input text is kept on closing\n        {\n            content: \"Click on Send Message\",\n            trigger: \"button:contains(Send message)\",\n            run: \"click\",\n        },\n        {\n            content: \"Open full composer\",\n            trigger: \"button[aria-label='Full composer']\",\n            run: \"click\",\n        },\n        {\n            content: \"Check that the composer contains the signature\",\n            trigger: '.o_field_html[name=\"body\"]',\n            run() {\n                const bodyContent = document.querySelector(\n                    '.o_field_html[name=\"body\"]'\n                ).textContent;\n                /** When opening the full composer, the system should add the\n                 * user's signature, as this is a new message and the signature\n                 * has not yet been added to it. */\n                if ((bodyContent.match(/--\\nErnest/g) || []).length !== 1) {\n                    console.log(\"Full composer should contain the user's signature once.\");\n                }\n            }\n        },\n        {\n            content: \"Write something in full composer\",\n            trigger: \".note-editable\",\n            run: \"editor keep the content\",\n        },\n        {\n            content: \"Close full composer\",\n            trigger: \".btn-close\",\n            run: \"click\",\n        },\n        {\n            content: \"Click on Send Message\",\n            trigger: \"button:contains(Send message)\",\n            run: \"click\",\n        },\n        {\n            content: \"Check full composer text is kept\",\n            trigger: \".o-mail-Composer-input\",\n            run() {\n                if (this.anchor.value !== \"keep the content\") {\n                    console.error(\n                        \"Composer in chatter should contain full composer text after discarding.\"\n                    );\n                }\n            },\n        },\n        {\n            content: \"Open full composer\",\n            trigger: \"button[aria-label='Full composer']\",\n            run: \"click\",\n        },\n        {\n            content: \"Check that the composer doesn't add the user's signature twice\",\n            trigger: \".note-editable\",\n            run() {\n                const bodyContent = document.querySelector(\n                    '.o_field_html[name=\"body\"]'\n                ).textContent;\n                /** When re-opening the full composer, the system shouldn't re-add\n                 * the user's signature to the message. As the user deleted the\n                 * signature in the previous steps (see: `editor keep the content`),\n                 * the editor shouldn't contain any signature. */\n                if ((bodyContent.match(/--\\nErnest/g) || []).length !== 0) {\n                    console.error(\"The composer should not contain the user's signature.\");\n                }\n            }\n        },\n        {\n            content: \"Close full composer\",\n            trigger: \".btn-close\",\n            run: \"click\",\n        },\n        {\n            content: \"Click on Send Message\",\n            trigger: \"button:contains(Send message)\",\n            run: \"click\",\n        },\n        {\n            content: \"Send message from chatter\",\n            trigger: \".o-mail-Composer-send\",\n            run: \"click\"\n        },\n        {\n            content: \"Check message is shown\",\n            trigger: '.o-mail-Message-body:contains(\"keep the content\")',\n        },\n        // Test that the server automatically adds the user's signature to the\n        // email when the user didn't open the full composer.\n        {\n            content: \"Click on Send Message\",\n            trigger: \"button:contains(Send message)\",\n            run: \"click\",\n        },\n        {\n            content: \"Write a message\",\n            trigger: \".o-mail-Composer-input\",\n            run: \"edit hello world\",\n        },\n        {\n            content: \"Send message from chatter\",\n            trigger: \".o-mail-Composer-send\",\n            run: \"click\"\n        },\n        {\n            content: \"Check message is shown\",\n            trigger: '.o-mail-Message-body:contains(\"hello world\")',\n        },\n        // Test text input lines are each wrapped in <p> in editor\n        // this makes each line editable without impacting the other lines\n        {\n            content: \"Click on Send Message\",\n            trigger: \"button:contains(Send message)\",\n            run: \"click\",\n        },\n        {\n            content: \"Write first line\",\n            trigger: \".o-mail-Composer-input\",\n            run: \"edit abc\",\n        },\n        {\n            content: \"Press enter to go to next line\",\n            trigger: \".o-mail-Composer-input\",\n            run: \"press enter\",\n        },\n        {\n            content: \"write second line\",\n            trigger: \".o-mail-Composer-input\",\n            run: \"fill efg\",\n        },\n        {\n            content: \"Open full composer\",\n            trigger: \"button[aria-label='Full composer']\",\n            run: \"click\",\n        },\n        {\n            content: \"Check the content of the editor\",\n            trigger:\n                \".o_mail_composer_form_view .odoo-editor-editable > p:contains(abc):not(:contains(efg))\",\n        },\n        {\n            content: \"Check the content of the editor\",\n            trigger:\n                \".o_mail_composer_form_view .odoo-editor-editable > p:contains(efg):not(:contains(abc))\",\n        },\n    ],\n});\n", "import { registry } from \"@web/core/registry\";\nimport { contains, scroll } from \"@web/../tests/utils\";\n\nregistry.category(\"web_tour.tours\").add(\"mail_message_load_order_tour\", {\n    steps: () => [\n        {\n            trigger: \".o-mail-DiscussSidebarChannel:contains(MyTestChannel)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-mail-Thread .o-mail-Message\",\n            async run() {\n                await contains(\".o-mail-Thread .o-mail-Message\", { count: 30 });\n                await contains(\".o-mail-Thread\", { scroll: \"bottom\" });\n            },\n        },\n        {\n            trigger: \"*[title='Pinned Messages']\",\n            run: \"click\",\n        },\n        {\n            content: \"Click on invisible jump (should hover card to be visible)\",\n            trigger: \".o-mail-MessageCard-jump:not(:visible)\",\n            run: \"click\",\n        },\n        {\n            // Messages depends on FETCH_LIMIT (currently set to 60) in\n            // the thread service. Thus, at first load the message range\n            // will be (31 - 60). This trigger ensures the next messages\n            // are fetched after jumping to the message.\n            trigger:\n                \".o-mail-Thread .o-mail-Message:first .o-mail-Message-textContent:not(:contains(31))\",\n            async run() {\n                await contains(\".o-mail-Thread .o-mail-Message\", { count: 31 });\n                await contains(\".o-mail-Thread\", { scroll: 0 });\n                // ensure 1 - 31 are loaded in order: 30 below and the\n                // one we're loading messages around.\n                const messages = Array.from(\n                    document.querySelectorAll(\".o-mail-Thread .o-mail-Message-content\")\n                ).map((el) => el.innerText);\n                for (let i = 0; i < 31; i++) {\n                    if (messages[i] !== (i + 1).toString()) {\n                        throw new Error(\"Wrong message order after loading around\");\n                    }\n                }\n                await scroll(\".o-mail-Thread\", \"bottom\");\n            },\n        },\n        {\n            // After jumping to the pinned message, the message range\n            // was (1 -31): 30 before (but none were found), 30 after\n            // and the pinned message itself. This trigger ensures the\n            // next messages are fetched after scrolling to the bottom.\n            trigger: \".o-mail-Thread .o-mail-Message .o-mail-Message-textContent:contains(17)\",\n            async run() {\n                await contains(\".o-mail-Thread .o-mail-Message\", { count: 60 });\n                // ensure 1 - 60  are loaded in order.\n                const messages = Array.from(\n                    document.querySelectorAll(\".o-mail-Thread .o-mail-Message-content\")\n                ).map((el) => el.innerText);\n                for (let i = 0; i < 60; i++) {\n                    if (messages[i] !== (i + 1).toString()) {\n                        throw new Error(\"Wrong message order after loading after\");\n                    }\n                }\n            },\n        },\n    ],\n});\n", "import { registry } from \"@web/core/registry\";\nimport { stepUtils } from \"@web_tour/tour_service/tour_utils\";\n\nregistry.category(\"web_tour.tours\").add(\"mail_template_dynamic_placeholder_tour\", {\n    url: \"/odoo\",\n    steps: () => [\n        stepUtils.showAppsMenuItem(),\n        {\n            content: 'Go into the Setting \"app\"',\n            trigger: '.o_app[data-menu-xmlid=\"base.menu_administration\"]',\n            run: \"click\",\n        },\n        {\n            content: \"Open email templates\",\n            trigger: 'button[name=\"open_mail_templates\"]',\n            run: \"click\",\n        },\n        {\n            content: \"Create a new email template\",\n            trigger: \"button.o_list_button_add\",\n            run: \"click\",\n        },\n        {\n            content: 'Insert # inside \"Subject\" input',\n            trigger: 'div[name=\"subject\"] input[type=\"text\"]',\n            run: \"edit(no_model_id #)\",\n        },\n        {\n            content: 'Select \"Contact\" model',\n            trigger: 'div[name=\"model_id\"] input[type=\"text\"]',\n            run: \"edit Contact\",\n        },\n        {\n            content: \"Wait for the autocomplete RPC\",\n            trigger: 'div[name=\"model_id\"] .ui-autocomplete:contains(\"Contact\")',\n        },\n        {\n            content: \"Click on contact\",\n            trigger: 'div[name=\"model_id\"] .ui-autocomplete',\n            run: async function () {\n                const contact = Array.from(\n                    document.querySelectorAll(\n                        'div[name=\"model_id\"] .ui-autocomplete .dropdown-item'\n                    )\n                ).find((el) => el.textContent === \"Contact\");\n                await contact.click();\n            },\n        },\n        {\n            content: \"Wait for the drop down to disappear\",\n            trigger: 'div[name=\"model_id\"] .o-autocomplete:not(:has(.ui-autocomplete))',\n            run: async () => {\n                // Ensure the system has registered a correct model value before\n                // we try to open the DPH.\n                // It seems that the autocomplete validation can be very slow.\n                await new Promise((r) => setTimeout(r, 200));\n            },\n        },\n        {\n            content: 'Retry insert # inside \"Subject\" input',\n            trigger: 'div[name=\"subject\"] input[type=\"text\"]',\n            run: \"edit (yes_model_id) && press #\",\n        },\n        {\n            content: \"Check if the dynamic placeholder popover is opened\",\n            trigger: \"div.o_model_field_selector_popover\",\n        },\n        {\n            content: \"filter the dph result\",\n            trigger: \"div.o_model_field_selector_popover_search input[type='text']\",\n            run: \"edit name\",\n        },\n        {\n            content: \"Click on the first entry of the dynamic placeholder\",\n            trigger: 'div.o_model_field_selector_popover button:contains(\"Company Name\")',\n            run: \"click\",\n        },\n        {\n            content: \"Enter a default value\",\n            trigger:\n                'div.o_model_field_selector_popover .o_model_field_selector_default_value_input input[type=\"text\"]',\n            run: \"edit defValue\",\n        },\n        {\n            content: \"Click on the insert button\",\n            trigger: \"div.o_model_field_selector_popover button:first-child\",\n            run: \"click\",\n        },\n        {\n            content: \"Wait for the popover to disappear\",\n            trigger: \"body:not(:has(.o_model_field_selector_popover))\",\n            run: \"click\",\n        },\n        {\n            content: \"Check if subject value was correctly updated\",\n            trigger: 'div[name=\"subject\"] input[type=\"text\"]',\n            run() {\n                const subjectValue = this.anchor.value;\n                const correctValue = \"yes_model_id {{object.company_name|||defValue}}\";\n                if (subjectValue !== correctValue) {\n                    console.error(\n                        `Email template should have \"${correctValue}\" in subject input (actual: ${subjectValue})`\n                    );\n                }\n            },\n        },\n        {\n            content: \"Insert text inside editable\",\n            trigger: \".note-editable.odoo-editor-editable\",\n            async run(actions) {\n                await actions.editor(`/`);\n                document.querySelector(\".note-editable\").dispatchEvent(\n                    new InputEvent(\"input\", {\n                        inputType: \"insertText\",\n                        data: \"/\",\n                    })\n                );\n            },\n        },\n        {\n            content: \"Click on the the dynamic placeholder powerBox options\",\n            trigger: \"div.o-we-powerbox .o-we-command:contains(Dynamic Placeholder)\",\n            run: \"click\",\n        },\n        {\n            content: \"Check if the dynamic placeholder popover is opened\",\n            trigger: \"div.o_model_field_selector_popover\",\n            run: \"click\",\n        },\n        {\n            content: \"filter the dph result\",\n            trigger: \"div.o_model_field_selector_popover_search input[type='text']\",\n            run: \"edit name\",\n        },\n        {\n            content: \"Click on the first entry of the dynamic placeholder\",\n            trigger: 'div.o_model_field_selector_popover button:contains(\"Company Name\")',\n            run: \"click\",\n        },\n        {\n            content: \"Enter a default value\",\n            trigger:\n                'div.o_model_field_selector_popover .o_model_field_selector_default_value_input input[type=\"text\"]',\n            run: \"edit defValue\",\n        },\n        {\n            content: \"Click on the insert button\",\n            trigger: \"div.o_model_field_selector_popover button:first-child\",\n            run: \"click\",\n        },\n        {\n            content: \"Ensure the editable contain the dynamic placeholder t tag\",\n            trigger: `.note-editable.odoo-editor-editable t[t-out=\"object.company_name\"]:contains(\"defValue\")`,\n            run: \"click\",\n        },\n        {\n            content: \"Discard form changes\",\n            trigger: \"button.o_form_button_cancel\",\n            run: \"click\",\n        },\n        {\n            content: \"Wait for the form view to disappear\",\n            trigger: \"body:not(:has(.o_form_sheet))\",\n        },\n    ],\n});\n", "import { registry } from \"@web/core/registry\";\nimport { contains, insertText } from \"@web/../tests/utils\";\n\n/**\n * Verify that a user can modify their own profile information.\n */\nregistry.category(\"web_tour.tours\").add(\"mail/static/tests/tours/user_modify_own_profile_tour.js\", {\n    steps: () => [\n        {\n            content: \"Open user account menu\",\n            trigger: \".o_user_menu button\",\n            run: \"click\",\n        },\n        {\n            content: \"Open preferences / profile screen\",\n            trigger: \"[data-menu=settings]\",\n            run: \"click\",\n        },\n        {\n            content: \"Update the email address\",\n            trigger: 'div[name=\"email\"] input',\n            async run() {\n                await insertText(\"div[name='email'] input\", \"updatedemail@example.com\", {\n                    replace: true,\n                });\n                await contains(\".o_form_dirty\", { count: 1 });\n            },\n        },\n        {\n            trigger: \"body.modal-open\",\n        },\n        {\n            content: \"Save the form\",\n            trigger: 'button[name=\"preference_save\"]',\n            run: \"click\",\n        },\n        {\n            content: \"Wait until the modal is closed\",\n            trigger: \"body:not(.modal-open)\",\n            async run() {\n                await contains(\".o_form_dirty\", { count: 0 });\n            },\n        },\n    ],\n});\n", "/** @odoo-module **/\n\nimport { registry } from \"@web/core/registry\";\n\nregistry.category(\"web_tour.tours\").add('portal_load_homepage', {\n    url: '/my',\n    steps: () => [\n        {\n            content: \"Check portal is loaded\",\n            trigger: 'a[href*=\"/my/account\"]:contains(\"Edit\"):first',\n            run: \"click\",\n        },\n        {\n            content: \"Load my account details\",\n            trigger: 'input[value=\"Joel Willis\"]',\n            run: \"click\",\n        },\n        {\n            content: 'type a different phone number',\n            trigger: 'input[name=\"phone\"]',\n            run: \"edit +1 555 666 7788\",\n        },\n        {\n            content: \"Submit the form\",\n            trigger: 'button[type=submit]',\n            run: \"click\",\n        },\n        {\n            content: \"Check that we are back on the portal\",\n            trigger: 'a[href*=\"/my/account\"]:contains(\"Edit\"):first',\n        }\n    ]\n});\n", "import { registry } from \"@web/core/registry\";\n\nregistry.category(\"web_tour.tours\").add(\"skip_to_content\", {\n    url: \"/\",\n    steps: () => [\n        {\n            content: \"Make sure that Skip to Content button is on top of all the links present in header\",\n            trigger: \"a:first-child[class~='o_skip_to_content']\",\n            run: \"click\"\n        },\n        {\n            content: \"Check if we have been redirected to #wrap\",\n            trigger: \"body\",\n            run: () => {\n                if (!window.location.href.endsWith(\"#wrap\")) {\n                    console.error(\"We should be on #wrap.\");\n                }\n            }\n        }\n    ]\n});\n", "/** @odoo-module */\n\nimport { accountTourSteps } from \"@account/js/tours/account\";\nimport { registry } from \"@web/core/registry\";\nimport { stepUtils } from \"@web_tour/tour_service/tour_utils\";\n\nregistry.category(\"web_tour.tours\").add('account_tax_group', {\n    url: \"/odoo\",\n    steps: () => [\n    ...accountTourSteps.goToAccountMenu(\"Go to Invoicing\"),\n    {\n        content: \"Go to Vendors\",\n        trigger: 'span:contains(\"Vendors\")',\n        run: \"click\",\n    },\n    {\n        content: \"Go to Bills\",\n        trigger: 'a:contains(\"Bills\")',\n        run: \"click\",\n    },\n    {\n        trigger: \".o_breadcrumb .text-truncate:contains(Bills)\",\n    },\n    {\n        content: \"Create new bill\",\n        trigger: '.o_control_panel_main_buttons .o_list_button_add',\n        run: \"click\",\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: \"edit Account Tax Group Partner\",\n    },\n    {\n        content: \"Valid vendor\",\n        trigger: '.ui-menu-item a:contains(\"Account Tax Group Partner\")',\n        run: \"click\",\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        run: \"click\",\n    },\n    {\n        content: \"Select input\",\n        trigger: 'div[name=\"invoice_line_ids\"] .o_selected_row .o_list_many2one[name=\"product_id\"] input',\n        run: \"edit Account Tax Group Product\",\n    },\n    {\n        content: \"Valid item\",\n        trigger: '.ui-menu-item-wrapper:contains(\"Account Tax Group Product\")',\n        run: \"click\",\n    },\n    // Save account.move\n    ...stepUtils.saveForm(),\n    // Edit tax group amount\n    {\n        content: \"Edit tax group amount\",\n        trigger: '.o_tax_group_edit',\n        run: \"click\",\n    },\n    {\n        content: \"Modify the input value\",\n        trigger: '.o_tax_group_edit_input input',\n        run() {\n            this.anchor.value = 200;\n            this.anchor.select();\n            this.anchor.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        run: \"click\",\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        run: \"click\",\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: \"edit 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: \"press Enter\",\n    },\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        run: \"click\",\n    },\n    // Save form\n    ...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        run: \"click\",\n    },\n    {\n        content: \"Edit tax value\",\n        trigger: '.o_tax_group_edit_input input',\n        run: \"edit 2 && click body\",\n    },\n    {\n        content: \"Check new value of total\",\n        trigger: '.oe_subtotal_footer_separator:contains(\"1,202\")',\n        run: \"click\",\n    },\n    {\n        content: \"Discard changes\",\n        trigger: '.o_form_button_cancel',\n        run: \"click\",\n    },\n    {\n        content: \"Check tax value is reset\",\n        trigger: '.o_tax_group_amount_value:contains(\"120\")',\n    },\n]});\n", "/** @odoo-module */\n\nimport { registry } from \"@web/core/registry\";\n\nregistry.category(\"web_tour.tours\").add('tests_shared_js_python', {\n    url: \"/account/init_tests_shared_js_python\",\n    steps: () => [\n    {\n        content: \"Click\",\n        trigger: 'button',\n        run: \"click\",\n    },\n    {\n        content: \"Wait\",\n        trigger: 'button.text-success',\n        timeout: 3000,\n    },\n]});\n", "import { registry } from \"@web/core/registry\";\nimport { stepUtils } from \"@web_tour/tour_service/tour_utils\";\n\nconst openProductAttribute = (product_attribute) => [\n    ...stepUtils.goToAppSteps(\"sale.sale_menu_root\", \"Go to the Sales App\"),\n    {\n        content: 'Open configuration menu',\n        trigger: '.o-dropdown[data-menu-xmlid=\"sale.menu_sale_config\"]',\n        run: \"click\",\n    },\n    {\n        content: 'Navigate to product attribute list view',\n        trigger: '.o-dropdown-item[data-menu-xmlid=\"sale.menu_product_attribute_action\"]',\n        run: \"click\",\n    },\n    {\n        content: `Navigate to ${product_attribute}`,\n        trigger: `.o_data_cell[data-tooltip=${product_attribute}]`,\n        run: \"click\",\n    },\n];\nconst deletePAV = (product_attribute_value, message) => [\n    {\n        content: 'Click delete button',\n        trigger: `.o_data_cell[data-tooltip=${product_attribute_value}] ~ .o_list_record_remove`,\n        run: \"click\",\n    },\n    {\n        content: 'Check correct message in modal',\n        trigger: message || '.modal-title:contains(\"Bye-bye, record!\")',\n        run: \"click\",\n    },\n    {\n        content: 'Close modal',\n        trigger: '.btn-close',\n        run: \"click\",\n    }\n]\n\n// This tour relies on data created on the Python test.\nregistry.category(\"web_tour.tours\").add('delete_product_attribute_value_tour', {\n    url: '/odoo',\n    steps: () => [\n        ...openProductAttribute(\"PA\"),\n        // Test error message on a used attribute value\n        ...deletePAV(\"pa_value_1\", \".text-prewrap:contains('pa_value_1')\"),\n        // Test deletability of a used attribute value on archived product\n        ...deletePAV(\"pa_value_2\"),\n        // Test deletability of a removed attribute value on product\n        ...deletePAV(\"pa_value_3\"),\n        {\n            content: 'Check test finished',\n            trigger: 'a:contains(\"Attributes\")',\n        }\n    ]\n});\n", "import { registry } from '@web/core/registry';\nimport { stepUtils } from '@web_tour/tour_service/tour_utils';\nimport comboConfiguratorTourUtils from '@sale/js/tours/combo_configurator_tour_utils';\nimport productConfiguratorTourUtils from '@sale/js/tours/product_configurator_tour_utils';\nimport tourUtils from '@sale/js/tours/tour_utils';\n\nregistry\n    .category('web_tour.tours')\n    .add('sale_combo_configurator', {\n        url: '/odoo',\n        steps: () => [\n            ...stepUtils.goToAppSteps('sale.sale_menu_root', \"Open the sales app\"),\n            ...tourUtils.createNewSalesOrder(),\n            ...tourUtils.selectCustomer(\"Test Partner\"),\n            ...tourUtils.addProduct(\"Combo product\"),\n            // Assert that the combo configurator has the correct data.\n            comboConfiguratorTourUtils.assertComboCount(2),\n            comboConfiguratorTourUtils.assertComboItemCount(\"Combo A\", 2),\n            comboConfiguratorTourUtils.assertComboItemCount(\"Combo B\", 2),\n            // Assert that price changes when the quantity is updated.\n            comboConfiguratorTourUtils.assertQuantity(1),\n            comboConfiguratorTourUtils.assertPrice('25.00'),\n            comboConfiguratorTourUtils.increaseQuantity(),\n            comboConfiguratorTourUtils.assertQuantity(2),\n            comboConfiguratorTourUtils.assertPrice('50.00'),\n            comboConfiguratorTourUtils.decreaseQuantity(),\n            comboConfiguratorTourUtils.assertQuantity(1),\n            comboConfiguratorTourUtils.assertPrice('25.00'),\n            comboConfiguratorTourUtils.setQuantity(3),\n            comboConfiguratorTourUtils.assertQuantity(3),\n            comboConfiguratorTourUtils.assertPrice('75.00'),\n            // Assert that the combo configurator can only be saved after selecting an item for each\n            // combo.\n            comboConfiguratorTourUtils.assertConfirmButtonDisabled(),\n            comboConfiguratorTourUtils.selectComboItem(\"Product A2\"),\n            comboConfiguratorTourUtils.selectComboItem(\"Product B2\"),\n            comboConfiguratorTourUtils.assertConfirmButtonEnabled(),\n            // Assert that the product configurator is opened when a product with `no_variant` PTALs\n            // is selected.\n            comboConfiguratorTourUtils.selectComboItem(\"Product A1\"),\n            productConfiguratorTourUtils.selectAttribute(\"Product A1\", \"No variant attribute\", \"A\"),\n            {\n                content: \"Confirm the product configurator\",\n                trigger: 'button[name=\"sale_product_configurator_confirm_button\"]',\n                run: 'click',\n            },\n            // Assert that the extra price of a combo item is applied correctly.\n            comboConfiguratorTourUtils.assertPrice('90.00'),\n            // Assert that the extra price of a `no_variant` PTAV is applied correctly.\n            comboConfiguratorTourUtils.selectComboItem(\"Product A1\"),\n            ...productConfiguratorTourUtils.selectAndSetCustomAttribute(\n                \"Product A1\", \"No variant attribute\", \"B\", \"Some custom value\"\n            ),\n            {\n                content: \"Confirm the product configurator\",\n                trigger: 'button[name=\"sale_product_configurator_confirm_button\"]',\n                run: 'click',\n            },\n            comboConfiguratorTourUtils.assertPrice('93.00'),\n            // Assert that the order's content is correct.\n            ...comboConfiguratorTourUtils.saveConfigurator(),\n            tourUtils.checkSOLDescriptionContains(\"Combo product x 3\"),\n            tourUtils.checkSOLDescriptionContains(\n                \"Product A1\", \"No variant attribute: B: Some custom value\"\n            ),\n            tourUtils.checkSOLDescriptionContains(\"Product B2\"),\n            {\n                content: \"Verify the combo item quantities\",\n                trigger: 'td[name=\"product_uom_qty\"]:contains(3.00)',\n            },\n            {\n                content: \"Verify the first combo item's unit price\",\n                trigger: 'td[name=\"price_unit\"]:contains(18.50)',\n            },\n            {\n                content: \"Verify the second combo item's unit price\",\n                trigger: 'td[name=\"price_unit\"]:contains(12.50)',\n            },\n            {\n                content: \"Verify the order's total price\",\n                trigger: 'div.oe_subtotal_footer:contains(93.00)',\n            },\n            // Assert that the combo configurator is opened with the previous selection when the\n            // combo is edited.\n            tourUtils.editLineMatching(\"Combo product x 3\"),\n            tourUtils.editConfiguration(),\n            comboConfiguratorTourUtils.setQuantity(2),\n            comboConfiguratorTourUtils.assertComboItemSelected(\"Product A1\"),\n            comboConfiguratorTourUtils.assertComboItemSelected(\"Product B2\"),\n            comboConfiguratorTourUtils.selectComboItem(\"Product A2\"),\n            // Assert that the order's content has been updated.\n            ...comboConfiguratorTourUtils.saveConfigurator(),\n            tourUtils.checkSOLDescriptionContains(\"Combo product x 2\"),\n            tourUtils.checkSOLDescriptionContains(\"Product A2\"),\n            tourUtils.checkSOLDescriptionContains(\"Product B2\"),\n            {\n                content: \"Verify the combo item quantities\",\n                trigger: 'td[name=\"product_uom_qty\"]:contains(2.00)',\n            },\n            {\n                content: \"Verify the first combo item's unit price\",\n                trigger: 'td[name=\"price_unit\"]:contains(12.50)',\n            },\n            {\n                content: \"Verify the second combo item's unit price\",\n                trigger: 'td[name=\"price_unit\"]:contains(12.50)',\n            },\n            {\n                content: \"Verify the order's total price\",\n                trigger: 'div.oe_subtotal_footer:contains(50.00)',\n            },\n            // Don't end the tour with a form in edition mode.\n            ...stepUtils.saveForm(),\n        ],\n    });\n", "/** @odoo-module **/\n\nimport { registry } from \"@web/core/registry\";\nimport { redirect } from \"@web/core/utils/urls\";\n\n// This tour relies on data created on the Python test.\nregistry.category(\"web_tour.tours\").add('sale_signature', {\n    url: '/my/quotes',\n    steps: () => [\n    {\n        content: \"open the test SO\",\n        trigger: 'a:contains(/^test SO$/)',\n        run: \"click\",\n    },\n    {\n        content: \"click sign\",\n        trigger: 'a:contains(\"Sign\")',\n        run: \"click\",\n    },\n    {\n        content: \"check submit is enabled\",\n        trigger: '.o_portal_sign_submit:enabled',\n    },\n    {\n        content: \"click select style\",\n        trigger: '.o_web_sign_auto_select_style button',\n        run: \"click\",\n    },\n    {\n        content: \"click style 4\",\n        trigger: \".o-dropdown-item:eq(3)\",\n        run: \"click\",\n    },\n    {\n        content: \"click submit\",\n        trigger: '.o_portal_sign_submit:enabled',\n        run: \"click\",\n    },\n    {\n        content: \"check it's confirmed\",\n        trigger: '#quote_content:contains(\"Thank You\")',\n        run: \"click\",\n    }, {\n        trigger: '#quote_content',\n        run: function () {\n            redirect(\"/odoo\");\n        },  // Avoid race condition at the end of the tour by returning to the home page.\n    },\n    {\n        trigger: 'nav',\n    }\n]});\n", "import { queryAll, queryValue, waitUntil } from '@odoo/hoot-dom';\n\nfunction comboSelector(comboName) {\n    return `\n        .sale-combo-configurator-dialog\n        [name=\"sale_combo_configurator_title\"]:contains(\"${comboName}\")\n    `;\n}\n\nfunction comboItemSelector(comboItemName, extraClasses=[]) {\n    const extraClassesSelector = extraClasses.map(extraClass => `.${extraClass}`).join('');\n    return `\n        .sale-combo-configurator-dialog\n        .combo-item-grid\n        .product-card${extraClassesSelector}:has(.card-title:contains(\"${comboItemName}\"))\n    `;\n}\n\nfunction assertComboCount(count) {\n    return {\n        content: `Assert that there are ${count} combos`,\n        trigger:'.sale-combo-configurator-dialog',\n        run: () => queryAll(\n            '.sale-combo-configurator-dialog [name=\"sale_combo_configurator_title\"]'\n        ).length === count,\n    };\n}\n\nfunction assertComboItemCount(comboName, count) {\n    return {\n        content: `Assert that there are ${count} combo items in combo ${comboName}`,\n        trigger: comboSelector(comboName),\n        run: () => queryAll(\n            `${comboSelector(comboName)} .combo-item-grid .product-card`\n        ).length === count,\n    };\n}\n\nfunction selectComboItem(comboItemName) {\n    return {\n        content: `Select combo item ${comboItemName}`,\n        trigger: comboItemSelector(comboItemName),\n        run: 'click',\n    };\n}\n\nfunction assertComboItemSelected(comboItemName) {\n    return {\n        content: `Assert that combo item ${comboItemName} is selected`,\n        trigger: comboItemSelector(comboItemName, ['selected']),\n    };\n}\n\nfunction increaseQuantity() {\n    return {\n        content: \"Increase the combo quantity\",\n        trigger: '.sale-combo-configurator-dialog button[name=\"sale_quantity_button_plus\"]',\n        run: 'click',\n    };\n}\n\nfunction decreaseQuantity() {\n    return {\n        content: \"Decrease the combo quantity\",\n        trigger: '.sale-combo-configurator-dialog button[name=\"sale_quantity_button_minus\"]',\n        run: 'click',\n    };\n}\n\nfunction setQuantity(quantity) {\n    return {\n        content: `Set the combo quantity to ${quantity}`,\n        trigger: '.sale-combo-configurator-dialog input[name=\"sale_quantity\"]',\n        run: `edit ${quantity} && click .modal-body`,\n    };\n}\n\nfunction assertQuantity(quantity) {\n    const quantitySelector = '.sale-combo-configurator-dialog input[name=\"sale_quantity\"]';\n    return {\n        content: `Assert that the combo quantity is ${quantity}`,\n        trigger: quantitySelector,\n        run: async () =>\n            await waitUntil(() => queryValue(quantitySelector) === quantity, { timeout: 1000 }),\n    };\n}\n\nfunction assertPrice(price) {\n    return {\n        content: `Assert that the price is ${price}`,\n        trigger: `\n            .sale-combo-configurator-dialog\n            [name=\"sale_combo_configurator_total\"]:contains(\"${price}\")\n        `,\n    };\n}\n\nfunction assertPriceInfo(priceInfo) {\n    return {\n        content: `Assert that the price info is ${priceInfo}`,\n        trigger: `.sale-combo-configurator-dialog footer.modal-footer:contains(\"${priceInfo}\")`,\n    };\n}\n\nfunction assertFooterButtonsDisabled() {\n    return {\n        content: \"Assert that the footer buttons are disabled\",\n        trigger: '.sale-combo-configurator-dialog footer.modal-footer button:disabled',\n    };\n}\n\nfunction assertFooterButtonsEnabled() {\n    return {\n        content: \"Assert that the footer buttons are enabled\",\n        trigger: '.sale-combo-configurator-dialog footer.modal-footer button:enabled',\n    };\n}\n\nfunction assertConfirmButtonDisabled() {\n    return {\n        content: \"Assert that the confirm button is disabled\",\n        trigger: `\n            .sale-combo-configurator-dialog\n            button[name=\"sale_combo_configurator_confirm_button\"]:disabled\n        `,\n    };\n}\n\nfunction assertConfirmButtonEnabled() {\n    return {\n        content: \"Assert that the confirm button is enabled\",\n        trigger: `\n            .sale-combo-configurator-dialog\n            button[name=\"sale_combo_configurator_confirm_button\"]:enabled\n        `,\n    };\n}\n\nfunction saveConfigurator() {\n    return [\n        {\n            content: \"Confirm the combo configurator\",\n            trigger: `\n                .sale-combo-configurator-dialog\n                button[name=\"sale_combo_configurator_confirm_button\"]\n            `,\n            run: 'click',\n        }, {\n            content: \"Wait until the modal is closed\",\n            trigger: 'body:not(:has(.modal))',\n        },\n    ];\n}\n\nexport default {\n    comboSelector,\n    comboItemSelector,\n    assertComboCount,\n    assertComboItemCount,\n    selectComboItem,\n    assertComboItemSelected,\n    increaseQuantity,\n    decreaseQuantity,\n    setQuantity,\n    assertQuantity,\n    assertPrice,\n    assertPriceInfo,\n    assertFooterButtonsDisabled,\n    assertFooterButtonsEnabled,\n    assertConfirmButtonDisabled,\n    assertConfirmButtonEnabled,\n    saveConfigurator,\n};\n", "import { queryAttribute, queryValue, waitUntil } from '@odoo/hoot-dom';\n\nfunction productSelector(productName) {\n    return `\n        table.o_sale_product_configurator_table\n        tr:has(td>div[name=\"o_sale_product_configurator_name\"]\n        span:contains(\"${productName}\"))\n    `;\n}\n\nfunction optionalProductSelector(productName) {\n    return `\n        table.o_sale_product_configurator_table_optional\n        tr:has(td>div[name=\"o_sale_product_configurator_name\"]\n        span:contains(\"${productName}\"))\n    `;\n}\n\nfunction optionalProductImageSrc(productName) {\n    return queryAttribute(\n        `${optionalProductSelector(productName)} td.o_sale_product_configurator_img>img`, 'src'\n    );\n}\n\nfunction addOptionalProduct(productName) {\n    return {\n        content: `Add ${productName}`,\n        trigger: `\n            ${optionalProductSelector(productName)}\n            td.o_sale_product_configurator_price\n            button:contains(\"Add\")\n        `,\n        run: 'click',\n    };\n}\n\nfunction removeOptionalProduct(productName) {\n    return {\n        content: `Remove ${productName}`,\n        trigger: `\n            ${productSelector(productName)}\n            td.o_sale_product_configurator_qty\n            a:contains(\"Remove\")\n        `,\n        run: 'click',\n    };\n}\n\nfunction increaseProductQuantity(productName) {\n    return {\n        content: `Increase the quantity of ${productName}`,\n        trigger: `\n            ${productSelector(productName)}\n            td.o_sale_product_configurator_qty\n            button:has(i.fa-plus)\n        `,\n        run: 'click',\n    };\n}\n\nfunction setProductQuantity(productName, quantity) {\n    return {\n        content: `Set the quantity of ${productName} to ${quantity}`,\n        trigger: `\n            ${productSelector(productName)}\n            td.o_sale_product_configurator_qty\n            input[name=\"sale_quantity\"]\n        `,\n        run: `edit ${quantity} && click .modal-body`,\n    };\n}\n\nfunction assertProductQuantity(productName, quantity) {\n    const quantitySelector = `\n        ${productSelector(productName)}\n        td.o_sale_product_configurator_qty\n        input[name=\"sale_quantity\"]\n    `;\n    return {\n        content: `Assert that the quantity of ${productName} is ${quantity}`,\n        trigger: quantitySelector,\n        run: async () =>\n            await waitUntil(() => queryValue(quantitySelector) === quantity, { timeout: 1000 }),\n    };\n}\n\nfunction selectAttribute(productName, attributeName, attributeValue, attributeType='radio') {\n    const ptalSelector = `\n        ${productSelector(productName)}\n        td>div[name=\"ptal\"]:has(label:contains(\"${attributeName}\"))\n    `;\n    const content = `Select ${attributeValue} for ${productName} ${attributeName}`;\n    switch (attributeType) {\n        case 'color':\n            return {\n                content: content,\n                trigger: `${ptalSelector} label[title=\"${attributeValue}\"]`,\n                run: 'click',\n            };\n        case 'multi':\n        case 'pills':\n        case 'radio':\n            return {\n                content: content,\n                trigger: `${ptalSelector} span:contains(\"${attributeValue}\")`,\n                run: 'click',\n            };\n        case 'select':\n            return {\n                content: content,\n                trigger: `${ptalSelector} select`,\n                run: `selectByLabel ${attributeValue}`,\n            };\n        default:\n            console.error(\"Unsupported attribute type\");\n    }\n}\n\nfunction setCustomAttribute(productName, attributeName, customValue) {\n    return {\n        content: `Set ${customValue} as a custom attribute for ${productName} ${attributeName}`,\n        trigger: `\n            ${productSelector(productName)}\n            td>div[name=\"ptal\"]:has(label:contains(\"${attributeName}\"))\n            input[type=\"text\"]\n        `,\n        run: `edit ${customValue} && click .modal-body`,\n    };\n}\n\nfunction selectAndSetCustomAttribute(\n    productName, attributeName, attributeValue, customValue, attributeType='radio'\n) {\n    return [\n        selectAttribute(productName, attributeName, attributeValue, attributeType),\n        setCustomAttribute(productName, attributeName, customValue),\n    ];\n}\n\nfunction assertPriceTotal(total) {\n    return {\n        content: `Assert that the total is ${total}`,\n        trigger:\n            `table.o_sale_product_configurator_table tr>td[colspan=\"4\"] span:contains(\"${total}\")`,\n    };\n}\n\nfunction assertProductPrice(productName, price) {\n    return {\n        content: `Assert that ${productName} costs ${price}`,\n        trigger: `\n            ${productSelector(productName)}\n            td.o_sale_product_configurator_price\n            span:contains(\"${price}\")\n        `,\n    };\n}\n\nfunction assertOptionalProductPrice(productName, price) {\n    return {\n        content: `Assert that ${productName} costs ${price}`,\n        trigger: `\n            ${optionalProductSelector(productName)}\n            td.o_sale_product_configurator_qty\n            span:contains(\"${price}\")\n        `,\n    };\n}\n\nfunction assertProductPriceInfo(productName, priceInfo) {\n    return {\n        content: `Assert that the price info of ${productName} is ${priceInfo}`,\n        trigger: `\n            ${productSelector(productName)}\n            td.o_sale_product_configurator_price\n            div:contains(\"${priceInfo}\")\n        `,\n    };\n}\n\nfunction assertOptionalProductPriceInfo(productName, priceInfo) {\n    return {\n        content: `Assert that the price info of ${productName} is ${priceInfo}`,\n        trigger: `\n            ${optionalProductSelector(productName)}\n            td.o_sale_product_configurator_qty\n            div:contains(\"${priceInfo}\")\n        `,\n    };\n}\n\nfunction assertProductNameContains(productName) {\n    return {\n        content: `Assert that the product name contains ${productName}`,\n        trigger: productSelector(productName),\n    };\n}\n\nfunction assertFooterButtonsDisabled() {\n    return {\n        content: \"Assert that the footer buttons are disabled\",\n        trigger: 'footer.modal-footer button:disabled',\n    };\n}\n\nfunction saveConfigurator() {\n    return [\n        {\n            trigger: '.modal button:contains(Confirm)',\n            run: 'click',\n        }, {\n            content: \"Wait until the modal is closed\",\n            trigger: 'body:not(:has(.modal))',\n        }\n    ];\n}\n\nexport default {\n    productSelector,\n    optionalProductSelector,\n    optionalProductImageSrc,\n    addOptionalProduct,\n    removeOptionalProduct,\n    increaseProductQuantity,\n    setProductQuantity,\n    assertProductQuantity,\n    selectAttribute,\n    setCustomAttribute,\n    selectAndSetCustomAttribute,\n    assertPriceTotal,\n    assertProductPrice,\n    assertOptionalProductPrice,\n    assertProductPriceInfo,\n    assertOptionalProductPriceInfo,\n    assertProductNameContains,\n    assertFooterButtonsDisabled,\n    saveConfigurator,\n};\n", "/** @odoo-module **/\n\nfunction createNewSalesOrder() {\n    return [\n        {\n            trigger: '.o_sale_order',\n        }, {\n            content: \"Create new order\",\n            trigger: '.o_list_button_add',\n            run: 'click',\n        },\n    ]\n}\n\nfunction selectCustomer(customerName) {\n    return [\n        {\n            content: `Select customer ${customerName}`,\n            trigger: '.o_field_widget[name=partner_id] input',\n            run: `edit ${customerName}`,\n        },\n        {\n            trigger: `ul.ui-autocomplete > li > a:contains(\"${customerName}\")`,\n            run: 'click',\n        },\n    ];\n}\n\nfunction addProduct(productName, rowNumber=1) {\n    return [\n        {\n            content: `Add product ${productName}`,\n            trigger: 'a:contains(\"Add a product\")',\n            run: 'click',\n        },\n        {\n            content: 'wait for new row to be created',\n            trigger: `.o_data_row:nth-child(${rowNumber})`,\n        },\n        {\n            trigger: 'div[name=\"product_template_id\"] input',  // TODO VFE o_selected_row\n            run: `edit ${productName}`,\n        },\n        {\n            trigger: `ul.ui-autocomplete a:contains(\"${productName}\")`,\n            run: 'click',\n        },\n    ];\n}\n\nfunction clickSomewhereElse() {\n    return [\n        // TODO find a way for onchange to finish first ?\n        {\n            content: 'click somewhere else to exit cell focus',\n            trigger: 'a[name=order_lines]',  // click on notebook tab to stop the sol edit mode.\n            run: 'click',\n        },\n        {\n            content: 'check that the soline is not focused anymore',\n            trigger: 'table.o_section_and_note_list_view:not(:has(.o_selected_row))',\n        }\n    ]\n}\n\nfunction checkSOLDescriptionContains(productName, text) {\n    // currently must be called after exiting the edit mode on the SOL\n    // TODO in the future: handle edit mode and look directly into the textarea value\n    if (!text) {\n        return {\n            trigger: `span:contains(\"${productName}\")`,\n        }\n    }\n    return {\n        trigger: `span:contains(\"${productName}\") ~ textarea`,\n    }\n}\n\nfunction editLineMatching(productName, text) {\n    let base_step = checkSOLDescriptionContains(productName, text);\n    base_step['run'] = 'click';\n    return base_step;\n}\n\nfunction editConfiguration() {\n    return {\n        trigger: '[name=product_template_id] button.fa-pencil',\n        run: 'click',\n    }\n}\n\nexport default {\n    createNewSalesOrder,\n    selectCustomer,\n    addProduct,\n    checkSOLDescriptionContains,\n    editLineMatching,\n    editConfiguration,\n    clickSomewhereElse,\n};\n", "/** @odoo-module **/\nimport { registry } from \"@web/core/registry\";\n\nregistry.category(\"web_tour.tours\").add('test_detailed_op_no_save_1', {  steps: () => [\n    {\n        trigger: '.o_field_x2many_list_row_add > a',\n        run: \"click\",\n    },\n    {\n        trigger: \".o_field_widget[name=product_id] input\",\n        run: \"edit Lot\",\n    },\n    {\n        trigger: \".ui-menu-item > a:contains('Product Lot')\",\n        run: \"click\",\n    },\n    {\n        trigger: \".btn-primary[name=action_confirm]\",\n        run: \"click\",\n    },\n    {\n        trigger: \".fa-list\",\n        run: \"click\",\n    },\n    {\n        trigger: \"h4:contains('Stock move')\",\n        run: \"click\",\n    },\n    {\n        trigger: \".modal .o_field_x2many_list_row_add > a\",\n        run: \"click\",\n    },\n    {\n        trigger: \".modal .o_field_widget[name=lot_name] input\",\n        run: \"edit lot1\",\n    },\n    {\n        trigger: \".modal .o_field_widget[name=quantity] input\",\n        run: \"edit 4\",\n    },\n    {\n        trigger: \".modal button:contains(save)\",\n        run: \"click\",\n    },\n    {\n        trigger: \"body:not(:has(.modal))\",\n    },\n    {\n        trigger: \".o_optional_columns_dropdown_toggle\",\n        run: \"click\",\n    },\n    {\n        trigger: 'input[name=\"picked\"]',\n        content: 'Check the picked field to display the column on the list view.',\n        run: \"check\",\n    },\n    {\n        trigger: \".o_data_cell[name=picked]\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_field_widget[name=picked] input\",\n        run: \"check\",\n    },\n    {\n        trigger: \".btn-primary[name=button_validate]\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_control_panel_actions button:contains('Traceability')\",\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_generate_serial_1', {  steps: () => [\n    {\n        trigger: '.o_field_x2many_list_row_add > a',\n        run: \"click\",\n    },\n    {\n        trigger: \".o_field_widget[name=product_id] input\",\n        run: \"edit Serial\",\n    },\n    {\n        trigger: \".ui-menu-item > a:contains('Product Serial')\",\n        run: \"click\",\n    },\n    {\n        trigger: \".btn-primary[name=action_confirm]\",\n        run: \"click\",\n    },\n    {\n        trigger: \".fa-list\",\n        run: \"click\",\n    },\n    {\n        trigger: \"h4:contains('Stock move')\",\n        run: \"click\",\n    },\n    {\n        trigger: '.o_widget_generate_serials > button',\n        run: \"click\",\n    },\n    {\n        trigger: \".modal h4:contains('Generate Serial numbers')\",\n        run: \"click\",\n    },\n    {\n        trigger: \".modal div[name=next_serial] input\",\n        run: \"edit serial_n_1\",\n    },\n    {\n        trigger: \".modal div[name=next_serial_count] input\",\n        run: \"edit 5 && click body\",\n    },\n    {\n        trigger: \".modal .btn-primary:contains('Generate')\",\n        run: \"click\",\n    },\n    {\n        trigger: \"span[data-tooltip=Quantity]:contains('5')\",\n        run: () => {\n            const nbLines = document.querySelectorAll(\".o_field_cell[name=lot_name]\").length;\n            if (nbLines !== 5){\n                console.error(\"wrong number of move lines generated. \" + nbLines + \" instead of 5\");\n            }\n        },\n    },\n    {\n        trigger: \".modal button:contains(save)\",\n        run: \"click\",\n    },\n    {\n        trigger: \"body:not(:has(.modal))\",\n    },\n    {\n        trigger: \".o_optional_columns_dropdown_toggle\",\n        run: \"click\",\n    },\n    {\n        trigger: 'input[name=\"picked\"]',\n        content: 'Check the picked field to display the column on the list view.',\n        run: function (actions) {\n            if (!this.anchor.checked) {\n                actions.click();\n            }\n        },\n    },\n    {\n        trigger: \".o_data_cell[name=picked]\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_field_widget[name=picked] input\",\n        run: function (actions) {\n            if (!this.anchor.checked) {\n                actions.click();\n            }\n        }\n    },\n    {\n        trigger: \".btn-primary[name=button_validate]\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_control_panel_actions button:contains('Traceability')\",\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_generate_serial_2', {  steps: () => [\n    {\n        trigger: '.o_field_x2many_list_row_add > a',\n        run: \"click\",\n    },\n    {\n        trigger: \".o_field_widget[name=product_id] input\",\n        run: \"edit Lot\",\n    },\n    {\n        trigger: \".ui-menu-item > a:contains('Product Lot 1')\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_field_widget[name=product_uom_qty] input\",\n        run: \"edit 100\",\n    },\n    {\n        trigger: \".btn-primary[name=action_confirm]\",\n        run: \"click\",\n    },\n    {\n        trigger: \".fa-list\",\n        run: \"click\",\n    },\n    {\n        trigger: \".modal h4:contains('Stock move')\",\n        run: \"click\",\n    },\n    // We generate lots for a first batch of 50 products\n    {\n        trigger: \".modal .o_widget_generate_serials > button\",\n        run: \"click\",\n    },\n    {\n        trigger: \".modal h4:contains('Generate Lot numbers')\",\n        run: \"click\",\n    },\n    {\n        trigger: \".modal div[name=next_serial] input\",\n        run: \"edit lot_n_1_1\",\n    },\n    {\n        trigger: \".modal div[name=next_serial_count] input\",\n        run: \"edit 7.5\",\n    },\n    {\n        trigger: \".modal div[name=total_received] input\",\n        run: \"edit 50\",\n    },\n    {\n        trigger: \".modal .modal-footer button.btn-primary:contains(Generate)\",\n        run: \"click\",\n    },\n    {\n        trigger: \".modal span[data-tooltip=Quantity]:contains(50)\",\n        run: () => {\n            const nbLines = document.querySelectorAll(\".o_field_cell[name=lot_name]\").length;\n            if (nbLines !== 7){\n                console.error(\"wrong number of move lines generated. \" + nbLines + \" instead of 7\");\n            }\n        },\n    },\n    // We generate lots for the last 50 products\n    {\n        trigger: \".modal .o_widget_generate_serials > button\",\n        run: \"click\",\n    },\n    {\n        trigger: \".modal h4:contains('Generate Lot numbers')\",\n    },\n    {\n        trigger: \".modal div[name=next_serial] input\",\n        run: \"edit lot_n_2_1\",\n    },\n    {\n        trigger: \".modal div[name=next_serial_count] input\",\n        run: \"edit 13\",\n    },\n    {\n        trigger: \".modal div[name=total_received] input\",\n        run: \"edit 50\",\n    },\n    {\n        trigger: \".modal div[name=keep_lines] input\",\n        run: \"check\",\n    },\n    {\n        trigger: \".modal .modal-footer button.btn-primary:contains(Generate)\",\n        run: \"click\",\n    },\n    {\n        trigger: \".modal span[data-tooltip=Quantity]:contains(100)\",\n        run: () => {\n            const nbLines = document.querySelectorAll(\".o_field_cell[name=lot_name]\").length;\n            if (nbLines !== 11){\n                console.error(\"wrong number of move lines generated. \" + nbLines + \" instead of 11\");\n            }\n        },\n    },\n    {\n        trigger: \".modal .o_form_button_save\",\n        run: \"click\",\n    },\n    {\n        trigger: \"body:not(:has(.modal))\",\n    },\n    {\n        trigger: \".o_optional_columns_dropdown_toggle\",\n        run: \"click\",\n    },\n    {\n        trigger: \"input[name='picked']\",\n        content: \"Check the picked field to display the column on the list view.\",\n        run: function (actions) {\n            if (!this.anchor.checked) {\n                actions.click();\n            }\n        },\n    },\n    {\n        trigger: \".o_data_cell[name=picked]\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_field_widget[name=picked] input\",\n        run: function (actions) {\n            if (!this.anchor.checked) {\n                actions.click();\n            }\n        }\n    },\n    {\n        trigger: \".btn-primary[name=button_validate]\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_control_panel_actions button:contains('Traceability')\",\n    },\n]});\n\nregistry.category('web_tour.tours').add('test_inventory_adjustment_apply_all', {  steps: () => [\n    {\n        trigger: '.o_list_button_add',\n        run: \"click\",\n    },\n    {\n        trigger: 'div[name=product_id] input',\n        run: \"edit Product 1\",\n    },\n    {\n        trigger: '.ui-menu-item > a:contains(\"Product 1\")',\n        run: \"click\",\n    },\n    {\n        trigger: 'div[name=inventory_quantity] input',\n        run: \"edit 123\",\n    },\n    // Unfocus to show the \"New\" button again\n    {\n        trigger: '.o_searchview_input_container',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_list_button_add',\n        run: \"click\",\n    },\n    {\n        trigger: 'div[name=product_id] input',\n        run: \"edit Product 2\",\n    },\n    {\n        trigger: '.ui-menu-item > a:contains(\"Product 2\")',\n        run: \"click\",\n    },\n    {\n        trigger: 'div[name=inventory_quantity] input',\n        run: \"edit 456\",\n    },\n    {\n        trigger: 'button[name=action_apply_all]',\n        run: \"click\",\n    },\n    {\n        trigger: '.modal-content button[name=action_apply]',\n        run: \"click\",\n    },\n    {\n        trigger: \"body:not(:has(.modal))\",\n    },\n    {\n        trigger: '.o_searchview_input_container',\n        run: () => {\n            const applyButtons = document.querySelectorAll('button[name=action_apply_inventory]');\n            if (applyButtons.length > 0){\n                console.error('Not all quants were applied!');\n            }\n        },\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_add_new_line', {\n    steps: () => [\n        {\n            trigger: \".o_form_editable\",\n        },\n        {\n            trigger: '.o_field_x2many_list_row_add > a',\n            run: \"click\",\n        },\n        {\n            trigger: \".o_field_widget[name=product_id] input\",\n            run: 'edit two',\n        },\n        {\n            trigger: \".ui-menu-item > a:contains('Product two')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".fa-list:eq(1)\",\n            run: \"click\",\n        },\n        {\n            trigger: \"h4:contains('Stock move')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal .o_field_x2many_list_row_add > a\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal .o_field_widget[name=lot_name] input\",\n            run: 'edit two',\n        },\n        {\n            trigger: \".modal .o_form_view.modal-content .o_form_button_save\",\n            run: \"click\",\n        },\n        {\n            trigger: \"body:not(:has(.modal))\",\n        },\n        {\n            trigger: \".o_form_view .o_form_button_save\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_renderer.o_form_saved\",\n        },\n    ]\n});\n\nregistry.category(\"web_tour.tours\").add(\"test_edit_existing_line\", {\n    steps: () => [\n        {\n            trigger: \".o_data_cell[name=quantity]\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_field_widget[name=quantity] input\",\n            run: \"edit 2\",\n        },\n        {\n            trigger: \".fa-list\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal h4:contains(Stock move)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal .o_data_cell[name=quantity]:eq(1)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal .o_field_widget[name=lot_name] input\",\n            run: \"edit two\",\n        },\n        {\n            trigger: \".modal .o_form_view.modal-content .o_form_button_save:enabled\",\n            run: \"click\",\n        },\n        {\n            content: \"wait the modal is totally closed before click on save\",\n            trigger: \"body:not(:has(.modal))\",\n        },\n        {\n            trigger: \".o_form_view .o_form_button_save\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_renderer.o_form_saved\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add('test_edit_existing_lines_2', {\n    steps: () => [\n        { trigger: \".o_data_row:has(.o_data_cell[data-tooltip='Product a']) .fa-list\", run: 'click'},\n        { trigger: \".o_data_cell[name=lot_name]\", run: 'click' },\n        { trigger: \".o_data_cell[name=lot_name] input\", run: 'edit SNa001'},\n        { trigger: \".o_form_view.modal-content .o_form_button_save\", run: 'click'},\n        { trigger: \"body:not(:has(div .modal-content))\"},\n        { trigger: \".o_data_row:has(.o_data_cell[data-tooltip='Product b']) .fa-list\", run: 'click' },\n        { trigger: \".o_data_cell[name=lot_name]\", run: 'click' },\n        { trigger: \".o_data_cell[name=lot_name] input\", run: 'edit SNb001'},\n        { trigger: \".o_form_view.modal-content .o_form_button_save\", run: 'click'},\n        { trigger: \"body:not(:has(div .modal-content))\"},\n        { trigger: \".o_form_view:not(.modal-content) .o_form_button_save\", run: 'click' },\n        { trigger: \".o_form_renderer.o_form_saved\" },\n    ]\n});\n\nregistry.category(\"web_tour.tours\").add('test_onchange_twice_lot_ids', {\n    steps: () => [\n        {\n            trigger: \".o_optional_columns_dropdown_toggle\",\n            run: \"click\",\n        },\n        {\n            trigger: \".dropdown-item:contains('Serial Numbers')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_data_cell.o_many2many_tags_cell\",\n            run: \"click\",\n        },\n        {\n            trigger: \".oi-close:first\",\n            run: \"click\",\n        },\n        {\n            trigger: \".oi-close:first\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_button_save\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_renderer.o_form_saved\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"test_add_new_line_in_detailled_op\", {\n    steps: () => [\n        {\n            trigger: \".o_list_view.o_field_x2many .o_data_row button[name='Open Move']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-content\",\n        },\n        {\n            trigger: \".modal-content .o_field_x2many_list_row_add > a\",\n            run: \"click\",\n        },\n        {\n            content: \"Pick LOT001 to create a move line with a quantity of 0.00\",\n            trigger: \".o_data_row .o_data_cell[name=lot_id]:contains(LOT001)\",\n            run: \"click\",\n        },\n        {\n            content: \"check that the move contains three lines\",\n            trigger:\n                \".modal-content:has(.modal-header .modal-title:contains(Open: Stock move)) .o_data_row:nth-child(3)\",\n        },\n        {\n            trigger: \".modal-header .modal-title:contains(Open: Stock move)\",\n            run: \"click\",\n        },\n        {\n            content: \"Check that the first line is associated with LOT001 for a quantity of 0.00\",\n            trigger:\n                \".modal-content .o_data_row:nth-child(1):has(.o_data_cell[name=quant_id]:contains(WH/Stock - LOT001)):has(.o_data_cell[name=quantity]:contains(0.00))\",\n        },\n        {\n            trigger: \".modal-content .o_field_x2many_list_row_add > a\",\n            run: \"click\",\n        },\n        {\n            content: \"LOT001 should not appear as it is not available\",\n            trigger: \".modal-header .modal-title:contains(Add line: Product Lot)\",\n            run: () => {\n                const lines = document.querySelectorAll(\".o_data_row .o_data_cell[name=lot_id]\");\n                if (lines.length !== 2) {\n                    console.error(\n                        \"Wrong number of available quants: \" + lines.length + \" instead of 2.\"\n                    );\n                }\n                const lineLOT001 = Array.from(lines).filter((line) =>\n                    line.textContent.includes(\"LOT001\")\n                );\n                if (lineLOT001.length) {\n                    console.error(\"LOT001 shoudld not be displayed as unavailable.\");\n                }\n            },\n        },\n        {\n            content: \"Cancel the move line creation\",\n            trigger: \".modal-header:has(.modal-title:contains(Add line: Product Lot)) .btn-close\",\n            run: \"click\",\n        },\n        {\n            content: \"Remove the newly created line\",\n            trigger:\n                \".modal-content .o_data_row:nth-child(1):has(.o_data_cell[name=quant_id]:contains(WH/Stock - LOT001)):has(.o_data_cell[name=quantity]:contains(0.00)) .o_list_record_remove\",\n            run: \"click\",\n        },\n        {\n            content: \"check that the move contains two lines\",\n            trigger:\n                \".modal-content:has(.modal-header .modal-title:contains(Open: Stock move)):not(:has(.o_data_row:nth-child(3)))\",\n        },\n        {\n            content: \"Check that the first line is associated with LOT001\",\n            trigger:\n                \".modal-content .o_data_row:nth-child(1) .o_data_cell[name=quant_id]:contains(WH/Stock - LOT001)\",\n        },\n        {\n            content: \"Check that the second line is associated with LOT002\",\n            trigger:\n                \".modal-content .o_data_row:nth-child(2) .o_data_cell[name=quant_id]:contains(WH/Stock - LOT002)\",\n        },\n        {\n            content: \"Modify the quant associated to the second line to fully use LOT003\",\n            trigger: \".modal-content .o_data_row:nth-child(2) .o_data_cell[name=quant_id]\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-content .o_data_row:nth-child(2) .o_field_widget[name=quant_id] input\",\n            run: \"edit LOT003\",\n        },\n        {\n            trigger: \".dropdown-item:contains(LOT003)\",\n            run: \"click\",\n        },\n        {\n            content: \"Modify the quantity of the first line from 10 to 8\",\n            trigger: \".modal-content .o_data_row:nth-child(1) .o_data_cell[name=quantity]\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-content .o_data_row:nth-child(1) .o_data_cell[name=quantity] .o_input\",\n            run: \"edit 8\",\n        },\n        {\n            trigger: \".modal-header .modal-title:contains(Open: Stock move)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-content .o_list_number:contains(18.00)\",\n        },\n        {\n            trigger: \".modal-content .o_field_x2many_list_row_add > a\",\n            run: \"click\",\n        },\n        {\n            content: \"LOT003 should not appear as it is not available\",\n            trigger: \".modal-header .modal-title:contains(Add line: Product Lot)\",\n            run: () => {\n                const lines = document.querySelectorAll(\".o_data_row .o_data_cell[name=lot_id]\");\n                if (lines.length !== 2) {\n                    console.error(\n                        \"Wrong number of available quants: \" + lines.length + \" instead of 2.\"\n                    );\n                }\n                const lineLOT003 = Array.from(lines).filter((line) =>\n                    line.textContent.includes(\"LOT003\")\n                );\n                if (lineLOT003.length) {\n                    console.error(\"LOT003 shoudld not be displayed as unavailable.\");\n                }\n            },\n        },\n        {\n            content: \"Pick LOT001 to create a move line with a quantity of 2.00\",\n            trigger: \".o_data_row .o_data_cell[name=lot_id]:contains(LOT001)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-content .o_list_number:contains(20.00)\",\n        },\n        {\n            trigger: \".modal-header .modal-title:contains(Open: Stock move)\",\n            run: \"click\",\n        },\n        {\n            content: \"Check that 2 units of LOT001 were added\",\n            trigger:\n                \".o_data_row:has(.o_data_cell[name=quant_id]:contains(WH/Stock - LOT001)) .o_data_cell[name=quantity]:contains(2.00)\",\n        },\n        {\n            content: \"Check that the third line is associated with LOT003\",\n            trigger:\n                \".modal-content .o_data_row:nth-child(3) .o_data_cell[name=quant_id]:contains(WH/Stock - LOT003)\",\n        },\n        {\n            content: \"Modify the quant associated to the third line to use LOT002\",\n            trigger: \".modal-content .o_data_row:nth-child(3) .o_data_cell[name=quant_id]\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-content .o_data_row:nth-child(3) .o_field_widget[name=quant_id] input\",\n            run: \"edit LOT002\",\n        },\n        {\n            trigger: \".dropdown-item:contains(LOT002)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-header .modal-title:contains(Open: Stock move)\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".modal-content .o_data_row:nth-child(3) .o_data_cell[name=quant_id]:contains(LOT002)\",\n        },\n        {\n            content: \"Modify the quantity of the first line from 10 to 15 to change the demand\",\n            trigger: \".modal-content .o_data_row:nth-child(3) .o_data_cell[name=quantity]\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-content .o_data_row:nth-child(3) .o_data_cell[name=quantity] .o_input\",\n            run: \"edit 15\",\n        },\n        {\n            trigger: \".modal-header .modal-title:contains(Open: Stock move)\",\n            run: \"click\",\n        },\n        {\n            content: \"Remove the LOT001 line with a quantity of 8.00\",\n            trigger:\n                \".o_data_row:has(.o_data_cell[name=quantity]:contains(8.00)) .o_list_record_remove\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-content .o_list_number:contains(17.00)\",\n        },\n        {\n            trigger: \".modal-content .o_field_x2many_list_row_add > a\",\n            run: \"click\",\n        },\n        {\n            content: \"LOT002 should not appear as it is not available\",\n            trigger: \".modal-header .modal-title:contains(Add line: Product Lot)\",\n            run: () => {\n                const lines = document.querySelectorAll(\".o_data_row .o_data_cell[name=lot_id]\");\n                if (lines.length !== 2) {\n                    console.error(\n                        \"Wrong number of available quants: \" + lines.length + \" instead of 2.\"\n                    );\n                }\n                const lineLOT002 = Array.from(lines).filter((line) =>\n                    line.textContent.includes(\"LOT002\")\n                );\n                if (lineLOT002.length) {\n                    console.error(\"LOT002 shoudld not be displayed as unavailable.\");\n                }\n            },\n        },\n        {\n            content: \"Pick LOT001 to create move line to fullfill the demand of 3\",\n            trigger: \".o_data_row .o_data_cell[name=lot_id]:contains(LOT001)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-content .o_list_number:contains(20.00)\",\n        },\n        {\n            trigger: \".modal-header .modal-title:contains(Open: Stock move)\",\n            run: \"click\",\n        },\n        {\n            content: \"Check that 3 units of LOT001 were added\",\n            trigger:\n                \".o_data_row:has(.o_data_cell[name=quant_id]:contains(WH/Stock - LOT001)) .o_data_cell[name=quantity]:contains(3.00)\",\n        },\n        {\n            trigger: \".modal-content .o_form_button_save\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_button_save\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_renderer.o_form_saved\",\n        },\n    ],\n});\n", "/** @odoo-module **/\n\nimport { registry } from \"@web/core/registry\";\n\n    registry.category(\"web_tour.tours\").add('test_stock_route_diagram_report', {\n        steps: () => [\n        {\n            trigger: \".o_breadcrumb\",\n        },\n    {\n        trigger: '.o_kanban_record',\n        run: \"click\",\n    },\n    {\n        trigger: '.nav-item > a:contains(\"Inventory\")',\n        run: \"click\",\n    },\n    {\n        trigger: '.btn[id=\"stock.view_diagram_button\"]',\n        run: \"click\",\n    },\n    {\n        trigger: ':iframe .o_report_stock_rule',\n    },\n    ],\n    });\n\nregistry.category(\"web_tour.tours\").add(\"test_context_from_warehouse_filter\", {\n    steps: () => [\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: \"edit 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: \"edit warehouse\",\n        },\n        {\n            trigger: \".o_menu_item.dropdown-item:contains(Search Warehouse for:) a.o_expand > i\",\n            run: \"click\",\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: \"edit warehouse\",\n        },\n        {\n            trigger: \".o_menu_item.dropdown-item:contains(Search Warehouse for:) a.o_expand > i\",\n            run: \"click\",\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: \".o_kanban_record:has(span:contains(Lovely Product))\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n            run: () => {\n                if (!document.querySelector(\"button[name=action_product_tmpl_forecast_report]\")) {\n                    const panelButtons = document.querySelectorAll(\n                        \".o_control_panel_actions button\"\n                    );\n                    const moreButton = Array.from(panelButtons).find(\n                        (button) => button.textContent.trim() == \"More\"\n                    );\n                    moreButton.click();\n                }\n            },\n        },\n        {\n            trigger: \"button[name=action_product_tmpl_forecast_report]\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_graph_view\",\n        },\n    ],\n});\n", "/** @odoo-module */\nimport { registry } from \"@web/core/registry\";\n\nregistry.category(\"web_tour.tours\").add(\"web_enterprise.test_studio_list_upsell\", {\n    steps: () => [\n        {\n            trigger: \".o_list_view\",\n        },\n        {\n            trigger: \".o_optional_columns_dropdown > button\",\n            run: \"click\",\n        },\n        {\n            trigger: \" .o-dropdown--menu .dropdown-item-studio\",\n        },\n    ],\n});\n", "/** @odoo-module */\n\nexport function assertEqual(actual, expected) {\n    if (actual !== expected) {\n        throw new Error(`Assert failed: expected: ${expected} ; got: ${actual}`);\n    }\n}\n\nexport async function nextTick() {\n    await new Promise((resolve) => setTimeout(resolve));\n    await new Promise((resolve) => requestAnimationFrame(resolve));\n}\n\nexport function stepNextTick() {\n    // FIXME: this probably should be handled by the tour-as-macro infrastructure\n    // The case is to have a dom node with a constant selector. It will trigger the next step\n    // even though the element will be changed after, resulting in non-deterministic bugs.\n    // This is especially the case in the case of inputs, where we'd want to assert their value changed\n    return {\n        trigger: \"body\",\n        run() {\n            return nextTick();\n        },\n    };\n}\n\nexport function stepNotInStudio(trigger) {\n    return [\n        {\n            trigger: \"body:not(:has(.o_studio))\",\n        },\n        {\n            trigger: trigger || \"body\",\n        },\n    ];\n}\n", "/** @odoo-module */\n\nimport { _t } from \"@web/core/l10n/translation\";\nimport { registry } from \"@web/core/registry\";\nimport { markup } from \"@odoo/owl\";\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_home_menu_background_tour\", {\n    url: \"/odoo\",\n    steps: () => [\n        {\n            isActive: [\"auto\"],\n            trigger: \".o_home_menu_background\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item\",\n            content: markup(\n                _t(\"Want to customize the background? Let\u2019s activate <b>Odoo Studio</b>.\")\n            ),\n            tooltipPosition: \"bottom\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_change_background\",\n            content: markup(_t(\"Change the <b>background</b>, make it yours.\")),\n            tooltipPosition: \"bottom\",\n            run: \"click\",\n        },\n    ],\n});\n", "/** @odoo-module */\n\nimport { browser } from \"@web/core/browser/browser\";\nimport { registry } from \"@web/core/registry\";\nimport { randomString } from \"@web_studio/utils\";\nimport {\n    assertEqual,\n    stepNextTick,\n    stepNotInStudio,\n} from \"@web_studio/../tests/tours/tour_helpers\";\n\nconst localStorage = browser.localStorage;\nlet createdAppString = null;\nlet createdMenuString = null;\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_main_and_rename\", {\n    url: \"/odoo?debug=1\",\n    steps: () => [\n        {\n            trigger: \".o_home_menu_background\",\n        },\n        {\n            // open studio\n            trigger: \".o_main_navbar .o_web_studio_navbar_item\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_new_app\",\n            run: \"click\",\n        },\n        {\n            // the next steps are here to create a new app\n            trigger: \".o_web_studio_app_creator_next\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_app_creator_name > input\",\n            run: `edit ${(createdAppString = randomString(6))}`,\n        },\n        {\n            trigger: \".o_web_studio_app_creator_next.is_ready\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_menu_creator > input\",\n            run: `edit ${(createdMenuString = randomString(6))}`,\n        },\n        {\n            trigger: \".o_web_studio_app_creator_next.is_ready\",\n            run: \"click\",\n        },\n        {\n            // disable chatter in model configurator, we'll test adding it on later\n            trigger: 'input[name=\"use_mail\"]',\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_model_configurator_next\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_menu_toggle:not(.o_menu_toggle_back)\",\n        },\n        {\n            // toggle the home menu outside of studio and come back in studio\n            trigger: \".o_web_studio_leave > a.btn\",\n            timeout: 60000 /* previous step reloads registry, etc. - could take a long time */,\n            run: \"click\",\n        },\n        {\n            trigger: `.o_web_client:not(.o_in_studio)` /* wait to be out of studio */,\n        },\n        {\n            trigger: \".o_menu_toggle:not(.o_menu_toggle_back)\",\n            timeout: 60000 /* previous step reloads registry, etc. - could take a long time */,\n            run: \"click\",\n        },\n        {\n            trigger: \".o_home_menu_background\",\n        },\n        {\n            trigger: \".o_main_navbar .o_web_studio_navbar_item\",\n            run: \"click\",\n        },\n        {\n            // open the app creator and leave it\n            trigger: \".o_web_studio_new_app\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_app_creator\",\n        },\n        {\n            trigger: \".o_web_studio_leave > a.btn\",\n            run: \"click\",\n        },\n        {\n            // go back to the previous app\n            trigger: \".o_home_menu\",\n            run: \"press Escape\",\n        },\n        {\n            trigger: `.o_web_client:not(.o_in_studio) .o_menu_brand:contains(${createdAppString})`,\n        },\n        {\n            // this should open the previous app outside of studio\n            // go back to the home menu\n            trigger: \".o_menu_toggle:not(.o_menu_toggle_back)\",\n            run: \"click\",\n        },\n        {\n            trigger: \"input.o_search_hidden\",\n            // Open Command Palette\n            run: `edit ${createdMenuString[0]}`,\n        },\n        {\n            trigger: \".o_command_palette_search input\",\n            run: `edit /${createdMenuString}`,\n        },\n        {\n            trigger: `.o_command.focused:contains(${createdAppString} / ${createdMenuString})`,\n        },\n        {\n            // search results should have been updated\n            trigger: \".o_command_palette\",\n            // Close the Command Palette\n            run: \"press Escape\",\n        },\n        {\n            // enter Studio\n            trigger: \".o_main_navbar .o_web_studio_navbar_item\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_studio_home_menu\",\n        },\n        {\n            // edit an app\n            trigger: `.o_app[data-menu-xmlid*=\"studio\"]:contains(${createdAppString})`,\n            run: function () {\n                // We can't emulate a hover to display the edit icon\n                const editIcon = this.anchor.querySelector(\".o_web_studio_edit_icon\");\n                editIcon.style.visibility = \"visible\";\n                editIcon.click();\n            },\n        },\n        {\n            // design the icon\n            // TODO: we initially tested this (change an app icon) at the end but a\n            // long-standing bug (KeyError: ir.ui.menu.display_name, caused by a registry\n            // issue with multiple workers) on runbot prevent us from doing it. It thus have\n            // been moved at the beginning of this test to avoid the registry to be reloaded\n            // before the write on ir.ui.menu.\n            trigger: \".o_web_studio_selector_background > button\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-dropdown--menu .o_web_studio_selector_value\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-footer .btn.btn-primary\",\n            run: \"click\",\n        },\n        {\n            // click on the created app\n            trigger: `.o_app[data-menu-xmlid*=\"studio\"]:contains(${createdAppString})`,\n            run: \"click\",\n        },\n        {\n            // create a new menu\n            trigger: \".o_main_navbar .o_web_edit_menu\",\n            run: \"click\",\n        },\n        {\n            trigger: \"footer.modal-footer .js_add_menu\",\n            run: \"click\",\n        },\n        {\n            trigger: 'input[name=\"menuName\"]',\n            run: `edit ${(createdMenuString = randomString(6))}`,\n        },\n        {\n            trigger: 'div.o_web_studio_menu_creator_model_choice input[value=\"existing\"]',\n            run: \"click\",\n        },\n        {\n            trigger: \"div.o_web_studio_menu_creator_model .o_record_selector input\",\n            run: \"edit a\",\n        },\n        {\n            trigger:\n                \".o_record_selector .o-autocomplete--dropdown-menu > li > a:not(:has(.fa-spin))\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_record_selector :not(.o-autocomplete dropdown-menu)\",\n        },\n        {\n            trigger: \".o_web_studio_add_menu_modal button:contains(Confirm):not(.disabled)\",\n            run: \"click\",\n        },\n        {\n            trigger: \":not(.o_inactive_modal) .o-web-studio-appmenu-editor\",\n        },\n        {\n            trigger: \".o-web-studio-appmenu-editor button:contains(Confirm):not(.disabled)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_menu\",\n        },\n        {\n            // check that the Studio menu is still there\n            // switch to form view\n            trigger: '.o_web_studio_views_icons > a[title=\"Form\"]',\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor\",\n        },\n        {\n            // wait for the form editor to be rendered because the sidebar is the same\n            // unfold 'Existing Fieldqs' section\n            trigger: \".o_web_studio_existing_fields_header\",\n            run: \"click\",\n        },\n        {\n            // add an new field\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_field_type_container:eq(1) .o_web_studio_field_char\",\n            run: \"drag_and_drop .o_web_studio_form_view_editor .o_inner_group\",\n        },\n        {\n            trigger: '.o_web_studio_sidebar input[name=\"technical_name\"]',\n        },\n        {\n            // click on the field\n            trigger: \".o_web_studio_form_view_editor .o_wrap_label:first label\",\n            // when it's there\n            run: \"click\",\n        },\n        {\n            // rename the label\n            trigger: '.o_web_studio_sidebar input[name=\"string\"]',\n            run: \"edit My Coucou Field && click .o_web_studio_sidebar\",\n        },\n        stepNextTick(),\n        {\n            // verify that the field name has changed and change it\n            trigger: '.o_web_studio_sidebar input[name=\"technical_name\"]',\n            async run(helper) {\n                assertEqual(this.anchor.value, \"my_coucou_field\");\n                await helper.edit(\"coucou\");\n                await helper.click(\".o_web_studio_sidebar\");\n            },\n            // the rename operation (/web_studio/rename_field + /web_studio/edit_view)\n            // takes a while and sometimes reaches the default 10s timeout\n            timeout: 20000,\n        },\n        {\n            // click on \"Add\" tab\n            trigger: \".o_web_studio_sidebar .o_web_studio_new\",\n            // the rename operation (/web_studio/rename_field + /web_studio/edit_view)\n            // takes a while and sometimes reaches the default 10s timeout\n            timeout: 20000,\n            run: \"click\",\n        },\n        {\n            // add a new field\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_field_type_container:eq(1) .o_web_studio_field_char\",\n            run: \"drag_and_drop .o_web_studio_form_view_editor .o_inner_group\",\n        },\n        {\n            // rename the field with the same name\n            trigger: '.o_web_studio_sidebar input[name=\"technical_name\"]',\n            run: \"edit coucou && click body\",\n        },\n        {\n            // an alert dialog should be opened\n            trigger: \".modal-footer > button:first\",\n            run: \"click\",\n        },\n        {\n            // rename the label\n            trigger: '.o_web_studio_sidebar input[name=\"string\"]',\n            run: \"edit COUCOU && click body\",\n        },\n        stepNextTick(),\n        {\n            // verify that the field name has changed (post-fixed by _1)\n            trigger: '.o_web_studio_sidebar input[name=\"technical_name\"]',\n            run(helper) {\n                assertEqual(this.anchor.value, \"coucou_1\");\n            },\n            // the rename operation (/web_studio/rename_field + /web_studio/edit_view)\n            // takes a while and sometimes reaches the default 10s timeout\n            timeout: 20000,\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_new\",\n            run: \"click\",\n        },\n        {\n            // add a monetary field --> create a currency field\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_field_type_container:eq(1) .o_web_studio_field_monetary\",\n            run: \"drag_and_drop (.o_inner_group:first .o_web_studio_hook:eq(1))\",\n        },\n        {\n            // verify that the monetary field is in the view\n            trigger:\n                '.o_web_studio_form_view_editor .o_wrap_label:eq(1) label:contains(\"New Monetary\")',\n        },\n        {\n            // switch the two first fields\n            trigger: \".o_web_studio_form_view_editor .o_inner_group:first .o-draggable:eq(1)\",\n            run: \"drag_and_drop .o_inner_group:first .o_web_studio_hook:first\",\n        },\n        {\n            // click on \"Add\" tab\n            trigger:\n                '.o_web_studio_form_view_editor .o_wrap_label:eq(0) label:contains(\"New Monetary\")',\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_new\",\n            run: \"click\",\n        },\n        {\n            // verify that the fields have been switched\n            trigger:\n                '.o_web_studio_form_view_editor .o_wrap_label:eq(0) label:contains(\"New Monetary\")',\n        },\n        {\n            // add a m2m field\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_field_type_container:eq(1) .o_web_studio_field_many2many\",\n            run: \"drag_and_drop .o_inner_group:first .o_web_studio_hook:first\",\n        },\n        {\n            // type something in the modal\n            trigger:\n                '.modal:not(.o_inactive_modal) [name=\"relation_id\"] input.o-autocomplete--input',\n            // we are sure \"Activity\" exists since studio depends on mail.\n            //Also, it is determinisic and field names should not conflict too much.\n            run: \"fill mail.activity\",\n        },\n        {\n            // select Activity as model\n            trigger:\n                '.modal:not(.o_inactive_modal) [name=\"relation_id\"] .o-autocomplete--dropdown-menu li a:not(:has(.fa-spin)):contains(Activity)',\n            run: \"click\",\n        },\n        {\n            trigger: \".modal:not(.o_inactive_modal) button:contains(Confirm):not(.disabled)\",\n            run: \"click\",\n        },\n        {\n            // select the m2m to set its properties\n            trigger: \".o_wrap_input:has(.o_field_many2many)\",\n            timeout: 15000, // creating M2M relations can take some time...\n            run: \"click\",\n        },\n        {\n            // change the `widget` attribute\n            trigger: '.o_web_studio_sidebar [name=\"widget\"] .o_select_menu_toggler_slot',\n            run: \"click\",\n        },\n        {\n            trigger: \".o-dropdown--menu .o_select_menu_item_label:contains('(many2many_tags)')\",\n            run: \"click\",\n        },\n        {\n            // use colors on the m2m tags\n            trigger: '.o_web_studio_sidebar [name=\"color_field\"]',\n            run: \"click\",\n        },\n        {\n            // add a statusbar\n            trigger: \".o_web_studio_statusbar_hook\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-footer .btn.btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_statusbar_status\",\n            run: \"click\",\n        },\n        {\n            // verify that a default value has been set for the statusbar\n            trigger:\n                '.o_web_studio_sidebar [name=\"default_value\"] .o_select_menu_toggler_slot:contains(First Status)',\n        },\n        {\n            trigger: \".o_web_studio_views_icons a[aria-label=Form]\",\n            run: \"click\",\n        },\n        {\n            // verify Chatter can be added after changing view to form\n            trigger: \".o_web_studio_add_chatter\",\n        },\n        {\n            // edit action\n            trigger: \".o_web_studio_menu .o_menu_sections li a:contains(Views)\",\n            run: \"click\",\n        },\n        {\n            // edit form view\n            trigger:\n                \".o_web_studio_view_category .o_web_studio_thumbnail_item.o_web_studio_thumbnail_form\",\n            run: \"click\",\n        },\n        {\n            // verify Chatter can be added after changing view to form\n            trigger: \".o_web_studio_add_chatter\",\n        },\n        {\n            // switch in list view\n            trigger: '.o_web_studio_menu .o_web_studio_views_icons a[title=\"List\"]',\n            run: \"click\",\n        },\n        {\n            // wait for the list editor to be rendered because the sidebar is the same\n            trigger: \".o_web_studio_list_view_editor\",\n        },\n        {\n            // unfold 'Existing Fieldqs' section\n            trigger: \".o_web_studio_existing_fields_header\",\n            run: \"click\",\n        },\n        {\n            // add an existing field (display_name)\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_existing_fields_section .o_web_studio_field_char:contains(COUCOU)\",\n            run: \"drag_and_drop .o_web_studio_list_view_editor th.o_web_studio_hook:first\",\n        },\n        {\n            // verify that the field is correctly named\n            trigger: '.o_web_studio_list_view_editor th:contains(\"COUCOU\")',\n        },\n        {\n            // leave Studio\n            trigger: \".o_web_studio_leave > a.btn\",\n            run: \"click\",\n        },\n        {\n            // come back to the home menu to check if the menu data have changed\n            trigger: \".o_web_client:not(.o_in_studio)\",\n        },\n        {\n            trigger: \".o_menu_toggle:not(.o_menu_toggle_back)\",\n            run: \"click\",\n        },\n        {\n            trigger: \"input.o_search_hidden\",\n            // Open Command Palette\n            run: `edit ${createdMenuString[0]}`,\n        },\n        {\n            trigger: \".o_command_palette_search input\",\n            run: `edit /${createdMenuString}`,\n        },\n        {\n            // search results should have been updated\n            trigger: `.o_command.focused:contains(${createdAppString} / ${createdMenuString})`,\n        },\n        {\n            trigger: \".o_command_palette\",\n            // Close the Command Palette\n            run: `press Escape`,\n        },\n        {\n            trigger: \".o_home_menu\",\n            // go back again to the app (using keyboard)\n            run: `press Escape`,\n        },\n        {\n            // wait to be back in the list view\n            trigger: \".o_list_view\",\n        },\n        {\n            // re-open studio\n            trigger: \".o_web_studio_navbar_item\",\n            run: \"click\",\n        },\n        {\n            // modify the list view\n            trigger: \".o_web_studio_sidebar .o_web_studio_view\",\n            run: \"click\",\n        },\n        {\n            //select field you want to sort and based on that sorting will be applied on List view\n            trigger:\n                '.o_web_studio_sidebar .o_web_studio_sidebar_select[name=\"sort_by\"] .o_select_menu_toggler',\n            run: \"click\",\n        },\n        {\n            trigger: \".dropdown-menu .dropdown-item\",\n            run: \"click\",\n        },\n        {\n            //change order of sorting, Select order and change it\n            trigger:\n                '.o_web_studio_sidebar .o_web_studio_sidebar_select[name=\"sort_order\"] .o_select_menu_toggler',\n            run: \"click\",\n        },\n        {\n            trigger: \".dropdown-menu .dropdown-item:nth-child(2)\",\n            run: \"click\",\n        },\n        {\n            // edit action\n            trigger: '.o_web_studio_menu .o_menu_sections li a:contains(\"Views\")',\n            run: \"click\",\n        },\n        {\n            // add a kanban\n            trigger: \".o_web_studio_view_category .o_web_studio_thumbnail_kanban.disabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_notebook .o_web_studio_new\",\n            run: \"click\",\n        },\n        {\n            // add a menu\n            trigger: \".o_web_studio_component.o_web_studio_field_menu\",\n            async run(helpers) {\n                const hook = \".o_web_studio_hook[data-structures='t,kanban_colorpicker']\";\n                document.querySelector(hook).style.setProperty(\"transition\", \"none\", \"important\");\n                await helpers.drag_and_drop(hook);\n            },\n        },\n        {\n            // add an aside\n            trigger: \".o_web_studio_component.o_web_studio_field_aside\",\n            async run(helpers) {\n                const hook = \".o_web_studio_hook[data-structures='aside']\";\n                document.querySelector(hook).style.setProperty(\"transition\", \"none\", \"important\");\n                await helpers.drag_and_drop(hook);\n            },\n        },\n        {\n            trigger: \".o_kanban_record main\",\n            content: \"card content has been wrapped in a <main> element\",\n        },\n        {\n            trigger: \".o_kanban_record aside\",\n        },\n        {\n            // add a colorpicker\n            trigger: \".o_web_studio_component.o_web_studio_field_color_picker\",\n            async run(helpers) {\n                const hook = \".o_web_studio_hook[data-structures='t,kanban_colorpicker']\";\n                document.querySelector(hook).style.setProperty(\"transition\", \"none\", \"important\");\n                await helpers.drag_and_drop(hook);\n            },\n        },\n        {\n            // select the menu for edition\n            trigger: \".o_dropdown_kanban\",\n            run: \"click\",\n        },\n        {\n            // select the colorpicker for edition\n            trigger: \"button.o_web_studio_field_color_picker:contains(Edit Color Picker)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_notebook_content h3:contains(Field)\",\n            content: \"sidebar is editing the color field\",\n        },\n        {\n            trigger: \".o_notebook .o_web_studio_view\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o_web_studio_property_highlight_color .o_select_menu_toggler:contains(Card color)\",\n        },\n        {\n            // edit action\n            trigger: '.o_web_studio_menu .o_menu_sections li a:contains(\"Views\")',\n            run: \"click\",\n        },\n        {\n            // check that the kanban view is now active\n            trigger: \".o_web_studio_view_category .o_web_studio_thumbnail_kanban:not(.disabled)\",\n        },\n        {\n            // add an activity view\n            trigger: \".o_web_studio_view_category .o_web_studio_thumbnail_activity.disabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_activity_view\",\n        },\n        {\n            // edit action\n            trigger: '.o_web_studio_menu .o_menu_sections li a:contains(\"Views\")',\n            timeout: 20000, // activating a view takes a while and sometimes reaches the default 10s timeout\n            run: \"click\",\n        },\n        {\n            // add a graph view\n            trigger: \".o_web_studio_view_category .o_web_studio_thumbnail_graph.disabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_graph_renderer\",\n        },\n        {\n            trigger: '.o_web_studio_menu .o_menu_sections li a:contains(\"Views\")',\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_views\",\n        },\n        {\n            run: \"click\",\n            // edit the search view\n            trigger:\n                \".o_web_studio_view_category .o_web_studio_thumbnail_item.o_web_studio_thumbnail_search\",\n        },\n        {\n            trigger: \".o_web_studio_search_view_editor\",\n        },\n        {\n            run: \"click\",\n            trigger: \".o_menu_toggle:not(.o_menu_toggle_back)\",\n        },\n        {\n            // export all modifications\n            trigger: \".o_web_studio_export\",\n            run: \"click\",\n        },\n        {\n            content: \"check that export feature is blazing fast\",\n            trigger: \".modal .modal-footer button:contains(export)\",\n            run: \"click\",\n        },\n        {\n            content: \"close modal\",\n            trigger: \".modal .modal-footer button:contains(cancel)\",\n            run: \"click\",\n        },\n        {\n            // click on the created app\n            trigger: '.o_app[data-menu-xmlid*=\"studio\"]:last',\n            run: \"click\",\n        },\n        {\n            // switch to form view\n            trigger: '.o_web_studio_views_icons > a[title=\"Form\"]',\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor\",\n        },\n        {\n            // click on the view tab\n            trigger: \".o_web_studio_view\",\n            run: \"click\",\n        },\n        {\n            // click on the restore default view button\n            trigger: \".o_web_studio_restore\",\n            run: \"click\",\n        },\n        {\n            // click on the ok button\n            trigger: \".modal-footer .btn.btn-primary\",\n            run: \"click\",\n        },\n        {\n            // checks that the field doesn't exist anymore\n            trigger: \".o_web_studio_form_view_editor:not(:has(.o_form_label))\",\n        },\n        {\n            trigger: \".o_web_studio_leave > a.btn\",\n            run: \"click\",\n        },\n        ...stepNotInStudio(\".o_form_view\"),\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_hide_fields_tour\", {\n    url: \"/odoo/action-studio?mode=home_menu&debug=1\",\n    steps: () => [\n        {\n            trigger: \".o_web_studio_new_app\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_app_creator_next\",\n            run: \"click\",\n        },\n        {\n            trigger: `\n        .o_web_studio_app_creator_name\n        > input`,\n            run: `edit ${randomString(6)}`,\n        },\n        {\n            // make another interaction to show \"next\" button\n            trigger: `\n        .o_web_studio_selectors\n        .o_web_studio_selector_icon > button`,\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_app_creator_next\",\n            run: \"click\",\n        },\n        {\n            trigger: `\n        .o_web_studio_menu_creator\n        > input`,\n            run: `edit ${randomString(6)}`,\n        },\n        {\n            trigger: \".o_web_studio_app_creator_next\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_model_configurator_next\",\n            run: \"click\",\n        },\n        {\n            // check that the Studio menu is still there\n            trigger: \".o_web_studio_menu\",\n        },\n        {\n            trigger: \".o_web_studio_leave > a.btn\",\n            timeout: 60000 /* previous step reloads registry, etc. - could take a long time */,\n            run: \"click\",\n        },\n        {\n            trigger: \".oe_title input\",\n            run: \"edit Test\",\n        },\n        {\n            trigger: \".o_form_button_save\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_menu\",\n        },\n        {\n            run: \"click\",\n            trigger: `\n        .o_web_studio_views_icons\n        > a[title=\"List\"]`,\n        },\n        {\n            // wait for the list editor to be rendered because the sidebar is the same\n            trigger: \".o_web_studio_list_view_editor\",\n        },\n        {\n            trigger: \".o_web_studio_existing_fields_header\",\n            run: \"click\",\n        },\n        {\n            trigger: `\n        .o_web_studio_sidebar\n        .o_web_studio_existing_fields\n        .o_web_studio_component:has(.o_web_studio_component_description:contains(display_name))`,\n            run: \"drag_and_drop .o_web_studio_list_view_editor .o_web_studio_hook\",\n        },\n        {\n            trigger: `\n        .o_list_table\n        th[data-name=\"display_name\"]`,\n            run: \"click\",\n        },\n        {\n            trigger: `\n        .o_web_studio_sidebar\n        [name=\"optional\"] .o_select_menu_toggler`,\n            run: \"click\",\n        },\n        {\n            trigger: \".o-dropdown--menu .o_select_menu_item:contains(Hide by default)\",\n            run: \"click\",\n        },\n        {\n            trigger: '.o_list_table:not(:has(th[data-name=\"display_name\"]))',\n        },\n        {\n            trigger: `\n        .o_web_studio_sidebar\n        .o_web_studio_view`,\n            run: \"click\",\n        },\n        {\n            trigger: `\n        .o_web_studio_sidebar_checkbox\n        input#show_invisible`,\n            run: \"click\",\n        },\n        {\n            trigger: `\n        .o_list_table\n        th[data-name=\"display_name\"].o_web_studio_show_invisible`,\n        },\n        {\n            trigger: \".o_web_studio_leave > a.btn\",\n            run: \"click\",\n        },\n        ...stepNotInStudio(\".o_list_view\"),\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_model_option_value_tour\", {\n    url: \"/odoo/action-studio?mode=home_menu&debug=tests\",\n    steps: () => [\n        {\n            trigger: \".o_web_studio_new_app\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_app_creator_next\",\n            run: \"click\",\n        },\n        {\n            trigger: `\n        .o_web_studio_app_creator_name\n        > input`,\n            run: `edit ${randomString(6)}`,\n        },\n        {\n            trigger: `\n        .o_web_studio_selectors\n        .o_web_studio_selector_icon > button`,\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_app_creator_next\",\n            run: \"click\",\n        },\n        {\n            trigger: `\n        .o_web_studio_menu_creator\n        > input`,\n            run: `edit ${randomString(6)}`,\n        },\n        {\n            trigger: \".o_web_studio_app_creator_next\",\n            run: \"click\",\n        },\n        {\n            // check monetary value in model configurator\n            trigger: 'input[name=\"use_value\"]',\n            run: \"click\",\n        },\n        {\n            // check lines value in model configurator\n            trigger: 'input[name=\"lines\"]',\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_model_configurator_next\",\n            run: \"click\",\n        },\n        {\n            trigger: '.o_web_studio_menu .o_web_studio_views_icons > a[title=\"Graph\"]',\n            timeout: 60000 /* previous step reloads registry, etc. - could take a long time */,\n            run: \"click\",\n        },\n        {\n            // wait for the graph editor to be rendered and also check for sample data\n            trigger: \".o_view_sample_data .o_graph_renderer\",\n        },\n        {\n            trigger: '.o_web_studio_menu .o_web_studio_views_icons a[title=\"Pivot\"]',\n            run: \"click\",\n        },\n        {\n            // wait for the pivot editor to be rendered and also check for sample data\n            trigger: \".o_pivot_view .o_view_sample_data .o_view_nocontent_empty_folder\",\n        },\n        {\n            trigger: \".o_web_studio_leave > a.btn\",\n            run: \"click\",\n        },\n        ...stepNotInStudio(\".o_pivot_view\"),\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_new_submenu_tour\", {\n    url: \"/odoo\",\n    steps: () => [\n        {\n            trigger: \".o_home_menu_background\",\n        },\n        {\n            // open studio\n            trigger: \".o_main_navbar .o_web_studio_navbar_item\",\n            run: \"click\",\n        },\n        {\n            trigger: \"body.o_in_studio\",\n        },\n        {\n            // click on the created app\n            trigger: '.o_app[data-menu-xmlid*=\"studio\"]:first',\n            run: \"click\",\n        },\n        {\n            // create a new menu\n            trigger: \".o-studio--menu .o_web_create_new_model\",\n            run: \"click\",\n        },\n        {\n            trigger: \"input[name=model_name]\",\n            run: `edit second menu ${randomString(6)}`,\n        },\n        {\n            trigger: \".confirm_button\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_model_configurator_next\",\n            run: \"click\",\n        },\n        {\n            // leave studio\n            trigger: \".o_web_studio_leave > a.btn\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_client:not(.o_in_studio)\",\n        },\n        {\n            // open studio again to check the new menu can be edited\n            trigger: \".o_main_navbar .o_web_studio_navbar_item\",\n            run: \"click\",\n        },\n        {\n            // check we are back in studio\n            trigger: \".o_in_studio\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_new_report_tour\", {\n    url: \"/odoo\",\n    steps: () => [\n        {\n            trigger: \".o_home_menu_background\",\n        },\n        {\n            // open studio\n            trigger: \".o_main_navbar .o_web_studio_navbar_item\",\n            run: \"click\",\n        },\n        {\n            trigger: \"body.o_in_studio\",\n        },\n        {\n            // click on the created app\n            trigger: '.o_app[data-menu-xmlid*=\"studio\"]:first',\n            run: \"click\",\n        },\n        {\n            // edit reports\n            trigger: \".o_web_studio_menu li a:contains(Reports)\",\n            run: \"click\",\n        },\n        {\n            // create a new report\n            trigger: \".o_control_panel .o-kanban-button-new\",\n            run: \"click\",\n        },\n        {\n            // select external layout\n            trigger: '.o_web_studio_report_layout_dialog div[data-layout=\"web.external_layout\"]',\n            run: \"click\",\n        },\n        {\n            // edit report name\n            trigger: '.o_web_studio_sidebar input[id=\"name\"]',\n            run: \"edit My Awesome Report && click body\",\n        },\n        {\n            // add a new group on the node\n            trigger: '.o_web_studio_sidebar .o_field_many2many_tags[name=\"groups_id\"] input',\n            run: \"click\",\n        },\n        {\n            trigger: \".o-autocomplete--dropdown-menu li:contains(Access Rights)\",\n            run: \"click\",\n        },\n        {\n            // wait for the group to appear\n            trigger:\n                '.o_web_studio_sidebar .o_field_many2many_tags[name=\"groups_id\"] .o_tag_badge_text:contains(Access Rights)',\n        },\n        {\n            trigger:\n                \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable div.page div\",\n            run() {\n                this.anchor.ownerDocument.getSelection().setPosition(this.anchor);\n                assertEqual(\n                    this.anchor.outerHTML,\n                    `<div class=\"oe_structure\" o-diff-key=\"3\"></div>`\n                );\n            },\n        },\n        {\n            trigger:\n                \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable div.page div\",\n            run() {\n                assertEqual(this.anchor.classList.contains(\"o-we-hint\"), true);\n            },\n        },\n        {\n            trigger:\n                \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable div.page div\",\n            run: \"editor some new text\",\n        },\n        {\n            trigger: \".o_web_studio_menu .o-web-studio-save-report.btn-primary\",\n            run: \"click\",\n        },\n        {\n            // The report has been saved\n            trigger: \".o_web_studio_menu .o-web-studio-save-report:not(.btn-primary)\",\n        },\n        {\n            trigger: \".o_web_studio_breadcrumb .o_back_button:contains(Reports)\",\n            run: \"click\",\n        },\n        {\n            content: \"open the dropdown\",\n            trigger: \".o_kanban_record:contains(My Awesome Report) .dropdown-toggle:not(:visible)\",\n            run: \"click\",\n        },\n        {\n            // duplicate the report\n            trigger: \".dropdown-menu a:contains(Duplicate)\",\n            run: \"click\",\n        },\n        {\n            // open the duplicate report\n            trigger: \".o_kanban_record:contains(My Awesome Report copy(1))\",\n            run: \"click\",\n        },\n        {\n            // switch to 'Report' tab\n            trigger: \".o_web_studio_sidebar input[id='name']\",\n            run() {\n                assertEqual(this.anchor.value, \"My Awesome Report copy(1)\");\n            },\n        },\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe div.page div\",\n            run() {\n                assertEqual(this.anchor.textContent, \"some new text\");\n            },\n        },\n        {\n            trigger:\n                '.o_web_studio_sidebar .o_field_many2many_tags[name=\"groups_id\"] .o_tag_badge_text:contains(Access Rights)',\n        },\n        {\n            trigger: \".o_web_studio_leave > a.btn\",\n            run: \"click\",\n        },\n        ...stepNotInStudio(),\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_new_report_basic_layout_tour\", {\n    url: \"/odoo\",\n    steps: () => [\n        {\n            trigger: \".o_home_menu_background\",\n        },\n        {\n            // open studio\n            trigger: \".o_main_navbar .o_web_studio_navbar_item\",\n            run: \"click\",\n        },\n        {\n            trigger: \"body.o_in_studio\",\n        },\n        {\n            // click on the created app\n            trigger: '.o_app[data-menu-xmlid*=\"studio\"]:first',\n            run: \"click\",\n        },\n        {\n            // edit reports\n            trigger: \".o_web_studio_menu li a:contains(Reports)\",\n            run: \"click\",\n        },\n        {\n            // create a new report\n            trigger: \".o_control_panel .o-kanban-button-new\",\n            run: \"click\",\n        },\n        {\n            // select basic layout\n            trigger: '.o_web_studio_report_layout_dialog div[data-layout=\"web.basic_layout\"]',\n            run: \"click\",\n        },\n        {\n            // edit report name\n            trigger: '.o_web_studio_sidebar input[id=\"name\"]',\n            run: \"edit My Awesome basic layout Report && click body\",\n        },\n        {\n            // add a new group on the node\n            trigger: '.o_web_studio_sidebar .o_field_many2many_tags[name=\"groups_id\"] input',\n            run: \"click\",\n        },\n        {\n            trigger: \".o-autocomplete--dropdown-menu li:contains(Access Rights)\",\n            run: \"click\",\n        },\n        {\n            // wait for the group to appear\n            trigger:\n                '.o_web_studio_sidebar .o_field_many2many_tags[name=\"groups_id\"] .o_tag_badge_text:contains(Access Rights)',\n        },\n        {\n            trigger: \".o_web_studio_menu .o-web-studio-save-report.btn-primary\",\n            run: \"click\",\n        },\n        {\n            // The report has been saved\n            trigger: \".o_web_studio_menu .o-web-studio-save-report:not(.btn-primary)\",\n        },\n        {\n            // leave the report\n            trigger: \".o_web_studio_breadcrumb .o_back_button:contains(Reports)\",\n            run: \"click\",\n        },\n        {\n            content: \"open the dropdown\",\n            trigger: \".o_kanban_record:contains(My Awesome basic layout Report)\",\n            run: \"hover && click .o_kanban_record:contains(My Awesome basic layout Report) .dropdown-toggle\",\n        },\n        {\n            // duplicate the report\n            trigger: \".dropdown-menu .dropdown-item:contains(Duplicate)\",\n            run: \"click\",\n        },\n        {\n            // open the duplicate report\n            trigger: \".o_kanban_record:contains(My Awesome basic layout Report copy(1))\",\n            run: \"click\",\n        },\n        {\n            trigger: '.o_web_studio_sidebar input[id=\"name\"]',\n            run() {\n                assertEqual(this.anchor.value, \"My Awesome basic layout Report copy(1)\");\n            },\n        },\n        {\n            trigger: \".o_web_studio_leave > a.btn\",\n            run: \"click\",\n        },\n        ...stepNotInStudio(),\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_approval_tour\", {\n    url: \"/odoo?debug=1\",\n    steps: () => [\n        {\n            // go to Apps menu\n            trigger: '.o_app[data-menu-xmlid=\"base.menu_management\"]',\n            run: \"click\",\n        },\n        {\n            trigger: \".o_cp_switch_buttons\",\n        },\n        {\n            // open studio\n            trigger: \".o_main_navbar .o_web_studio_navbar_item\",\n            run: \"click\",\n        },\n        {\n            // switch to form view editor\n            trigger: '.o_web_studio_views_icons > a[title=\"Form\"]',\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_view\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar #show_invisible\",\n            run: \"click\",\n        },\n        {\n            content: \"click on first button it finds that has a node id\",\n            trigger:\n                \".o_web_studio_form_view_editor button[name='button_immediate_upgrade'].o-web-studio-editor--element-clickable\",\n            run: \"click\",\n        },\n        {\n            // enable approvals for the button\n            trigger: '.o_web_studio_sidebar_approval [name=\"create_approval_rule\"]',\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_snackbar .fa-check\",\n        },\n        {\n            // add approval rule\n            trigger: '.o_web_studio_sidebar_approval [name=\"create_approval_rule\"]',\n            run: \"click\",\n        },\n        {\n            // set approval message\n            trigger: '.o_web_studio_sidebar_approval input[name*=\"approval_message\"]',\n            run: \"edit nope\",\n        },\n        {\n            trigger: \".o_studio_sidebar_approval_rule:eq(1)\",\n        },\n        {\n            // set domain on first rule\n            trigger: \".o_web_studio_sidebar_approval .o_approval_domain\",\n            run: \"click\",\n        },\n        {\n            // set stupid domain that is always truthy\n            trigger: \".o_domain_selector_debug_container textarea\",\n            run: function () {\n                this.anchor.focus();\n                this.anchor.value = '[[\"id\", \"!=\", False]]';\n                this.anchor.dispatchEvent(new Event(\"change\", { bubbles: true }));\n            },\n        },\n        {\n            // save domain and close modal\n            trigger: \" .modal-footer .btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_snackbar .fa-check\",\n        },\n        {\n            // add second approval rule when the first is set\n            trigger: '.o_web_studio_sidebar_approval [name=\"create_approval_rule\"]',\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_snackbar .fa-check\",\n        },\n        {\n            // enable 'force different users' for one rule (doesn't matter which)\n            trigger: '.o_web_studio_sidebar label[for*=\"exclusive_user\"]',\n            run: \"click\",\n        },\n        {\n            // switch to kanban view editor\n            trigger: '.o_web_studio_views_icons > a[title=\"Kanban\"]',\n            run: \"click\",\n        },\n        {\n            // leave studio\n            trigger: \".o_web_studio_leave > a.btn\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_client:not(.o_in_studio)\",\n        },\n        {\n            // TODO: add breacrumb to access multi-record view when closing studio and close studio from form instead of from kanban\n            // trigger: \".o_breadcrumb .o_back_button\",\n            trigger: \"body\",\n        },\n        {\n            trigger: \".o_searchview_dropdown_toggler\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-dropdown-item:contains(Installed)\",\n            run: \"click\",\n        },\n        {\n            content:\n                \"open first record (should be the one that was used, so the button should be there)\",\n            trigger:\n                \".o_kanban_view .o_kanban_record:not(:has(button[name='button_immediate_install'])) .o_dropdown_kanban .dropdown-toggle\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-dropdown--menu .dropdown-item\",\n            run: \"click\",\n        },\n        {\n            // try to do the action\n            trigger: \"button[name='button_immediate_upgrade']\",\n            run: \"click\",\n        },\n        {\n            // there should be a warning\n            trigger: \".o_notification_bar.bg-warning\",\n            run: \"click\",\n        },\n        {\n            trigger: \".breadcrumb .o_back_button\",\n            run: \"click\",\n        },\n        {\n            trigger: \"body .o_modules_kanban\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_custom_field_tour\", {\n    url: \"/odoo\",\n    steps: () => [\n        {\n            // go to Apps menu\n            trigger: '.o_app[data-menu-xmlid=\"base.menu_management\"]',\n            run: \"click\",\n        },\n        {\n            // click on the list view\n            trigger: \".o_switch_view.o_list\",\n            run: \"click\",\n        },\n        {\n            // click on optional column dropdown\n            trigger: \".o_optional_columns_dropdown_toggle\",\n            run: \"click\",\n        },\n        {\n            // click on add custom field\n            trigger: \".dropdown-item-studio\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_client.o_in_studio\",\n        },\n        {\n            // go to home menu\n            trigger: \".o_menu_toggle\",\n            run: \"click\",\n        },\n        {\n            //leave studio\n            trigger: \".o_web_studio_leave > a.btn\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_client:not(.o_in_studio)\",\n        },\n        {\n            // studio left.\n            trigger: '.o_app[data-menu-xmlid=\"base.menu_management\"]',\n            run: \"click\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_local_storage_tour\", {\n    url: \"/odoo\",\n    steps: () => [\n        {\n            trigger: '.o_app[data-menu-xmlid=\"base.menu_management\"]',\n            run: function () {\n                localStorage.setItem(\"openStudioOnReload\", \"main\");\n                window.location.reload();\n            },\n        },\n        {\n            trigger: \".o_web_client.o_in_studio\",\n        },\n        {\n            // should be directly in studio mode\n            trigger: '.o_app[data-menu-xmlid=\"base.menu_management\"]',\n            run: \"click\",\n        },\n        {\n            trigger: \".o_menu_toggle\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_leave > a.btn\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_client:not(.o_in_studio)\",\n        },\n        {\n            // studio left.\n            trigger: '.o_app[data-menu-xmlid=\"base.menu_management\"]',\n            run: function () {\n                window.location.reload();\n            },\n        },\n        {\n            trigger: \".o_web_client:not(.o_in_studio)\",\n        },\n        {\n            // studio left after refresh.\n            trigger: '.o_app[data-menu-xmlid=\"base.menu_management\"]',\n            run: \"click\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_custom_background_tour\", {\n    url: \"/odoo\",\n    steps: () => [\n        {\n            content: \"class for custom background must be enabled (outside studio)\",\n            trigger: \".o_home_menu_background_custom.o_home_menu_background:not(.o_in_studio)\",\n            run: () => null,\n        },\n        {\n            content: \"opening studio\",\n            trigger: \".o_main_navbar .o_web_studio_navbar_item\",\n            run: \"click\",\n        },\n        {\n            content: \"class for custom background must be enabled (in studio)\",\n            trigger: \".o_home_menu_background_custom.o_home_menu_background.o_in_studio\",\n            run: () => null,\n        },\n        {\n            content: \"reset the background\",\n            trigger: \".o_web_studio_reset_default_background\",\n            run: \"click\",\n        },\n        {\n            content: \"validate the reset of the background\",\n            trigger: \".modal-dialog .btn-primary\",\n            run: \"click\",\n        },\n        {\n            content: \"class for custom background must be disabled (inside studio)\",\n            trigger: \".o_home_menu_background.o_in_studio:not(.o_home_menu_background_custom)\",\n            run: () => null,\n        },\n        {\n            content: \"leaving studio\",\n            trigger: \".o_web_studio_leave a\",\n            run: \"click\",\n        },\n        {\n            content: \"class for custom background must be disabled (outside studio)\",\n            trigger: \".o_home_menu_background:not(.o_in_studio.o_home_menu_background_custom)\",\n            run: () => null,\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_create_app_with_pipeline_and_user_assignment\", {\n    steps: () => [\n        {\n            trigger: \".o_home_menu_background\",\n        },\n        {\n            // open studio\n            trigger: \".o_main_navbar .o_web_studio_navbar_item\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_new_app\",\n            run: \"click\",\n        },\n        {\n            // the next steps are here to create a new app\n            trigger: \".o_web_studio_app_creator_next\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_app_creator_name > input\",\n            run: `edit ${(createdAppString = randomString(6))}`,\n        },\n        {\n            trigger: \".o_web_studio_app_creator_next.is_ready\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_menu_creator > input\",\n            run: `edit ${(createdAppString = randomString(6))}`,\n        },\n        {\n            trigger: \".o_web_studio_app_creator_next.is_ready\",\n            run: \"click\",\n        },\n        {\n            trigger: \"input#use_stages\",\n            run: \"click\",\n        },\n        {\n            trigger: \"input#use_responsible\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_model_configurator_next\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_editor .o_menu_sections a:contains(Views)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_thumbnail_item.o_web_studio_thumbnail_kanban\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_kanban_view_editor\",\n        },\n        {\n            trigger: \".o_avatar.o_m2o_avatar\",\n            run() {\n                const avatarImg = document.querySelector(\".o_avatar.o_m2o_avatar a\");\n                if (!avatarImg.getAttribute(\"title\") === \"Assign\") {\n                    throw new Error(\n                        \"It should be possible to assign a record, when no one is currently selected\"\n                    );\n                }\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_alter_field_existing_in_multiple_views_tour\", {\n    steps: () => [\n        {\n            trigger: \".o_home_menu_background\",\n        },\n        {\n            // open studio\n            trigger: \".o_main_navbar .o_web_studio_navbar_item button\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_new_app\",\n            run: \"click\",\n        },\n        {\n            // the next steps are here to create a new app\n            trigger: \".o_web_studio_app_creator_next\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_app_creator_name > input\",\n            run: `edit ${(createdAppString = randomString(6))}`,\n        },\n        {\n            trigger: \".o_web_studio_app_creator_next.is_ready\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_menu_creator > input\",\n            run: `edit ${createdAppString}`,\n        },\n        {\n            trigger: \".o_web_studio_app_creator_next.is_ready\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_model_configurator_next\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar\",\n        },\n        {\n            // unfold 'Existing Fieldqs' section\n            trigger: \".o_web_studio_existing_fields_header\",\n            timeout: 60000,\n            run: \"click\",\n        },\n        {\n            // add an existing field (the one we created)\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_field_type_container:eq(2) .o_web_studio_field_many2many:contains(Followers (Partners))\",\n            run: \"drag_and_drop .o_inner_group:first .o_web_studio_hook:first\",\n        },\n        {\n            trigger: \".o_web_studio_new \",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_field_type_container:eq(1) .o_web_studio_field_many2many\",\n            run: \"drag_and_drop div.o_web_studio_hook:last\",\n        },\n        {\n            trigger: \".modal-body\",\n        },\n        {\n            trigger: '.modal:not(.o_inactive_modal) [name=\"relation_id\"] input',\n            run: `edit ${createdAppString}`,\n        },\n        {\n            // select the first model\n            trigger:\n                \".modal:not(.o_inactive_modal) .o-autocomplete--dropdown-menu > li > a:not(:has(.fa-spin))\",\n            run: \"click\",\n        },\n        {\n            trigger: \"button:contains(Confirm)\",\n            run: \"click\",\n        },\n        {\n            // edit list view\n            trigger: \".o_web_studio_editX2Many\",\n            run: \"click\",\n        },\n        {\n            // wait for list view to be loaded\n            trigger: \".o_web_studio_list_view_editor\",\n        },\n        {\n            // go to view\n            trigger: \".o_web_studio_view \",\n            run: \"click\",\n        },\n        {\n            // show invisible elements\n            trigger: 'label[for=\"show_invisible\"]',\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_new \",\n            run: \"click\",\n        },\n        {\n            // unfold 'Existing Fieldqs' section\n            trigger: \".o_web_studio_existing_fields_header\",\n            run: \"click\",\n        },\n        {\n            // add an existing field (the one we created)\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_field_type_container:eq(1) .o_web_studio_field_many2many:contains(Followers (Partners))\",\n            run: \"drag_and_drop .o_web_studio_list_view_editor th.o_web_studio_hook:first\",\n        },\n        {\n            // select field\n            trigger: \"th[data-name='message_partner_ids']\",\n            run: \"click\",\n        },\n        {\n            // make it invisible\n            trigger: \"#invisible\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_snackbar .fa.fa-check\",\n        },\n        {\n            // check if the invisible option is checked\n            trigger: \"#invisible:checked\",\n        },\n    ],\n});\n\nconst buttonToogleStudio = {\n    trigger: `button[title=\"Toggle Studio\"]`,\n    run: \"click\",\n};\nconst addActionButtonModalSteps = (\n    ActionLabel = \"web_studio_new_button_action_name\",\n    ActionName = \"Privacy Lookup\"\n) => [\n    {\n        trigger: \".o-web-studio-editor--add-button-action\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o-web-studio-editor--modal-add-action input#set_label\",\n        run: `edit ${ActionLabel}`,\n    },\n    {\n        trigger: \".o-web-studio-editor--modal-add-action input#set_button_type_to_action\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o-web-studio-editor--modal-add-action .o_record_selector input\",\n        run: `edit ${ActionName}`,\n    },\n    {\n        trigger: `.o-web-studio-editor--modal-add-action .o-autocomplete--dropdown-menu li a:not(:has(.fa-spin)):contains(${ActionName})`,\n        run: \"click\",\n    },\n    {\n        trigger: \"footer button.o-web-studio-editor--add-button-confirm\",\n        run: \"click\",\n    },\n];\n\nconst addMethodButtonModalSteps = () => [\n    {\n        trigger: \".o-web-studio-editor--add-button-action\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o-web-studio-editor--modal-add-action input#set_label\",\n        run: `edit test`,\n    },\n    {\n        trigger: \".o-web-studio-editor--modal-add-action input#set_button_type_to_object\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o-web-studio-editor--modal-add-action  input#set_method\",\n        run: `edit demo && click body`,\n    },\n];\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_check_method_in_model\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view .o_form_editable\",\n            run: \"click\",\n        },\n        buttonToogleStudio,\n        ...addMethodButtonModalSteps(),\n        {\n            trigger: \"div.text-danger\",\n            run() {\n                const div_error = document.querySelector(\"div.text-danger\");\n                assertEqual(\n                    div_error.innerHTML,\n                    \"The method demo does not exist on the model res.partner().\"\n                );\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_create_action_button_in_form_view\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view .o_form_editable\",\n            run: \"click\",\n        },\n        buttonToogleStudio,\n        ...addActionButtonModalSteps(),\n        {\n            trigger: \".o_web_studio_leave a\",\n            run: \"click\",\n        },\n        ...stepNotInStudio(\".o_form_view\"),\n    ],\n});\n\nregistry\n    .category(\"web_tour.tours\")\n    .add(\"web_studio_test_create_second_action_button_in_form_view\", {\n        steps: () => [\n            {\n                trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o_form_view .o_form_editable\",\n                run: \"click\",\n            },\n            buttonToogleStudio,\n            ...addActionButtonModalSteps(\"web_studio_other_button_action_name\", \"Download (vCard)\"),\n            {\n                trigger: \".o_web_studio_leave a\",\n                run: \"click\",\n            },\n            ...stepNotInStudio(\".o_form_view\"),\n        ],\n    });\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_create_action_button_in_list_view\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view .o_form_editable\",\n            run: \"click\",\n        },\n        buttonToogleStudio,\n        {\n            trigger: \".o_web_studio_views_icons a[aria-label='List']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_optional_columns_dropdown button\",\n            run: \"click\",\n        },\n        ...addActionButtonModalSteps(),\n        {\n            trigger: \".o_web_studio_leave a\",\n            run: \"click\",\n        },\n        ...stepNotInStudio(\".o_list_view\"),\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_remove_action_button_in_form_view\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view .o_form_editable\",\n            run: \"click\",\n        },\n        buttonToogleStudio,\n        {\n            trigger: 'button[studioxpath=\"/form[1]/header[1]/button[1]\"]',\n            run: \"click\",\n        },\n        {\n            trigger: \"button.o_web_studio_remove\",\n            run: \"click\",\n        },\n        {\n            trigger: \"footer.modal-footer>button.btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_leave a\",\n            run: \"click\",\n        },\n        ...stepNotInStudio(\".o_form_view\"),\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_remove_action_button_in_list_view\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view .o_form_editable\",\n            run: \"click\",\n        },\n        buttonToogleStudio,\n        {\n            trigger: \".o_web_studio_views_icons a[aria-label='List']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_optional_columns_dropdown button\",\n            run: \"click\",\n        },\n        {\n            trigger: 'button[studioxpath=\"/list[1]/header[1]/button[1]\"]',\n            run: \"click\",\n        },\n        {\n            trigger: \"button.o_web_studio_remove\",\n            run: \"click\",\n        },\n        {\n            trigger: \"footer.modal-footer>button.btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_leave a\",\n            run: \"click\",\n        },\n        ...stepNotInStudio(\".o_list_view\"),\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_monetary_create\", {\n    url: \"/odoo?debug=1\",\n    steps: () => [\n        // This tour drag&drop a monetary field and verify that a currency is created\n        {\n            trigger: \".o_home_menu_background\",\n        },\n        {\n            // open studio\n            trigger: \".o_main_navbar .o_web_studio_navbar_item\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_new_app\",\n        },\n        {\n            trigger: \".o_app[data-menu-xmlid='web_studio.studio_app_menu']\",\n            run: \"click\",\n        },\n        // drag&drop a monetary and verify that the currency is in the view\n        {\n            // add a new monetary field\n            trigger: \".o_web_studio_sidebar .o_web_studio_field_monetary\",\n            run: \"drag_and_drop .o_web_studio_form_view_editor .o_inner_group\",\n        },\n        {\n            // verify that the currency is set\n            trigger: \".o_web_studio_sidebar .o_web_studio_property_currency_field .text-start\",\n            run() {\n                assertEqual(this.anchor.textContent, \"Currency (x_studio_currency_id)\");\n            },\n        },\n        {\n            // currency field is in the view\n            trigger: \".o_web_studio_view_renderer div[data-field-name='x_studio_currency_id']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_properties.active\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_monetary_change_currency_name\", {\n    url: \"/odoo?debug=1\",\n    steps: () => [\n        // Changing currency name also change the currency name in the monetary currency selection\n        {\n            trigger: \".o_home_menu_background\",\n        },\n        {\n            // open studio\n            trigger: \".o_main_navbar .o_web_studio_navbar_item\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_new_app\",\n        },\n        {\n            trigger: \".o_app[data-menu-xmlid='web_studio.studio_app_menu']\",\n            run: \"click\",\n        },\n        {\n            // currency field is in the view and click on it\n            trigger: \".o_web_studio_view_renderer [data-field-name='x_studio_currency_test']\",\n            run: \"click\",\n        },\n        {\n            // change the currency name\n            trigger: \"input[name='string']\",\n            run: \"edit NewCurrency && click body\",\n        },\n        {\n            // click on monetary\n            trigger: \"div[data-field-name^='x_studio_monetary_test']\",\n            run: \"click\",\n        },\n        {\n            // verify that the currency name changed in the monetary field\n            trigger: \".o_web_studio_sidebar .o_web_studio_property_currency_field .text-start\",\n            run() {\n                assertEqual(this.anchor.textContent, \"NewCurrency (x_studio_currency_test)\");\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_related_monetary_creation\", {\n    url: \"/odoo?debug=1\",\n    steps: () => [\n        {\n            trigger: \".o_home_menu_background\",\n        },\n        {\n            // open studio\n            trigger: \".o_main_navbar .o_web_studio_navbar_item\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_new_app\",\n        },\n        {\n            trigger: \".o_app[data-menu-xmlid='web_studio.studio_app_menu']\",\n            run: \"click\",\n        },\n        {\n            // add a new related field\n            trigger: \".o_web_studio_sidebar .o_web_studio_field_related\",\n            run: \"drag_and_drop .o_web_studio_form_view_editor .o_inner_group\",\n        },\n        {\n            trigger: \".o_model_field_selector_value\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_model_field_selector_popover_search input\",\n            run: \"edit X Test\",\n        },\n        {\n            run: \"click\",\n            trigger:\n                \".o_model_field_selector_popover_item[data-name='x_test'] .o_model_field_selector_popover_item_relation\",\n        },\n        {\n            trigger: \".o_model_field_selector_popover_search input\",\n            run: \"edit X Studio Monetary Test\",\n        },\n        {\n            run: \"click\",\n            trigger:\n                \".o_model_field_selector_popover_item[data-name='x_studio_monetary_test'] button\",\n        },\n        {\n            trigger: \".modal-footer button.btn-primary\",\n            run: \"click\",\n        },\n        {\n            // The related monetary is created\n            trigger: \".o_web_studio_view_renderer .o_form_label:contains('New Related Field')\",\n            run: \"click\",\n        },\n        {\n            // The currency is created\n            trigger: \".o_web_studio_view_renderer [data-field-name='x_studio_currency_id']\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_monetary_change_currency_field\", {\n    url: \"/odoo\",\n    steps: () => [\n        // Change currency and verify that the view take the changes into account (the dollar appears)\n        {\n            // open the custom app form view\n            trigger: \"a[data-menu-xmlid='web_studio.studio_app_menu']\",\n            run: \"click\",\n        },\n        {\n            // fill the required char input\n            trigger: \".o_field_char input\",\n            run: \"edit title\",\n        },\n        {\n            // fill the new currency (many2one) input #1\n            trigger: \"div [name='x_studio_currency_test2'] input\",\n            run: \"edit USD\",\n        },\n        {\n            // add a new currency field step #2\n            trigger: '.ui-menu-item a:contains(\"USD\")',\n            run: \"click\",\n        },\n        {\n            // save the view form\n            trigger: \"button.o_form_button_save\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_saved\",\n        },\n        {\n            // open studio with the record\n            trigger: \".o_main_navbar .o_web_studio_navbar_item button\",\n            run: \"click\",\n        },\n        {\n            // check that there is no currency symbol in renderer\n            trigger: \"div[name='x_studio_monetary_test'] span\",\n            run() {\n                assertEqual(this.anchor.textContent, \"0.00\");\n            },\n        },\n        {\n            // click on the monetary field\n            trigger: \"div[data-field-name='x_studio_monetary_test']\",\n            run: \"click\",\n        },\n        {\n            // change the currency_field in the monetary\n            trigger: \".o_web_studio_sidebar .o_web_studio_property_currency_field button\",\n            run: \"click\",\n        },\n        {\n            // click on the second currency, which is \"X Studio Currency Test2\"\n            trigger: \".o-dropdown--menu .o_select_menu_item:nth-child(2)\",\n            run: \"click\",\n        },\n        {\n            //wait until the currency has been set (also test the reactivity)\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_property_currency_field span.text-start:contains('X Studio Currency Test2')\",\n        },\n        {\n            // by changing the currency, we should have a $ symbol in the renderer\n            trigger: \"div[name^='x_studio_monetary'] span\",\n            run() {\n                assertEqual(this.anchor.textContent, \"$\u00a00.00\");\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_monetary_change_currency_not_in_view\", {\n    url: \"/odoo\",\n    steps: () => [\n        // Change a currency that is not present in the view insert it in the view\n        {\n            trigger: \".o_home_menu_background\",\n        },\n        {\n            // open studio\n            trigger: \".o_main_navbar .o_web_studio_navbar_item\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_new_app\",\n        },\n        {\n            trigger: \".o_app[data-menu-xmlid='web_studio.studio_app_menu']\",\n            run: \"click\",\n        },\n        {\n            // click on the monetary field\n            trigger: \"div[data-field-name='x_studio_monetary_test']\",\n            run: \"click\",\n        },\n        {\n            // change the currency_field in the monetary\n            trigger: \".o_web_studio_sidebar .o_web_studio_property_currency_field button\",\n            run: \"click\",\n        },\n        {\n            // click on the second currency, which is \"X Studio Currency Test2\"\n            trigger: \".o-dropdown--menu .o_select_menu_item:nth-child(2)\",\n            run: \"click\",\n        },\n        {\n            // wait until the currency has been set\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_property_currency_field span.text-start:contains('X Studio Currency Test2')\",\n        },\n        {\n            // go to view tab\n            trigger: \".o_web_studio_view\",\n            run: \"click\",\n        },\n        {\n            // currency field is in the view and click on it\n            trigger: \".o_web_studio_view_renderer div[data-field-name='x_studio_currency_test2']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_properties.active\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_monetary_add_existing_monetary\", {\n    url: \"/odoo?debug=1\",\n    steps: () => [\n        // Add an existing monetary trough the \"existing fields\" and verify that the currency\n        // is added to the view\n        {\n            trigger: \".o_home_menu_background\",\n        },\n        {\n            // open studio\n            trigger: \".o_main_navbar .o_web_studio_navbar_item\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_new_app\",\n        },\n        {\n            trigger: \".o_app[data-menu-xmlid='web_studio.studio_app_menu']\",\n            run: \"click\",\n        },\n        {\n            // click on \"existing fields\"\n            trigger: \".o_web_studio_existing_fields_header\",\n            run: \"click\",\n        },\n        {\n            // add the existing monetary field\n            trigger: \".o_web_studio_existing_fields_section .o_web_studio_field_monetary\",\n            run: \"drag_and_drop .o_form_renderer .o_web_studio_hook\",\n        },\n        {\n            // monetary exist and click on monetary\n            trigger: \"div[data-field-name='x_studio_monetary_test']\",\n            run: \"click\",\n        },\n        {\n            // verify that the currency name changed in the monetary field\n            trigger: \".o_web_studio_sidebar .o_web_studio_property_currency_field .text-start\",\n            run() {\n                assertEqual(\n                    this.anchor.textContent,\n                    \"X Studio Currency Test (x_studio_currency_test)\"\n                );\n            },\n        },\n        {\n            // currency field is in the view\n            trigger: \"div[data-field-name='x_studio_currency_test']\",\n        },\n    ],\n});\n\nregistry\n    .category(\"web_tour.tours\")\n    .add(\"web_studio_monetary_create_monetary_with_existing_currency\", {\n        url: \"/odoo?debug=1\",\n        steps: () => [\n            // Add a new monetary field, since a currency already exists, it should take it instead\n            // of creating a new one\n            {\n                trigger: \".o_home_menu_background\",\n            },\n            {\n                // open studio\n                trigger: \".o_main_navbar .o_web_studio_navbar_item\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o_web_studio_new_app\",\n            },\n            {\n                trigger: \".o_app[data-menu-xmlid='web_studio.studio_app_menu']\",\n                run: \"click\",\n            },\n            {\n                // go to Add tab\n                trigger: \".o_web_studio_new\",\n                run: \"click\",\n            },\n            {\n                // add a new monetary field\n                trigger: \".o_web_studio_sidebar .o_web_studio_field_monetary\",\n                async run(helpers) {\n                    await helpers.drag_and_drop(\n                        `.o_web_studio_form_view_editor .o_inner_group .o_web_studio_hook:eq(1)`,\n                        {\n                            position: {\n                                bottom: 0,\n                            },\n                            relative: true,\n                        }\n                    );\n                },\n            },\n            {\n                // there is only one occurence of the currency field in the view\n                trigger: \".o_form_renderer div[data-field-name^='x_studio_monetary']\",\n                run() {\n                    const o2mNumber = document.querySelectorAll(\"div.o_field_many2one\");\n                    assertEqual(o2mNumber.length, 1);\n                },\n            },\n        ],\n    });\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_add_field_into_empty_group_by\", {\n    url: \"/odoo?debug=1\",\n    steps: () => [\n        {\n            trigger: \".o_home_menu_background\",\n        },\n        {\n            trigger: \".o_main_navbar .o_web_studio_navbar_item\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_new_app\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_app_creator_next\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_app_creator_name > input\",\n            run: `edit ${randomString(6)}`,\n        },\n        {\n            trigger: \".o_web_studio_app_creator_next.is_ready\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_menu_creator > input\",\n            run: `edit ${randomString(6)}`,\n        },\n        {\n            trigger: \".o_web_studio_app_creator_next.is_ready\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_model_configurator_next\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_views_icons a:last\",\n            run: \"click\",\n        },\n        {\n            trigger: `\n        .o_web_studio_sidebar\n        .o_web_studio_existing_fields\n        .o_web_studio_component:has(.o_web_studio_component_description:contains(create_date))`,\n            run: \"drag_and_drop .o-web-studio-search--groupbys .o_web_studio_hook\",\n        },\n    ],\n});\n", "/** @odoo-module */\nimport { registry } from \"@web/core/registry\";\nimport { download } from \"@web/core/network/download\";\nimport { patch } from \"@web/core/utils/patch\";\nimport { parseXML, serializeXML } from \"@web/core/utils/xml\";\nimport { assertEqual, stepNotInStudio, nextTick } from \"@web_studio/../tests/tours/tour_helpers\";\nimport { cookie } from \"@web/core/browser/cookie\";\nimport { Editor } from \"@html_editor/editor\";\nimport { nodeSize } from \"@html_editor/utils/position\";\n\nconst getBoundingClientRect = Element.prototype.getBoundingClientRect;\n\nconst editorsWeakMap = new WeakMap();\npatch(Editor.prototype, {\n    attachTo(editable) {\n        editorsWeakMap.set(editable.ownerDocument, this);\n        return super.attachTo(...arguments);\n    },\n});\n\nfunction normalizeXML(str) {\n    const doc = parseXML(str);\n    /* Recursively trim text nodes conditionally\n     * if they start or end with a newline (\\n).\n     * In that case we make the assumption that all whitespaces\n     * are materializing indentation.\n     * If there are only spaces (\\s), we make the assumption that they\n     * are actual spaces that are visible to the naked eye of the user.\n     */\n    const nodes = [...doc.childNodes];\n    for (const node of nodes) {\n        if (node.nodeType === Node.TEXT_NODE) {\n            let nodeValue = node.nodeValue;\n            if (nodeValue.startsWith(\"\\n\")) {\n                nodeValue = nodeValue.trimStart();\n            }\n            if (nodeValue.endsWith(\"\\n\")) {\n                nodeValue = nodeValue.trimEnd();\n            }\n            node.nodeValue = nodeValue;\n        }\n        if (node.nodeType === Node.ELEMENT_NODE) {\n            nodes.push(...node.childNodes);\n        }\n    }\n\n    return serializeXML(doc);\n}\n\nfunction insertText(element, text, offsets = null) {\n    const doc = element.ownerDocument;\n    const sel = doc.getSelection();\n    let range;\n    if (sel && sel.rangeCount) {\n        range = sel.getRangeAt(sel.rangeCount - 1);\n    }\n    if (offsets || !range) {\n        const { start, end } = offsets || {};\n        sel.removeAllRanges();\n        range = doc.createRange();\n        range.setStart(element, start || 0);\n        range.setEnd(element, end || start || 0);\n        sel.addRange(range);\n    }\n\n    const evOptions = {\n        view: doc.defaultView,\n        bubbles: true,\n        composed: true,\n        cancelable: true,\n        isTrusted: true,\n    };\n\n    for (const char of text) {\n        element.dispatchEvent(\n            new KeyboardEvent(\"keydown\", {\n                ...evOptions,\n                key: char,\n            })\n        );\n        element.dispatchEvent(\n            new KeyboardEvent(\"keypress\", {\n                ...evOptions,\n                key: char,\n            })\n        );\n        element.dispatchEvent(\n            new InputEvent(\"input\", {\n                ...evOptions,\n                inputType: \"insertText\",\n                data: char,\n            })\n        );\n        const newNode = doc.createTextNode(char);\n        element.append(newNode);\n        range.setEndAfter(newNode);\n\n        element.dispatchEvent(\n            new KeyboardEvent(\"keyup\", {\n                ...evOptions,\n                key: char,\n            })\n        );\n    }\n}\n\nfunction openEditorPowerBox(element, offsets = null) {\n    return insertText(element, \"/\", offsets);\n}\n\n/* global ace */\n\n// This function allows to use and test the feature that automatically\n// saves when we leave the reportEditor.\n// Implem detail: it is done at willUnmount, so we need to wait for the promise\n// to be sure we leave the tour when the save is done.\nfunction patchReportEditorModelForSilentSave() {\n    const saveProms = [];\n    const { ReportEditorModel } = odoo.loader.modules.get(\n        \"@web_studio/client_action/report_editor/report_editor_model\"\n    );\n    const _unpatch = patch(ReportEditorModel.prototype, {\n        saveReport() {\n            const prom = super.saveReport(...arguments);\n            saveProms.push(prom);\n            return prom;\n        },\n    });\n\n    return {\n        wait: async (unpatch = true) => {\n            await Promise.all(saveProms);\n            if (unpatch) {\n                _unpatch();\n            }\n        },\n        saveProms,\n        unpatch: _unpatch,\n    };\n}\n\nlet silentPatch;\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_basic_report_edition\", {\n    steps: () => [\n        {\n            trigger: \".o_web_studio_sidebar input[id='name']\",\n            run: \"edit modified in test && click body\",\n        },\n        {\n            trigger: \".o_web_studio_menu .breadcrumb-item.active\",\n            run() {\n                assertEqual(this.anchor.textContent, \"modified in test\");\n            },\n        },\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable p:eq(0)\",\n            run: \"editor edited with odoo editor\",\n        },\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable p:eq(2)\",\n            run: \"editor edited with odoo editor 2\",\n        },\n        {\n            // Don't explicitly save, this is a feature\n            trigger: \".o_web_studio_leave a\",\n            run(helpers) {\n                silentPatch = patchReportEditorModelForSilentSave();\n                helpers.click();\n            },\n        },\n        ...stepNotInStudio(),\n        {\n            trigger: \"body\",\n            run() {\n                return silentPatch.wait();\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_basic_report_edition_xml\", {\n    steps: () => [\n        {\n            trigger: \".o_web_studio_sidebar button[name='report_edit_sources']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_xml_resource_selector .o_web_studio_xml_resource_select_menu\",\n            run() {\n                assertEqual(\n                    this.anchor.textContent,\n                    \"web_studio.test_report_document (web_studio.test_report_document)\"\n                );\n            },\n        },\n        {\n            trigger: \".o_web_studio_code_editor.ace_editor\",\n            run() {\n                ace.edit(this.anchor)\n                    .getSession()\n                    .insert(\n                        { row: 2, column: 0 },\n                        '<span class=\"test-added-0\">in document view</span>\\n'\n                    );\n            },\n        },\n        {\n            trigger: \".o_web_studio_xml_resource_selector .o_select_menu_toggler\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-dropdown--menu\",\n            run(helpers) {\n                const mainView = Array.from(\n                    this.anchor.querySelectorAll(\".o_select_menu_item\")\n                ).find(\n                    (el) =>\n                        el.textContent ===\n                        \"web_studio.test_report (web_studio.studio_test_report_view)\"\n                );\n                helpers.click(mainView);\n            },\n        },\n        {\n            trigger: \".o_web_studio_code_editor.ace_editor\",\n            run() {\n                ace.edit(this.anchor)\n                    .getSession()\n                    .insert(\n                        { row: 2, column: 0 },\n                        '<span class=\"test-added-1\">in main view</span>\\n'\n                    );\n            },\n        },\n        {\n            trigger: \".o-web-studio-save-report.btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-save-report:not(.btn-primary)\",\n        },\n        {\n            trigger: \".o-web-studio-report-container :iframe body\",\n            run() {\n                assertEqual(\n                    this.anchor.querySelector(\".test-added-0\").textContent,\n                    \"in document view\"\n                );\n                assertEqual(this.anchor.querySelector(\".test-added-1\").textContent, \"in main view\");\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_basic_report_edition_discard\", {\n    steps: () => [\n        {\n            trigger: \".o_web_studio_sidebar input[id='name']\",\n            run: \"edit modified in test && click body\",\n        },\n        {\n            trigger: \".o_web_studio_menu .breadcrumb-item.active\",\n            run() {\n                assertEqual(this.anchor.textContent, \"modified in test\");\n            },\n        },\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable p:eq(0)\",\n            run: \"editor edited with odoo editor\",\n        },\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable p:eq(2)\",\n            run: \"editor edited with odoo editor 2\",\n        },\n        {\n            trigger: \".o-web-studio-discard-report.btn-secondary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-dialog .btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable p:eq(0)\",\n            run() {\n                assertEqual(this.anchor.textContent, \"\");\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_basic_report_edition_cancel_discard\", {\n    steps: () => [\n        {\n            trigger: \".o_web_studio_sidebar input[id='name']\",\n            run: \"edit modified in test && click body\",\n        },\n        {\n            trigger: \".o_web_studio_menu .breadcrumb-item.active\",\n            run() {\n                assertEqual(this.anchor.textContent, \"modified in test\");\n            },\n        },\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable p:eq(0)\",\n            run: \"editor edited with odoo editor\",\n        },\n        {\n            trigger: \".o-web-studio-discard-report.btn-secondary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-dialog .btn-secondary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable p:eq(0)\",\n            run() {\n                assertEqual(this.anchor.textContent, \"edited with odoo editor\");\n            },\n        },\n        {\n            trigger: \".o-web-studio-save-report.btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-save-report:not(.btn-primary)\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_basic_report_edition_xml_discard\", {\n    steps: () => [\n        {\n            trigger: \".o_web_studio_sidebar button[name='report_edit_sources']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_code_editor.ace_editor\",\n            run() {\n                ace.edit(this.anchor)\n                    .getSession()\n                    .insert({ row: 2, column: 0 }, '<span class=\"test-added\">in main view</span>');\n            },\n        },\n        {\n            trigger: \".o-web-studio-discard-report.btn-secondary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-report-container :iframe body\",\n            run() {\n                const element = this.anchor.querySelector(\".test-added\");\n                if (element) {\n                    throw new Error(\"The changes should have been discarded\");\n                }\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_basic_report_edition_error\", {\n    steps: () => [\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable p:eq(0)\",\n            run: \"editor edited with odoo editor\",\n        },\n        {\n            // Brutally add a t-else: this will crash in python on save\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable\",\n            run() {\n                const editor = editorsWeakMap.get(this.anchor.ownerDocument);\n                const telse = editor.document.createElement(\"t\");\n                telse.setAttribute(\"t-else\", \"\");\n                editor.shared.dom.insert(telse);\n                editor.shared.history.addStep();\n            },\n        },\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable p:eq(2)\",\n            run: \"editor edited with odoo editor 2\",\n        },\n        {\n            trigger: \".o-web-studio-save-report.btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_notification .o_notification_title\",\n            run() {\n                assertEqual(this.anchor.textContent, \"Report edition failed\");\n            },\n        },\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable p:eq(0)\",\n            run() {\n                // The iframe shouldn't have been reset after an error\n                assertEqual(this.anchor.textContent, \"edited with odoo editor\");\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_basic_report_edition_xml_error\", {\n    steps: () => [\n        {\n            trigger: \".o_web_studio_sidebar button[name='report_edit_sources']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_code_editor.ace_editor\",\n            run() {\n                ace.edit(this.anchor)\n                    .getSession()\n                    .insert(\n                        { row: 2, column: 0 },\n                        '<span t-else=\"\" class=\"test-added\">in main view</span>'\n                    );\n            },\n        },\n        {\n            trigger: \".o-web-studio-save-report.btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_notification .o_notification_title\",\n            run() {\n                assertEqual(this.anchor.textContent, \"Report edition failed\");\n            },\n        },\n        {\n            trigger: \".o-web-studio-report-container :iframe body\",\n            run() {\n                const element = this.anchor.querySelector(\".test-added\");\n                if (element) {\n                    throw new Error(\"The changes should have been discarded\");\n                }\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_report_reset_archs\", {\n    steps: () => [\n        {\n            trigger: \".o_web_studio_sidebar button[name='report_reset_archs']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-footer\",\n            run(helpers) {\n                const button = Array.from(this.anchor.querySelectorAll(\"button\")).find(\n                    (el) => el.textContent === \"Reset report\" && el.classList.contains(\"btn-danger\")\n                );\n                helpers.click(button);\n            },\n        },\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable p:eq(1)\",\n            run() {\n                assertEqual(this.anchor.textContent, \"from file\");\n            },\n        },\n    ],\n});\n\nlet downloadProm;\nconst steps = [];\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_print_preview\", {\n    steps: () => [\n        {\n            trigger: \".o_web_studio_sidebar button[name='report_print_preview']\",\n            run(helpers) {\n                downloadProm = new Promise((resolve) => {\n                    const unpatch = patch(download, {\n                        _download(options) {\n                            steps.push(\"download report\");\n                            const context = JSON.parse(options.data.context);\n                            assertEqual(context[\"report_pdf_no_attachment\"], true);\n                            assertEqual(context[\"discard_logo_check\"], true);\n                            assertEqual(context[\"active_ids\"].length, 1);\n                            unpatch();\n                            resolve();\n                        },\n                    });\n                });\n                return helpers.click();\n            },\n        },\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg\",\n            async run() {\n                await downloadProm;\n                assertEqual(steps.length, 1);\n                assertEqual(steps[0], \"download report\");\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_table_rendering\", {\n    steps: () => [\n        {\n            trigger:\n                \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable .valid_table\",\n            run() {\n                assertEqual(\n                    this.anchor.outerHTML.replace(/\\n\\s*/g, \"\"),\n                    `<table class=\"valid_table\" o-diff-key=\"3\">\n                        <tbody o-diff-key=\"4\"><tr o-diff-key=\"5\"><td o-diff-key=\"6\">I am valid</td></tr>\n                    </tbody></table>`.replace(/\\n\\s*/g, \"\")\n                );\n            },\n        },\n        {\n            trigger:\n                \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable .invalid_table\",\n            run() {\n                assertEqual(\n                    this.anchor.outerHTML.replace(/\\n\\s*/g, \"\"),\n                    `<q-table class=\"invalid_table oe_unbreakable\" o-diff-key=\"7\" style=\"--q-table-col-count: 1;\">\n                    <t t-foreach=\"doc.child_ids\" t-as=\"child\" o-diff-key=\"8\" oe-context=\"{&quot;docs&quot;: {&quot;model&quot;: &quot;res.partner&quot;, &quot;name&quot;: &quot;Contact&quot;, &quot;in_foreach&quot;: false}, &quot;company&quot;: {&quot;model&quot;: &quot;res.company&quot;, &quot;name&quot;: &quot;Companies&quot;, &quot;in_foreach&quot;: false}, &quot;doc&quot;: {&quot;model&quot;: &quot;res.partner&quot;, &quot;name&quot;: &quot;Contact&quot;, &quot;in_foreach&quot;: true}, &quot;child&quot;: {&quot;model&quot;: &quot;res.partner&quot;, &quot;name&quot;: &quot;Contact&quot;, &quot;in_foreach&quot;: true}}\">\n                        <q-tr o-diff-key=\"9\" class=\"oe_unbreakable\"><q-td o-diff-key=\"10\" class=\"oe_unbreakable\">I am not valid</q-td></q-tr>\n                    </t>\n                </q-table>`.replace(/\\n\\s*/g, \"\")\n                );\n            },\n        },\n        {\n            trigger:\n                \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable .invalid_table q-td\",\n            run: \"editor edited with odooEditor\",\n        },\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable p:eq(1)\",\n            run: \"editor p edited with odooEditor\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar input[id='name']\",\n            run: \"edit modified && click body\",\n        },\n        {\n            trigger: \".o-web-studio-save-report.btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-save-report:not(.btn-primary)\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_field_placeholder\", {\n    steps: () => [\n        {\n            // 1 sec delay to make sure we call the download route\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable p:eq(2)\",\n            async run(helpers) {\n                const el = this.anchor;\n                openEditorPowerBox(el);\n            },\n        },\n        {\n            trigger: \".o-we-powerbox .o-we-command-description:contains(Insert a field)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-field-dynamic-placeholder\",\n        },\n        {\n            trigger:\n                \".o-web-studio-report-editor-wysiwyg div:has(> .o-web-studio-report-container)\",\n            async run() {\n                const placeholderBox = getBoundingClientRect.call(\n                    document.querySelector(\".o-web-studio-field-dynamic-placeholder\")\n                );\n                assertEqual(this.anchor.scrollTop, 0);\n                this.anchor.scrollTop = 9999;\n                await new Promise(requestAnimationFrame);\n                const newPlaceholderbox = getBoundingClientRect.call(\n                    document.querySelector(\".o-web-studio-field-dynamic-placeholder\")\n                );\n                // The field placeholder should have followed its anchor, and it happens that the anchor's container\n                // has been scrolled, so the anchor has moved upwards (and is actually outside of the viewPort, to the top)\n                assertEqual(placeholderBox.top > newPlaceholderbox.top, true);\n            },\n        },\n        {\n            trigger:\n                \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_popover_search input\",\n            run: \"edit Job Position\",\n        },\n        {\n            trigger:\n                \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_popover_item_name:contains(Job Position)\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_default_value_input input\",\n            run: \"edit some default value\",\n        },\n        {\n            trigger: \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_popover\",\n            run: \"press Enter\",\n        },\n        {\n            trigger:\n                \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable span[t-field='doc.function'][title='doc.function']\",\n        },\n        {\n            trigger: \".o-web-studio-save-report.btn-primary\",\n        },\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable p:eq(0)\",\n            run() {\n                insertText(this.anchor, \"edited with odooEditor\");\n            },\n        },\n        {\n            trigger: \".o-web-studio-save-report.btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-save-report:not(.btn-primary)\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_add_field_blank_report\", {\n    steps: () => [\n        {\n            // edit reports\n            trigger: \".o_web_studio_menu li a:contains(Reports)\",\n            run: \"click\",\n        },\n        {\n            // create a new report\n            trigger: \".o_control_panel .o-kanban-button-new\",\n            run: \"click\",\n        },\n        {\n            // select basic layout\n            trigger: '.o_web_studio_report_layout_dialog div[data-layout=\"web.basic_layout\"]',\n            run: \"click\",\n        },\n        {\n            trigger: \":iframe .odoo-editor-editable .page div\",\n            async run(helpers) {\n                const el = this.anchor;\n                openEditorPowerBox(el);\n            },\n        },\n        {\n            trigger: \".o-we-powerbox .o-we-command-description:contains(Insert a field)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-field-dynamic-placeholder\",\n        },\n        {\n            trigger:\n                \".o-web-studio-report-editor-wysiwyg div:has(> .o-web-studio-report-container)\",\n            async run() {\n                const placeholderBox = getBoundingClientRect.call(\n                    document.querySelector(\".o-web-studio-field-dynamic-placeholder\")\n                );\n                assertEqual(this.anchor.scrollTop, 0);\n                this.anchor.scrollTop = 9999;\n                await new Promise(requestAnimationFrame);\n                const newPlaceholderbox = getBoundingClientRect.call(\n                    document.querySelector(\".o-web-studio-field-dynamic-placeholder\")\n                );\n                // The field placeholder should have followed its anchor, and it happens that the anchor's container\n                // has been scrolled, so the anchor has moved upwards (and is actually outside of the viewPort, to the top)\n                assertEqual(placeholderBox.top > newPlaceholderbox.top, true);\n            },\n        },\n        {\n            trigger:\n                \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_popover_search input\",\n            run: \"edit Job Position\",\n        },\n        {\n            trigger:\n                \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_popover_item_name:contains(Job Position)\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_default_value_input input\",\n            run: \"edit some default value\",\n        },\n        {\n            trigger: \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_popover\",\n            run: \"press Enter\",\n        },\n        {\n            // check that field was added successfully\n            trigger: \":iframe .odoo-editor-editable .page div > span:contains(some default value)\",\n        },\n        {\n            trigger: \":iframe .odoo-editor-editable .page div\",\n            run() {\n                insertText(this.anchor, \"Custo\");\n            },\n        },\n        {\n            trigger: \".o-web-studio-save-report.btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-save-report:not(.btn-primary)\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_toolbar_appearance\", {\n    steps: () => [\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable .to_edit\",\n            run() {\n                const anchor = this.anchor;\n                const doc = anchor.ownerDocument;\n                const selection = doc.getSelection();\n                selection.removeAllRanges();\n                const range = doc.createRange();\n                range.selectNode(anchor.firstChild);\n                selection.addRange(range);\n            },\n        },\n        {\n            trigger: \".o-we-toolbar\",\n        },\n        {\n            trigger: \".o-we-toolbar button[name='bold']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-we-toolbar button[name='italic']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-discard-report\",\n            run: \"click\",\n        },\n        {\n            trigger: \"body:not(:has(.o-we-toolbar))\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_edition_without_lang\", {\n    steps: () => [\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable p:eq(1)\",\n            run() {\n                assertEqual(this.anchor.textContent, \"original term\");\n            },\n        },\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable p:eq(1)\",\n            async run() {\n                insertText(this.anchor, \" edited\");\n            },\n        },\n        {\n            trigger: \".o_web_studio_sidebar button[name='report_edit_sources']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_xml_resource_select_menu button.o_select_menu_toggler\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o-dropdown--menu .o_select_menu_item_label:contains(report_editor_customization_diff)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_code_editor_info .o_field_translate:not(:visible)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_translation_dialog .row:eq(1)\",\n            run() {\n                assertEqual(this.anchor.children[0].textContent.trim(), \"French / Fran\u00e7ais\");\n                assertEqual(this.anchor.children[1].textContent.trim(), \"original term edited\");\n            },\n        },\n        {\n            trigger: \".o_translation_dialog .row:eq(1) textarea\",\n            run: \"edit translated edited term && click body\",\n        },\n        {\n            trigger: \".modal-footer button.btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_editor\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_report_xml_other_record\", {\n    steps: () => [\n        {\n            trigger: \".o_web_studio_sidebar button[name='report_edit_sources']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_xml_editor\",\n        },\n        {\n            trigger: \".o-web-studio-report-container :iframe body p:contains(partner_1)\",\n            run() {\n                assertEqual(\n                    document.querySelector(\".o-web-studio-report-search-record input\").value,\n                    \"partner_1\"\n                );\n            },\n        },\n        {\n            trigger: \".o-web-studio-report-pager .o_pager_next\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-report-container :iframe body p:contains(partner_2)\",\n            run() {\n                assertEqual(\n                    document.querySelector(\".o-web-studio-report-search-record input\").value,\n                    \"partner_2\"\n                );\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_partial_eval\", {\n    steps: () => [\n        {\n            trigger: \".o-web-studio-report-container :iframe .odoo-editor-editable .lol\",\n            run() {\n                const closestContextElement = this.anchor.closest(\"[oe-context]\");\n                const oeContext = closestContextElement.getAttribute(\"oe-context\");\n                const expected = {\n                    docs: { model: \"res.partner\", name: \"Contact\", in_foreach: false },\n                    company: { model: \"res.company\", name: \"Companies\", in_foreach: false },\n                    doc: { model: \"res.partner\", name: \"Contact\", in_foreach: true },\n                    my_children: { model: \"res.partner\", name: \"Contact\", in_foreach: false },\n                    child: { model: \"res.partner\", name: \"Contact\", in_foreach: true },\n                };\n                assertEqual(JSON.stringify(JSON.parse(oeContext)), JSON.stringify(expected));\n            },\n        },\n        {\n            trigger: \".o-web-studio-report-container :iframe .odoo-editor-editable .couic\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_render_multicompany\", {\n    steps: () => [\n        {\n            trigger: \".o-web-studio-report-container :iframe .odoo-editor-editable .test_layout\",\n        },\n        {\n            trigger: \".o-web-studio-report-container :iframe .odoo-editor-editable img\",\n            run() {\n                const cids = cookie.get(\"cids\").split(\"-\");\n                assertEqual(this.anchor.getAttribute(\"src\"), `/logo.png?company=${cids[0]}`);\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_add_non_searchable_field\", {\n    steps: () => [\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable p:eq(2)\",\n            async run(helpers) {\n                const el = this.anchor;\n                openEditorPowerBox(el);\n            },\n        },\n        {\n            trigger: \".o-we-powerbox .o-we-command-description:contains(Insert a field)\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_popover_search input\",\n            run: \"edit New\",\n        },\n        {\n            trigger: \"[data-name=avatar_1024] > button.o_model_field_selector_popover_item_name\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_default_value_input input\",\n            run: \"edit file default value\",\n        },\n        {\n            trigger: \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_popover\",\n            run: \"press Enter\",\n        },\n        {\n            trigger: \".o-web-studio-save-report.btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-save-report:not(.btn-primary)\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_report_edition_binary_field\", {\n    steps: () => [\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable p:eq(2)\",\n            async run(helpers) {\n                const el = this.anchor;\n                openEditorPowerBox(el);\n            },\n        },\n        {\n            trigger: \".o-we-powerbox .o-we-command-description:contains(Insert a field)\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_popover_search input\",\n            run: \"edit Company\",\n        },\n        {\n            trigger: \"[data-name=company_id] > button.o_model_field_selector_popover_item_relation\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_popover_search input\",\n            run: \"edit New File\",\n        },\n        {\n            trigger:\n                \".o_model_field_selector_popover_item_name:contains(New File):not(:contains(filename))\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_default_value_input input\",\n            run: \"edit file default value\",\n        },\n        {\n            trigger: \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_popover\",\n            run: \"press Enter\",\n        },\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable p:eq(2)\",\n            async run(helpers) {\n                const el = this.anchor;\n                openEditorPowerBox(el, { start: nodeSize(el) }); // after the file field\n            },\n        },\n        {\n            trigger: \".o-we-powerbox .o-we-command-description:contains(Insert a field)\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_popover_search input\",\n            run: \"edit Company\",\n        },\n        {\n            trigger: \"[data-name=company_id] > button.o_model_field_selector_popover_item_relation\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_popover_search input\",\n            run: \"edit New Image\",\n        },\n        {\n            trigger: \".o_model_field_selector_popover_item_name:contains(New Image)\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_default_value_input input\",\n            run: \"edit image default value\",\n        },\n        {\n            trigger: \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_popover\",\n            run: \"press Enter\",\n        },\n        {\n            trigger: \".o-web-studio-save-report.btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-save-report:not(.btn-primary)\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_report_edition_dynamic_table\", {\n    steps: () => [\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable p:eq(2)\",\n            async run(helpers) {\n                const el = this.anchor;\n                openEditorPowerBox(el);\n            },\n        },\n        {\n            trigger:\n                \".o-we-powerbox .o-we-command-description:contains(Insert a table based on a relational field)\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_popover_search input\",\n            run: \"edit Activities\",\n        },\n        {\n            trigger: \"[data-name=activity_ids] > button.o_model_field_selector_popover_item_name\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_default_value_input input\",\n            run: \"edit First Column\",\n        },\n        {\n            trigger: \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_popover\",\n            run: \"press Enter\",\n        },\n        {\n            trigger:\n                \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable table tr td:contains(First Column)\",\n        },\n        {\n            trigger:\n                \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable table tr[t-foreach]\",\n            run() {\n                const el = this.anchor;\n                const context = JSON.parse(el.getAttribute(\"oe-context\"));\n                assertEqual(context.x2many_record.model, \"mail.activity\");\n            },\n        },\n        {\n            trigger:\n                \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable table tr td:contains(Insert a field...)\",\n            run() {\n                openEditorPowerBox(this.anchor);\n            },\n        },\n        {\n            trigger: \".o-we-powerbox .o-we-command-description:contains(Insert a field)\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_popover_search input\",\n            run: \"edit Summary\",\n        },\n        {\n            trigger:\n                \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_popover_item_name:contains(Summary)\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_default_value_input input\",\n            run: \"edit Some Summary\",\n        },\n        {\n            trigger: \".o-web-studio-field-dynamic-placeholder .o_model_field_selector_popover\",\n            run: \"press Enter\",\n        },\n        {\n            trigger:\n                \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable table td span[t-field='x2many_record.summary']\",\n        },\n        {\n            trigger: \".o-web-studio-save-report.btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-save-report:not(.btn-primary)\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_saving_xml_editor_reload\", {\n    steps: () => [\n        {\n            trigger: \"button[name='report_edit_sources']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-save-report:not(.btn-primary)\",\n        },\n        {\n            trigger: \".o_web_studio_xml_editor .ace_editor\",\n            run() {\n                ace.edit(this.anchor)\n                    .getSession()\n                    .insert(\n                        { row: 2, column: 0 },\n                        '<span class=\"test-added-0\">in document view</span>\\n'\n                    );\n            },\n        },\n        {\n            trigger: \".o-web-studio-save-report.btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-save-report:not(.btn-primary)\",\n        },\n        {\n            trigger: \".o_web_studio_xml_editor .ace_editor\",\n            run() {\n                const aceValue = ace.edit(this.anchor).getSession().getValue();\n\n                assertEqual(\n                    normalizeXML(aceValue),\n                    normalizeXML(`\n                        <t t-name=\"web_studio.test_report_document\">\n                            <div><p t-field=\"doc.name\"/></div>\n                            <span class=\"test-added-0\">in document view</span>\n                            <p><br/></p>\n                        </t>`)\n                );\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_error_at_loading\", {\n    steps: () => [\n        {\n            trigger: \"body:not(:has(.o_error_dialog)) .o-web-studio-report-editor\",\n            run: \"click\",\n        },\n        {\n            trigger: \":iframe div\",\n            run() {\n                assertEqual(\n                    this.anchor.textContent,\n                    \"The report could not be rendered due to an error\"\n                );\n            },\n        },\n        {\n            trigger: \"button[name='report_edit_sources']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_xml_editor\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_error_at_loading_debug\", {\n    steps: () => [\n        {\n            trigger: \"body:not(:has(.o_error_dialog)) .o-web-studio-report-editor\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-report-container:not(:has(iframe))\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-report-container strong:contains(builtins.ValueError)\",\n            run: \"click\",\n        },\n        {\n            trigger: \"button[name='report_edit_sources']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_xml_editor\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-report-container:not(:has(iframe))\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o-web-studio-report-container strong:contains(odoo.addons.base.models.ir_qweb.QWebException)\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_xml_and_form_diff\", {\n    steps: () => [\n        {\n            trigger: \".o-web-studio-report-editor-wysiwyg :iframe .odoo-editor-editable p:eq(2)\",\n            run() {\n                insertText(this.anchor, \"edited with odooEditor\");\n            },\n        },\n        {\n            trigger: \"button[name='report_edit_sources']\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o_web_studio_xml_resource_select_menu button.o_select_menu_toggler .o_select_menu_toggler_slot\",\n            run() {\n                const currentViewKey = this.anchor.textContent.split(\" (\")[0];\n                assertEqual(\n                    currentViewKey,\n                    \"web_studio.report_editor_customization_diff.view._web_studio.test_report_document\"\n                );\n            },\n        },\n        {\n            trigger: \".o_web_studio_xml_resource_select_menu button.o_select_menu_toggler\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o-dropdown--menu .o_select_menu_item_label:contains(web_studio.test_report_document)\",\n            run: \"click\",\n        },\n        {\n            trigger: \"button[name='view_diff']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view table.diff\",\n            run() {\n                assertEqual(\n                    document.querySelector(\".o_form_view .o_field_widget[name='view_name']\")\n                        .textContent,\n                    \"web_studio.test_report_document\"\n                );\n                assertEqual(\n                    document.querySelector(\n                        \".o_form_view .o_field_widget[name='compare_view_id'] input\"\n                    ).value,\n                    \"web_studio_backup__web_studio.test_report_document\"\n                );\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_record_model_differs_from_action\", {\n    steps: () => {\n        const stepsToAssert = [];\n\n        return [\n            {\n                trigger: \".o_studio_report_kanban_view\",\n                run() {\n                    const { ReportEditorModel } = odoo.loader.modules.get(\n                        \"@web_studio/client_action/report_editor/report_editor_model\"\n                    );\n\n                    patch(ReportEditorModel.prototype, {\n                        async loadReportEditor() {\n                            await super.loadReportEditor(...arguments);\n                            stepsToAssert.push(\n                                `report editor loaded. actionModel: \"${this._services.studio.editedAction.res_model}\". reportModel: \"${this.reportResModel}\"`\n                            );\n                        },\n                    });\n                },\n            },\n            {\n                trigger: \".o_studio_report_kanban_view .o_searchview input\",\n                run: \"fill dummy test\",\n            },\n            {\n                trigger:\n                    \".o_studio_report_kanban_view .o_searchview .o_menu_item:contains(Report):contains(dummy test)\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o_facet_remove\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o_kanban_record:contains(dummy test)\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o-web-studio-report-editor-wysiwyg\",\n                run() {\n                    assertEqual(\n                        JSON.stringify(stepsToAssert),\n                        JSON.stringify([\n                            `report editor loaded. actionModel: \"res.partner\". reportModel: \"x_dummy.test\"`,\n                        ])\n                    );\n                },\n            },\n        ];\n    },\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_remove_branding_on_copy\", {\n    steps: () => [\n        {\n            trigger: \"body :iframe .odoo-editor-editable#wrapwrap\",\n            async run() {\n                const doc = this.anchor.ownerDocument;\n                const editor = editorsWeakMap.get(doc);\n                const originNode = this.anchor.querySelector(`[ws-view-id]`);\n                const copy = originNode.cloneNode(true);\n                originNode.insertAdjacentElement(\"afterend\", copy);\n                editor.shared.history.addStep();\n                // Wait for a full macrotask tick and a frame to let the mutation observer\n                // of the ReportEditorWysiwyg to catch up on the change and finish its operations\n                await nextTick();\n                const attributeCopy = {};\n                for (const attr of copy.attributes) {\n                    attributeCopy[attr.name] = attr.value;\n                }\n                assertEqual(JSON.stringify(attributeCopy), `{\"contenteditable\":\"true\"}`);\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_different_view_document_name\", {\n    steps: () => [\n        {\n            trigger: \".o_web_studio_sidebar button[name='report_edit_sources']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_xml_resource_selector .o_select_menu_toggler\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-dropdown--menu\",\n            run() {\n                const sources = Array.from(this.anchor.querySelectorAll(\".o_select_menu_item\")).map(\n                    (e) => e.textContent\n                );\n                assertEqual(\n                    sources.includes(\n                        \"Uses: web_studio.test_report_document (web_studio.test_report_document_1)\"\n                    ),\n                    true\n                );\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_edit_main_arch\", {\n    steps: () => [\n        {\n            trigger: \":iframe .odoo-editor-editable .outside-t-call\",\n            async run() {\n                const doc = this.anchor.ownerDocument;\n                const editor = editorsWeakMap.get(doc);\n                const newNode = doc.createElement(\"div\");\n                newNode.classList.add(\"added\");\n                this.anchor.insertAdjacentElement(\"beforebegin\", newNode);\n                editor.shared.history.addStep();\n                await nextTick();\n            },\n        },\n        {\n            trigger: \".o-web-studio-save-report.btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-save-report:not(.btn-primary)\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_edit_in_t_call\", {\n    steps: () => [\n        {\n            trigger: \":iframe .odoo-editor-editable .in-t-call\",\n            async run() {\n                const doc = this.anchor.ownerDocument;\n                const editor = editorsWeakMap.get(doc);\n                const newNode = doc.createElement(\"div\");\n                newNode.classList.add(\"added\");\n                this.anchor.insertAdjacentElement(\"beforebegin\", newNode);\n                editor.shared.history.addStep();\n                await nextTick();\n            },\n        },\n        {\n            trigger: \".o-web-studio-save-report.btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-save-report:not(.btn-primary)\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_edit_main_and_in_t_call\", {\n    steps: () => [\n        {\n            trigger: \":iframe .odoo-editor-editable#wrapwrap\",\n            async run() {\n                const doc = this.anchor.ownerDocument;\n                const editor = editorsWeakMap.get(doc);\n                const newNode0 = doc.createElement(\"div\");\n                newNode0.classList.add(\"added0\");\n                const target0 = this.anchor.querySelector(\".outside-t-call\");\n                target0.insertAdjacentElement(\"beforebegin\", newNode0);\n                editor.shared.history.addStep();\n                await nextTick();\n                const newNode1 = doc.createElement(\"div\");\n                newNode1.classList.add(\"added1\");\n                const target1 = this.anchor.querySelector(\".in-t-call\");\n                target1.insertAdjacentElement(\"beforebegin\", newNode1);\n                editor.shared.history.addStep();\n                await nextTick();\n            },\n        },\n        {\n            trigger: \".o-web-studio-save-report.btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-save-report:not(.btn-primary)\",\n            run() {},\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_image_crop\", {\n    steps: () => [\n        {\n            trigger: \"body :iframe .odoo-editor-editable .myimg\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-we-toolbar button[name='image_crop']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-main-components-container .o_we_crop_widget .cropper-container\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_translations_are_copied\", {\n    steps: () => [\n        {\n            trigger: \"body :iframe .odoo-editor-editable#wrapwrap div:contains(term2)\",\n            run() {\n                const doc = this.anchor.ownerDocument;\n                const editor = editorsWeakMap.get(doc);\n                const newNode = doc.createElement(\"div\");\n                (newNode.textContent = \"term3 from edition\"),\n                    this.anchor.insertAdjacentElement(\"beforebegin\", newNode);\n                editor.shared.history.addStep();\n                return nextTick();\n            },\n        },\n        {\n            trigger: \".o-web-studio-save-report.btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-save-report:not(.btn-primary)\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_reports_view_concurrence\", {\n    steps: () => [\n        {\n            trigger: \".o_menu_sections li:contains('Reports')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_kanban_record[data-id] \",\n            run: \"dblclick\",\n        },\n        {\n            trigger: \".o-web-studio-report-editor\",\n        },\n    ],\n});\n", "/** @odoo-module */\nimport { registry } from \"@web/core/registry\";\nimport { stepNotInStudio, assertEqual } from \"@web_studio/../tests/tours/tour_helpers\";\nimport { queryFirst, drag, waitFor } from \"@odoo/hoot-dom\";\n\nregistry\n    .category(\"web_tour.tours\")\n    .add(\"web_studio_test_form_view_not_altered_by_studio_xml_edition\", {\n        url: \"/odoo?debug=1\",\n        steps: () => [\n            {\n                trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o_form_view .o_form_editable\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o_web_studio_navbar_item button:enabled\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o_web_studio_sidebar .o_web_studio_view\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o_web_studio_open_xml_editor\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o_web_studio_code_editor_info\",\n            },\n            {\n                trigger: \".o_web_studio_leave\",\n                run: \"click\",\n            },\n            ...stepNotInStudio(\".o_form_view .o_form_editable\"),\n        ],\n    });\n\n/* global ace */\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_edit_with_xml_editor\", {\n    url: \"/odoo?debug=1\",\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".someDiv:not(:visible)\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_view\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_open_xml_editor\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_xml_editor\",\n        },\n        {\n            trigger: \".o_web_studio_xml_resource_selector .o_select_menu_toggler\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-dropdown--menu .o_select_menu_item:contains(Odoo Studio)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".ace_content\",\n            run() {\n                ace.edit(document.querySelector(\".ace_editor\")).setValue(\"<data/>\");\n            },\n        },\n        {\n            trigger: \".o_web_studio_xml_editor .o_web_studio_xml_resource_selector .btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_snackbar:not(:has(.fa-spin))\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n            run() {\n                if (document.querySelector(\".someDiv\")) {\n                    throw new Error(\"The edition of the view's arch via the xml editor failed\");\n                }\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_enter_x2many_edition_and_add_field\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view .o_form_editable\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor .o_field_widget[name='user_ids']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-edit-x2manys-buttons\",\n        },\n        {\n            trigger: \".o_web_studio_editX2Many[data-type='form']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_view_controller.o_form_view.test-user-form\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_existing_fields_header\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_existing_fields_section:not(.d-none)\",\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_existing_fields_section .o_web_studio_component:contains(User log entries)\",\n            run() {\n                queryFirst(\n                    \".o_web_studio_sidebar .o_web_studio_existing_fields_section .o_web_studio_component:contains(User log entries)\"\n                ).scrollIntoView();\n            },\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_existing_fields_section .o_web_studio_component:contains(User log entries)\",\n            run: \"drag_and_drop(.o_web_studio_form_view_editor .o_web_studio_hook:eq(1))\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor .o_field_widget[name='log_ids']\",\n            run() {\n                const countFields = document.querySelectorAll(\n                    \".o_web_studio_form_view_editor .o_field_widget\"\n                ).length;\n                if (!countFields === 2) {\n                    throw new Error(\"There should be 2 fields in the form view\");\n                }\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_enter_x2many_auto_inlined_subview\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view .o_form_editable\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o_web_studio_form_view_editor .o_field_widget[name='user_ids'] .o_field_x2many_list\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-edit-x2manys-buttons\",\n        },\n        {\n            trigger: \".o_web_studio_editX2Many[data-type='list']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_view_controller.o_list_view.test-user-list\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_existing_fields_header\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_existing_fields_section:not(.d-none)\",\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_existing_fields_section .o_web_studio_component:contains(User log entries)\",\n            run() {\n                queryFirst(\n                    \".o_web_studio_sidebar .o_web_studio_existing_fields_section .o_web_studio_component:contains(User log entries)\"\n                ).scrollIntoView();\n            },\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_existing_fields_section .o_web_studio_component:contains(User log entries)\",\n            run: \"drag_and_drop(.o_web_studio_list_view_editor .o_web_studio_hook:eq(1))\",\n        },\n        {\n            trigger: \".o_web_studio_list_view_editor th[data-name='log_ids']\",\n            run() {\n                const countFields = document.querySelectorAll(\n                    \".o_web_studio_form_view_editor th[data-name]\"\n                ).length;\n                if (!countFields === 2) {\n                    throw new Error(\"There should be 2 fields in the form view\");\n                }\n            },\n        },\n    ],\n});\n\nregistry\n    .category(\"web_tour.tours\")\n    .add(\"web_studio_enter_x2many_auto_inlined_subview_with_multiple_field_matching\", {\n        steps: () => [\n            {\n                trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o_form_view .o_form_editable\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o_web_studio_navbar_item button:enabled\",\n                run: \"click\",\n            },\n            {\n                trigger:\n                    \".o_web_studio_form_view_editor .o_field_widget[name='user_ids']:eq(1) .o_field_x2many_list\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o-web-studio-edit-x2manys-buttons\",\n            },\n            {\n                trigger: \".o_web_studio_editX2Many[data-type='list']\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o_view_controller.o_list_view.test-user-list\",\n            },\n            {\n                trigger: \".o_web_studio_sidebar .o_web_studio_existing_fields_header\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o_web_studio_existing_fields_section:not(.d-none)\",\n            },\n            {\n                trigger:\n                    \".o_web_studio_sidebar .o_web_studio_existing_fields_section .o_web_studio_component:contains(User log entries)\",\n                run() {\n                    queryFirst(\n                        \".o_web_studio_sidebar .o_web_studio_existing_fields_section .o_web_studio_component:contains(User log entries)\"\n                    ).scrollIntoView();\n                },\n            },\n            {\n                trigger:\n                    \".o_web_studio_sidebar .o_web_studio_existing_fields_section .o_web_studio_component:contains(User log entries)\",\n                run: \"drag_and_drop(.o_web_studio_list_view_editor .o_web_studio_hook:eq(1))\",\n            },\n            {\n                trigger: \".o_web_studio_list_view_editor th[data-name='log_ids']\",\n                run() {\n                    const countFields = document.querySelectorAll(\n                        \".o_web_studio_form_view_editor th[data-name]\"\n                    ).length;\n                    if (!countFields === 2) {\n                        throw new Error(\"There should be 2 fields in the form view\");\n                    }\n                },\n            },\n        ],\n    });\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_boolean_field_drag_and_drop\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view .o_form_editable\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_new_fields .o_web_studio_field_boolean\",\n            run: \"drag_and_drop(.o_web_studio_form_view_editor .o_web_studio_hook:eq(0))\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor\",\n        },\n        {\n            trigger: \".o_wrap_field_boolean .o_wrap_label\",\n            run: \"drag_and_drop(.o_web_studio_form_view_editor .o_web_studio_hook:eq(2))\",\n        },\n        {\n            trigger: \".o_wrap_label:eq(1):contains('New CheckBox')\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_field_with_group\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_list_view\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_list_view_editor th[data-name='function']\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_existing_fields_header\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_existing_fields_section:not(.d-none)\",\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_existing_fields_section .o_web_studio_component:contains(Website Link)\",\n            run() {\n                queryFirst(\n                    \".o_web_studio_sidebar .o_web_studio_existing_fields_section .o_web_studio_component:contains(Website Link)\"\n                ).scrollIntoView();\n            },\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_existing_fields_section .o_web_studio_component:contains(Website Link)\",\n            run: \"drag_and_drop(.o_web_studio_list_view_editor th.o_web_studio_hook:eq(2))\",\n        },\n        {\n            trigger:\n                \".o_web_studio_list_view_editor th.o_web_studio_hook:not(.o_web_studio_nearest_hook)\",\n        },\n        {\n            trigger: \".o_web_studio_list_view_editor th[data-name='website']\",\n            run() {\n                const countFields = document.querySelectorAll(\n                    \".o_web_studio_list_view_editor th[data-name]\"\n                ).length;\n                if (!countFields === 3) {\n                    throw new Error(\"There should be 3 fields in the form view\");\n                }\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_set_tree_node_conditional_invisibility\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_list_view\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_list_view_editor th[data-name='title']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_attrs[data-type=invisible]\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_model_field_selector_value\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_model_field_selector_popover_item_name:contains('Display Name')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_tree_editor_condition input.o_input\",\n            run: \"edit Robert && click body\",\n        },\n        {\n            trigger: \".modal-footer .btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_list_view_editor th[data-name='title']\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_elements_with_groups_form\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_existing_fields_header\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_existing_fields_section:not(.d-none)\",\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_existing_fields_section .o_web_studio_component:contains(Website Link)\",\n            run() {\n                queryFirst(\n                    \".o_web_studio_sidebar .o_web_studio_existing_fields_section .o_web_studio_component:contains(Website Link)\"\n                ).scrollIntoView();\n            },\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_existing_fields_section .o_web_studio_component:contains(Website Link)\",\n            run: \"drag_and_drop(.o_web_studio_form_view_editor .o_inner_group .o_web_studio_hook:eq(1))\",\n        },\n        {\n            trigger:\n                \".o_web_studio_form_view_editor .o_web_studio_hook:not(.o_web_studio_nearest_hook)\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor .o_field_widget[name='website']:not(:visible)\",\n            run() {\n                const countFields = document.querySelectorAll(\n                    \".o_web_studio_form_view_editor .o_field_widget[name]\"\n                ).length;\n                if (!countFields === 2) {\n                    throw new Error(\"There should be 2 fields in the form view\");\n                }\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"test_element_group_in_sidebar\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view .o_form_editable\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor .o_field_widget[name='display_name']\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor .o_field_widget[name='display_name']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_field_many2many_tags[name='groups_id'] .badge\",\n            run() {\n                const tag = document.querySelector(\n                    \".o_field_many2many_tags[name='groups_id'] .badge\"\n                );\n                if (!tag || !tag.textContent.includes(\"Test Group\")) {\n                    throw new Error(\"The groups should be displayed in the sidebar\");\n                }\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_custom_selection_field_edit_values\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_new_fields .o_web_studio_field_selection\",\n            run: \"drag_and_drop(.o_web_studio_hook:eq(0))\",\n        },\n        {\n            trigger: \".o_web_studio_add_selection .o-web-studio-interactive-list-item-input\",\n            run: \"edit some value\",\n        },\n        {\n            trigger: \".modal-footer .btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \"body:not(:has(.modal))\",\n        },\n        {\n            trigger: \".o_web_studio_leave\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor .o_wrap_input:has(.o_field_selection)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_edit_selection_values\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".modal:not(.o_inactive_modal) .o_web_studio_add_selection .o-web-studio-interactive-list-item-input\",\n            run: \"edit another value cancel\",\n        },\n        {\n            trigger: \".o_web_studio_add_selection .o-web-studio-interactive-list-edit-item\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_selection_editor li:nth-child(2)\",\n            async run() {\n                assertEqual(this.anchor.textContent, \"another value cancel\");\n            },\n        },\n        {\n            trigger: \".modal-footer .btn-secondary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_edit_selection_values\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_selection_editor li\",\n            run() {\n                assertEqual(this.anchor.textContent, \"some value\");\n            },\n        },\n        {\n            trigger:\n                \".modal:not(.o_inactive_modal) .o_web_studio_add_selection .o-web-studio-interactive-list-item-input\",\n            run: \"edit another value\",\n        },\n        {\n            trigger: \".modal-footer .btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \"body:not(:has(.modal))\",\n        },\n        {\n            trigger: \".o_web_studio_leave\",\n            run: \"click\",\n        },\n        ...stepNotInStudio(),\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_create_one2many_lines_then_edit_name\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_new_fields .o_web_studio_field_lines\",\n            run: \"drag_and_drop(.o_web_studio_hook:eq(0))\",\n        },\n        {\n            trigger: \".o_field_x2many_list\",\n        },\n        {\n            trigger: \".o_form_label\",\n            timeout: 20000,\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_properties.active\",\n        },\n        {\n            trigger: \"input[name='string']\",\n            run: \"edit new name && click body\",\n        },\n        {\n            trigger: \".o_web_studio_leave\",\n            timeout: 20000,\n            run: \"click\",\n        },\n        ...stepNotInStudio(\".o_form_view\"),\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_address_view_id_no_edit\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n        },\n        {\n            trigger: \".o_address_format\",\n            run: function () {\n                if (\n                    this.anchor.querySelectorAll(\"[name=lang]\").length ||\n                    !this.anchor.querySelectorAll(\"[name=street]\").length\n                ) {\n                    throw new Error(\n                        \"The address view id set on the company country should be displayed\"\n                    );\n                }\n            },\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_view_renderer\",\n        },\n        {\n            trigger: \".o_address_format\",\n            run: function () {\n                if (\n                    this.anchor.querySelectorAll(\"[name=street]\").length ||\n                    !this.anchor.querySelectorAll(\"[name=lang]\").length\n                ) {\n                    throw new Error(\n                        \"The address view id set on the company country shouldn't be editable\"\n                    );\n                }\n            },\n        },\n        {\n            trigger: \".o_web_studio_leave\",\n            run: \"click\",\n        },\n        ...stepNotInStudio(\".o_form_view\"),\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_create_new_model_from_existing_view\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_kanban_view\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_create_new_model\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-dialog\",\n        },\n        {\n            trigger: \"input[name='model_name']\",\n            run: \"edit new model\",\n        },\n        {\n            trigger: \".confirm_button\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_model_configurator_next\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_create_model_with_clickable_stages\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_create_new_model\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-dialog\",\n        },\n        {\n            trigger: \"input[name='model_name']\",\n            run: \"edit new model\",\n        },\n        {\n            trigger: \".modal:not(.o_inactive_modal) .confirm_button\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal:not(.o_inactive_modal) .modal-body #use_stages\",\n            run: \"check\",\n        },\n        {\n            trigger: \".modal:not(.o_inactive_modal) .o_web_studio_model_configurator_next\",\n            run: \"click\",\n        },\n        {\n            content: \"Wait the modal is closed before continue\",\n            trigger: \"body:not(:has(.modal))\",\n        },\n        {\n            trigger: \".o_web_studio_leave\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n        },\n        {\n            trigger: \"input#x_name_0\",\n            run: \"edit new record\",\n        },\n        {\n            trigger: \".o_arrow_button:contains(In Progress)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_arrow_button_current:contains(In Progress)\",\n        },\n        {\n            trigger: \".o_form_button_save:not(:visible)\",\n            run: \"click\",\n        },\n        {\n            // trigger: \".o_back_button\", TODO: add breacrumb to access multi-record view when closing studio\n            trigger: \".o_nav_entry:contains(new model)\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o_kanban_group:contains(In Progress) div[name=studio_auto_kanban_header] span.h5:contains(new record)\",\n        },\n    ],\n});\n\nregistry\n    .category(\"web_tour.tours\")\n    .add(\"web_studio_test_enter_x2many_edition_with_multiple_subviews\", {\n        steps: () => [\n            {\n                trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o_form_view span:contains('Address Type')\",\n            },\n            {\n                trigger: \".o_web_studio_navbar_item button:enabled\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o_list_renderer span:contains('Address Type')\",\n            },\n            {\n                trigger:\n                    \".o_web_studio_form_view_editor .o_field_widget[name='child_ids'] .o_field_x2many_list\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o-web-studio-edit-x2manys-buttons\",\n            },\n            {\n                trigger: \".o_web_studio_editX2Many[data-type='list']\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o_content > .o_list_renderer span:contains('Address Type')\",\n            },\n        ],\n    });\n\nregistry\n    .category(\"web_tour.tours\")\n    .add(\"web_studio_test_enter_x2many_edition_with_multiple_subviews_correct_xpath\", {\n        steps: () => [\n            {\n                trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o_form_view\",\n            },\n            {\n                trigger: \".o_web_studio_navbar_item button:enabled\",\n                run: \"click\",\n            },\n            {\n                trigger:\n                    \".o_web_studio_form_view_editor .o_field_widget[name='child_ids'] .o_field_x2many_list\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o-web-studio-edit-x2manys-buttons\",\n            },\n            {\n                trigger: \".o_web_studio_editX2Many[data-type='list']\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o_view_controller.o_list_view.test-subview-list\",\n            },\n            {\n                trigger: \".o_web_studio_sidebar .o_web_studio_existing_fields_header\",\n                run: \"click\",\n            },\n            {\n                trigger: \".o_web_studio_sidebar .o_web_studio_existing_fields_section\",\n            },\n            {\n                trigger: `.o_web_studio_sidebar .o_web_studio_existing_fields_section .o_web_studio_component[data-drop='${JSON.stringify(\n                    { fieldName: \"active\" }\n                )}']`,\n                run: \"drag_and_drop(.o_web_studio_hook:eq(0))\",\n            },\n            {\n                content: \"Check that the active field has been added\",\n                trigger: \".o_web_studio_view_renderer .o_list_view thead th[data-name='active']\",\n            },\n        ],\n    });\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_studio_view_is_last\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_existing_fields_header\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_existing_fields_section:not(.d-none)\",\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_existing_fields_section .o_web_studio_component:contains(Website Link)\",\n            run() {\n                queryFirst(\n                    \".o_web_studio_sidebar .o_web_studio_existing_fields_section .o_web_studio_component:contains(Website Link)\"\n                ).scrollIntoView();\n            },\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_existing_fields_section .o_web_studio_component:contains(Website Link)\",\n            run: \"drag_and_drop(.o_web_studio_form_view_editor .o_inner_group .o_web_studio_hook:last)\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor .o_field_widget[name='website']:not(:visible)\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_edit_form_subview_attributes\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o_web_studio_form_view_editor .o_field_widget[name='child_ids'] .o_field_x2many_list\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-edit-x2manys-buttons\",\n        },\n        {\n            trigger: \".o_web_studio_editX2Many[data-type='form']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_view_controller.o_form_view.test-subview-form\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar.o_notebook .nav-link:contains(View)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar input[name='create']:checked\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar input[name='create']:not(:checked)\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_x2many_two_levels_edition\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view .o_form_editable\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor .o_field_widget[name='user_ids']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-web-studio-edit-x2manys-buttons\",\n        },\n        {\n            trigger: \".o_web_studio_editX2Many[data-type='form']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_view_controller.o_form_view.test-subview-form-1\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor .o_field_widget[name='log_ids']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_editX2Many[data-type='form']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_view_controller.o_form_view.test-subview-form-2\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_existing_fields_header\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_existing_fields\",\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_existing_fields_section .o_web_studio_component:contains(Created on)\",\n            run: \"drag_and_drop .o_web_studio_hook\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor [data-field-name='create_date']\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_field_group_studio_no_fetch\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view .o_form_editable\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor\",\n            run() {\n                assertEqual(this.anchor.querySelectorAll(\".o_field_widget\").length, 1);\n                assertEqual(\n                    this.anchor.querySelectorAll(\".o_field_widget\")[0].dataset.studioXpath,\n                    \"/form[1]/field[2]\"\n                );\n            },\n        },\n        {\n            trigger: \".o_web_studio_views_icons a[title='List']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_list_view_editor\",\n            run() {\n                assertEqual(this.anchor.querySelectorAll(\"th:not(.o_web_studio_hook)\").length, 1);\n                assertEqual(\n                    this.anchor.querySelectorAll(\"th:not(.o_web_studio_hook)\")[0].dataset\n                        .studioXpath,\n                    \"/list[1]/field[2]\"\n                );\n            },\n        },\n        {\n            trigger: \".o_web_studio_views_icons a[title='Kanban']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_kanban_view_editor\",\n            run() {\n                assertEqual(\n                    this.anchor.querySelectorAll(\n                        \".o_kanban_record:not(.o_kanban_demo):not(.o_kanban_ghost) .o-web-studio-editor--element-clickable\"\n                    ).length,\n                    1\n                );\n                assertEqual(\n                    this.anchor\n                        .querySelectorAll(\n                            \".o_kanban_record:not(.o_kanban_demo):not(.o_kanban_ghost) .o-web-studio-editor--element-clickable\"\n                        )[0]\n                        .getAttribute(\"studioxpath\"),\n                    \"/kanban[1]/t[1]/field[2]\"\n                );\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_move_similar_field\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor\",\n        },\n        {\n            trigger: \".o_notebook_headers a:contains('two')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".tab-pane.active [data-field-name=display_name]\",\n            run: \"drag_and_drop(.o_web_studio_form_view_editor .o_web_studio_hook:eq(1))\",\n        },\n        {\n            trigger: \".o_web_studio_leave\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_related_file\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_user_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n        },\n        {\n            content: \"second\",\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor\",\n        },\n        {\n            trigger: \".o_web_studio_field_related\",\n            run: \"drag_and_drop(.o_inner_group)\",\n        },\n        {\n            trigger: \".modal-dialog\",\n        },\n        {\n            trigger: \".o_model_field_selector_value\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_model_field_selector_popover\",\n        },\n        {\n            trigger: \".o_model_field_selector_popover_search input\",\n            run: \"edit Related Partner\",\n        },\n        {\n            trigger: \"[data-name=partner_id] > button.o_model_field_selector_popover_item_relation\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_model_field_selector_popover_title:contains(Related Partner)\",\n        },\n        {\n            trigger: \".o_model_field_selector_popover_search input\",\n            run: \"edit New File\",\n        },\n        {\n            trigger:\n                \".o_model_field_selector_popover_item_name:contains(New File):not(:contains(filename))\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-footer .btn-primary:first\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_leave\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_undo_new_field\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view .o_form_editable\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_new_fields .o_web_studio_field_integer\",\n            run: \"drag_and_drop(.o_web_studio_form_view_editor .o_web_studio_hook:eq(1))\",\n        },\n        {\n            trigger: \"button.o_web_studio_undo.o_web_studio_active\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_leave\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_change_lone_attr_modifier_form\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor\",\n        },\n        {\n            trigger: \".o_field_widget[name='name']\",\n            run: \"click\",\n        },\n        {\n            trigger: `.o_web_studio_sidebar input[name=\"required\"]`,\n        },\n        {\n            trigger: \".o_web_studio_sidebar\",\n            run() {\n                const required = this.anchor.querySelector(`input[name=\"required\"]`);\n                assertEqual(required.checked, true);\n            },\n        },\n        {\n            trigger: '.o_web_studio_sidebar input[name=\"required\"]',\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor:not(:has(.o_required_modifier))\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar\",\n            run() {\n                const required = this.anchor.querySelector(`input[name=\"required\"]`);\n                assertEqual(required.checked, false);\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_new_field_rename_description\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_component.o_web_studio_field_char\",\n            run: \"drag_and_drop(.o_web_studio_form_view_editor .o_web_studio_hook:eq(1))\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar input[name='string']\",\n            run: \"edit my new field && click body\",\n        },\n        {\n            trigger:\n                \".o_web_studio_form_view_editor label[for='x_studio_my_new_field_0']:contains(my new field)\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_edit_digits_option\", {\n    url: \"/odoo\",\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view .o_form_editable\",\n            run: \"click\",\n        },\n\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_view_renderer\",\n        },\n        {\n            trigger: \"[name=partner_latitude]\",\n            run: \"click\",\n        },\n        {\n            trigger: \"input#digits\",\n            run: \"edit 2 && click body\",\n        },\n        {\n            trigger: \".o_web_studio_leave\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_no_fetch_subview\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \"input#name_0\",\n            run: \"edit value\",\n        },\n        {\n            trigger: \"button.o_form_button_save\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_new_fields .o_web_studio_field_many2many\",\n            run: \"drag_and_drop(.o_web_studio_form_view_editor .o_web_studio_hook:eq(0))\",\n        },\n        {\n            trigger: \".o_record_selector input\",\n            run: \"edit Contact\",\n        },\n        {\n            trigger: \"a.dropdown-item:contains(Contact)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-footer button.btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_wrap_field label:contains('New Many2Many')\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_button_rainbow_effect\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view .o_form_editable\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: `.o_web_studio_view_renderer button[name=\"open_commercial_entity\"]`,\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar #effect\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar #rainbow_message\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar\",\n            run() {\n                const blob = new Blob(\n                    [\n                        \"iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAF0lEQVR4nGJxKFrEwMDAxAAGgAAAAP//D+IBWx9K7TUAAAAASUVORK5CYII=\",\n                    ],\n                    { type: \"image/png\" }\n                );\n                const file = new File([blob], \"my_studio_image.png\");\n\n                const fileInput = document.querySelector(\n                    \".o_web_studio_sidebar .o_file_input input\"\n                );\n                const dataTransfer = new DataTransfer();\n                dataTransfer.items.add(file);\n                fileInput.files = dataTransfer.files;\n                fileInput.dispatchEvent(new Event(\"change\"));\n            },\n        },\n        {\n            trigger: \".o_web_studio_sidebar img[src^='/web/content']\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio.test_res_users_fake_fields\", {\n    steps: () => [\n        {\n            trigger: \".o_web_studio_existing_fields_header\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_existing_fields\",\n            run() {\n                const elements = [...document.querySelectorAll(\".o_web_studio_component\")];\n                const fieldStrings = elements.map((el) => el.innerText.split(\"\\n\")[0]);\n                assertEqual(fieldStrings.includes(\"Administration\"), false);\n                assertEqual(fieldStrings.includes(\"Multi Companies\"), false);\n            },\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_reload_after_restoring_default_view\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor .o_field_widget[name='name']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar input[name='string']\",\n            run: \"edit new name\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_view\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_restore\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-footer .btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_undo:not(.o_web_studio_active)\",\n        },\n        {\n            trigger:\n                \".o_web_studio_form_view_editor .o_field_widget[name='name'] span:contains('Name')\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_edit_reified_field\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o_web_studio_form_view_editor .o_field_widget[name^='sel_groups_'],.o_web_studio_form_view_editor .o_field_widget[name^='in_groups_']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar input[name='string']\",\n            run: \"edit new name && click body\",\n        },\n        {\n            trigger: \".o_web_studio_leave\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_add_all_types_fields_related\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_user_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor\",\n        },\n        {\n            trigger: \".o_web_studio_field_related\",\n            run: \"drag_and_drop(.o_inner_group)\",\n        },\n        {\n            trigger: \".modal-dialog\",\n        },\n        {\n            trigger: \".o_model_field_selector_value\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_model_field_selector_popover\",\n        },\n        {\n            trigger: \".o_model_field_selector_popover_search input\",\n            run: \"edit Display Name\",\n        },\n        {\n            trigger: \".o_model_field_selector_popover_item_name:contains(Display Name)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-footer .btn-primary:first\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_leave\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_add_one2many_no_related_many2one\", {\n    steps: () => [\n        {\n            trigger: \".o_home_menu_background\",\n        },\n        {\n            trigger: \".o_main_navbar .o_web_studio_navbar_item\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_new_app\",\n        },\n        {\n            trigger: \".o_app[data-menu-xmlid='web_studio.studio_app_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_field_one2many\",\n            run: \"drag_and_drop .o_web_studio_form_view_editor .o_inner_group\",\n        },\n        {\n            trigger: \"h4.modal-title\",\n            run() {\n                assertEqual(this.anchor.textContent, \"No related many2one fields found\");\n            },\n        },\n        {\n            trigger: \".modal-footer .btn.btn-primary\",\n            run: \"click\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_approval_button_xml_id\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor button[name='base.action_model_data']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar [name='create_approval_rule']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_view_renderer .o_web_studio_approval_avatar\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_approval_group #approval_group_id\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_kanban_field_bold\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_kanban_view\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        // first field has class fw-bold\n        {\n            trigger:\n                \".o_web_studio_view_renderer .o_kanban_record .o-web-studio-editor--element-clickable\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_property input#class:value(fs-6 fw-bold whatever)\",\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_property input[type=checkbox]#bold:checked\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_property input[type=checkbox]#bold\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_property input#class:value(fs-6 whatever)\",\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_property input[type=checkbox]#bold:not(:checked)\",\n        },\n        // second field has class fw-bolder\n        {\n            trigger:\n                \".o_web_studio_view_renderer .o_kanban_record .o-web-studio-editor--element-clickable:eq(1)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_property input#class:value(fw-bolder)\",\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_property input[type=checkbox]#bold:checked\",\n        },\n        // third field isn't bold\n        {\n            trigger:\n                \".o_web_studio_view_renderer .o_kanban_record .o-web-studio-editor--element-clickable:eq(2)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_property input#class:value(text-muted)\",\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_property input[type=checkbox]#bold:not(:checked)\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_property input[type=checkbox]#bold\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_property input#class:value(fw-bold)\",\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar .o_web_studio_property input[type=checkbox]#bold:checked\",\n        },\n    ],\n});\n\nasync function animationFrame(timeoutBefore) {\n    await new Promise((resolve) => setTimeout(resolve, timeoutBefore));\n    await new Promise(requestAnimationFrame);\n}\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_kanban_menu_ribbon\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_kanban_view\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_view_renderer .o_kanban_view\",\n        },\n        {\n            trigger: \".nav .o_web_studio_new\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_view_renderer .o_web_studio_hook[data-type='t']\",\n        },\n        {\n            trigger: \".o_web_studio_component.o_web_studio_field_menu\",\n            async run() {\n                await animationFrame();\n                const { drop, moveTo } = await drag(this.anchor);\n                await moveTo(\".o_kanban_record:first()\");\n                await animationFrame(500); // wait for animations to finish in under 500ms\n                const target = await waitFor(\".o_web_studio_hook_visible\", {\n                    visible: true,\n                    timeout: 5000,\n                });\n                await moveTo(target);\n                await drop(target);\n            },\n        },\n        {\n            trigger: \".o_kanban_view .o_kanban_record:first() .o_dropdown_kanban\",\n        },\n        {\n            trigger: \".o_web_studio_component.o_web_studio_field_ribbon\",\n            run: \"drag_and_drop(.o_web_studio_hook[data-type='ribbon'])\",\n        },\n        {\n            trigger:\n                \".o_kanban_view .o_kanban_record:first() .o_widget_web_ribbon .ribbon:contains(demo)\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_create_related\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_view_renderer .o_form_view\",\n        },\n        {\n            trigger: \".o_web_studio_component.o_web_studio_field_related\",\n            run: \"drag_and_drop (.o_web_studio_hook:last())\",\n        },\n        {\n            trigger: \".modal .o_model_field_selector_value\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_model_field_selector_popover_search input\",\n            run: \"edit Related Company\",\n        },\n        {\n            trigger:\n                \".o_model_field_selector_popover_item[data-name='parent_id'] .o_model_field_selector_popover_relation_icon\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_model_field_selector_popover_title:contains(Related Company)\",\n        },\n        {\n            trigger: \".o_model_field_selector_popover_search input\",\n            run: \"edit Contact\",\n        },\n        {\n            trigger:\n                \".o_model_field_selector_popover_item[data-name='child_ids'] .o_model_field_selector_popover_item_name\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal footer button.btn-primary\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_view_renderer .o_field_widget.o_field_one2many\",\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add(\"web_studio_test_negated_groups\", {\n    steps: () => [\n        {\n            trigger: \"a[data-menu-xmlid='web_studio.studio_test_partner_menu']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_form_view\",\n        },\n        {\n            trigger: \".o_web_studio_navbar_item button:enabled\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar .o_web_studio_view\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar input#show_invisible\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_web_studio_form_view_editor .o-web-studio-editor--element-clickable\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar [name='negated_groups_id'] .o_badge:contains(studio has group)\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar [name='negated_groups_id'] .o_delete\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar [name='negated_groups_id']:not(:contains(studio has group))\",\n        },\n        {\n            trigger: \".o_web_studio_sidebar [name='negated_groups_id'] input\",\n            run: \"edit Access Rights\",\n        },\n        {\n            trigger: \"a.dropdown-item:contains(Administration / Access Rights)\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o_web_studio_sidebar [name='negated_groups_id'] .o_badge:contains(Administration / Access Rights)\",\n        },\n    ],\n});\n", "/** @odoo-module **/\n\nimport { animationFrame } from \"@odoo/hoot-dom\";\nimport { patch } from \"@web/core/utils/patch\";\nimport { TourHelpers } from \"@web_tour/tour_service/tour_helpers\";\n\npatch(TourHelpers.prototype, {\n    async scan(barcode) {\n        odoo.__WOWL_DEBUG__.root.env.services.barcode.bus.trigger(\"barcode_scanned\", { barcode });\n        await animationFrame();\n    },\n});\n", "/** @odoo-module **/\n\n/**\n * Returns the line for the given index and ensure the line is an HTMLElement (fails otherwise).\n *\n * @param {(HTMLElement|integer)} lineOrIndex either the line itself, either the index to find it\n * @param {string} errorClue\n * @returns {HTMLElement}\n */\nexport function _getLineOrFail (lineOrIndex, errorClue=\"No line found\") {\n    const line = typeof lineOrIndex === \"number\" ?\n        document.querySelectorAll('.o_barcode_line')[lineOrIndex] :\n        lineOrIndex;\n    if (line instanceof HTMLElement) {\n        return line;\n    }\n    const errorMessage = typeof lineOrIndex === \"number\" ?\n        `${errorClue}: the given index (${lineOrIndex}) doesn't match an existing line` :\n        `${errorClue}: the given object isn't a line`;\n    fail(errorMessage);\n}\n/**\n * @param {string} selector\n * @param {Object} description\n * @param {string} [description.barcode] the line's product's barcode\n * @param {Boolean} [description.completed] get only completed line if true\n * @param {Boolean} [description.selected] get only selected line if true\n * @param {string} [description.selector]\n * @returns {string}\n */\nexport function _prepareSelector(selector, description) {\n    const { barcode, selected, completed } = description;\n    if (selected !== undefined) {\n        selector += selected ? \".o_selected\" : \":not(.o_selected)\";\n    }\n    if (completed !== undefined) {\n        selector += completed ? \".o_line_completed\" : \":not(.o_line_completed)\";\n    }\n    selector += barcode ? `[data-barcode=\"${barcode}\"]`: \"\";\n    description.selector = selector;\n    return selector;\n}\n\nexport function fail(errorMessage) {\n    throw new Error(errorMessage);\n}\n\n/**\n * Get and returns exactly one line, fails if multiple lines are found).\n * @param {Object} description @see getLines\n * @returns {HTMLElement}\n */\nexport function getLine(description = {}) {\n    const line = getLines(description);\n    if (line.length > 1) {\n        fail(`getLine: Multiple lines were found for the selector \"${description.selector}\" (use 'getLines' instead if its wanted)`);\n    } else if (line.length === 0) {\n        fail(`getLine: No line was found for the selector \"${description.selector}\"`);\n    }\n    return line[0];\n}\n\n/**\n * Get and returns all lines matching the given description, fails if no line is found.\n * @param {Object} [description] if no description, will return all the barcode's line\n * @see _prepareSelector for more information about description's keys.\n * @returns {Element[]}\n */\nexport function getLines(description = {}) {\n    const selector = _prepareSelector(\":not(.o_sublines) > .o_barcode_line\", description);\n    const lines = document.querySelectorAll(selector);\n    const {index} = description;\n    if (index !== undefined) {\n        if (typeof index === \"number\") { // Single index (not an array), returns only one line.\n            return [lines[index]];\n        }\n        const chosenLines = [];\n        for (const i of index) {\n            chosenLines.push(lines[i]);\n        }\n        if (chosenLines.length !== index.length) {\n            fail(`Expects ${index.length} lines, got ${chosenLines.length}`)\n        }\n        return chosenLines;\n    }\n    return Array.from(lines);\n}\n\nexport function getSubline(description = {}) {\n    const subline = getSublines(description);\n    if (subline.length > 1) {\n        fail(`Multiple sublines were found for the selector \"${description.selector}\"`);\n    }\n    return subline[0];\n}\n\nexport function getSublines(description = {}) {\n    const selector = _prepareSelector(\".o_sublines .o_barcode_line\", description);\n    const sublines = document.querySelectorAll(selector);\n    if (!sublines.length) {\n        fail(`No subline was found for the selector \"${selector}\"`);\n    }\n    return sublines;\n}\n\nexport function triggerKeydown(eventKey, shiftkey = false) {\n    document.querySelector('.o_barcode_client_action')\n        .dispatchEvent(new window.KeyboardEvent('keydown', {bubbles: true, key: eventKey, shiftKey: shiftkey}));\n}\n\nexport function assert(current, expected, info) {\n    if (current !== expected) {\n        fail(`${info}: \"${current}\" instead of \"${expected}\".`);\n    }\n}\n\n/**\n * Checks if a button on the given line is visible or not.\n *\n * @param {HTMLElement|Integer} lineOrIndex the line (or its index) to test its the button visibility\n * @param {string} buttonName could be 'add_quantity', 'remove_unit' or 'set'.\n * @param {boolean} [shouldBeVisible=true]\n */\nexport function assertButtonIsVisible(lineOrIndex, buttonName, shouldBeVisible = true) {\n    const line = _getLineOrFail(lineOrIndex);\n    const button = line.querySelector(`.o_line_button.o_${buttonName}`);\n    const label = line.querySelector('.o_product_label,.package')?.innerText;\n    assert(!!button, shouldBeVisible,\n        `${label ? label + \" line\" : \"Line\"}'s button \"${buttonName}\" ${shouldBeVisible ? \"should\" : \"should not\"} be visible`);\n}\n\nexport function assertValidateVisible(expected) {\n    const validateButton = document.querySelector('.o_validate_page,.o_apply_page');\n    assert(!!validateButton, expected, 'Validate visible');\n}\n\nexport function assertValidateEnabled(expected) {\n    const validateButton = document.querySelector('.o_validate_page,.o_apply_page') || false;\n    assert(validateButton && !validateButton.hasAttribute('disabled'), expected, 'Validate enabled');\n}\n\nexport function assertValidateIsHighlighted(expected) {\n    const validateButton = document.querySelector('.o_validate_page,.o_apply_page') || false;\n    const isHighlighted = validateButton && validateButton.classList.contains('btn-primary');\n    assert(isHighlighted, expected, 'Validate button is highlighted');\n}\n\nexport function assertLinesCount(expectedCount, description) {\n    const currentCount = getLines(description).length;\n    assert(currentCount, expectedCount, `Should have ${expectedCount} line(s)`);\n}\n\nexport function assertScanMessage(expected) {\n    const instruction = document.querySelector(`.o_scan_message`);\n    const cssClass = instruction.classList[1];\n    assert(cssClass, `o_${expected}`, \"Not the right message displayed\");\n}\n\nexport function assertSublinesCount(expected) {\n    const current = document.querySelectorAll('.o_sublines > .o_barcode_line').length;\n    assert(current, expected, `Should have ${expected} subline(s), found ${current}`);\n}\n\nexport function assertLineDestinationIsNotVisible(lineOrIndex) {\n    const line = _getLineOrFail(lineOrIndex);\n    const destinationElement = line.querySelector('.o_line_destination_location');\n    if (destinationElement) {\n        const product = line.querySelector('.o_product_label').innerText;\n        fail(`The destination for line of the product ${product} should not be visible, \"${destinationElement.innerText}\" instead`);\n    }\n}\n\n\n/**\n * Checks if the given line is going in the given location. Implies the destination is visible.\n * @param {Element} lineOrIndex\n * @param {string} location\n */\nexport function assertLineDestinationLocation(lineOrIndex, location) {\n    const line = _getLineOrFail(lineOrIndex, \"Can't check the line's destination\");\n    const destinationElement = line.querySelector('.o_line_destination_location');\n    const product = line.querySelector('.o_product_label').innerText;\n    if (!destinationElement) {\n        fail(`The destination (${location}) for line of the product ${product} is not visible`);\n    }\n    assert(\n        destinationElement.innerText, location,\n        `The destination for line of product ${product} isn't in the right location`);\n}\n\nexport function assertLineIsHighlighted(lineOrIndex, expected = true) {\n    const line = _getLineOrFail(lineOrIndex, \"Can't check if the line is highlighted\");\n    const errorMessage = `line ${expected ? \"should\" : \"shouldn't\"} be highlighted`;\n    assert(line.classList.contains('o_highlight'), expected, errorMessage);\n}\n\nexport function assertLineLocations(lineOrIndex, source = null, destination = null) {\n    const line = _getLineOrFail(lineOrIndex, \"Can't check line's locations\");\n    if (source) {\n        assertLineSourceLocation(line, source);\n    } else {\n        assertLineSourceIsNotVisible(line);\n    }\n    if (destination) {\n        assertLineDestinationLocation(line, destination);\n    } else {\n        assertLineDestinationIsNotVisible(line);\n    }\n}\n\nexport function assertLineProduct(lineOrIndex, productName) {\n    const line = _getLineOrFail(lineOrIndex, \"Can't check line's product\");\n    const lineProduct = line.querySelector('.o_product_label').innerText;\n    assert(lineProduct, productName, \"Not the expected product\");\n}\n\n\n/**\n * Checks the quantity. It will get the done/counted quantity and will also\n * checks the on the reserved/on hand quantity if there is one.\n * Also, if the unit of measure is displayed, it will check that too.\n *\n * @param {(HTMLElement|integer)} lineOrIndex @see _getLineOrFail\n * @param {string} expectedQuantityWithUOM quantity on the line, formatted as \"n (/ N) ( UOM)\"\n */\nexport function assertLineQty(lineOrIndex, expectedQuantityWithUOM) {\n    const line = _getLineOrFail(lineOrIndex, \"Can't check the line's quantity\");\n    const elQty = line.querySelector('.o_barcode_scanner_qty');\n    const elUOM = line.querySelector('.o_line_uom');\n    let qtyText = elQty.innerText;\n    let errorMessage = \"Something wrong with the quantities\";\n    if (elUOM) {\n        qtyText += \" \" + elUOM.innerText;\n        errorMessage += \" or with the Unit of Measure\";\n    }\n    assert(qtyText, expectedQuantityWithUOM, errorMessage);\n}\n\nexport function assertLineSourceIsNotVisible(lineOrIndex) {\n    const line = _getLineOrFail(lineOrIndex);\n    const sourceElement = line.parentNode.querySelector('.o_barcode_location_line');\n    if (sourceElement) {\n        const product = line.querySelector('.o_product_label').innerText;\n        fail(`The location for line of the product ${product} should not be visible, \"${sourceElement.innerText}\" instead`);\n    }\n}\n\n/**\n * Checks the given lot or serial number is written on the given line.\n * Can also check none is written too.\n *\n * @param {(HTMLElement|integer)} lineOrIndex @see _getLineOrFail\n * @param {string|Boolean} expectedTrackingNumber either the expected lot/serial\n * number (or an empty string if the lot is displayed but empty), either false\n * if the element shouldn't be visible at all.\n */\nexport function assertLineTrackingNumber(lineOrIndex, expectedTrackingNumber) {\n    const line = _getLineOrFail(lineOrIndex);\n    const elTrackingNumber = line.querySelector(\".o_line_lot_name\");\n    const product = line.querySelector('.product-label')?.innerText || \"\";\n    if (expectedTrackingNumber === false) {\n        assert(\n            !!elTrackingNumber, false,\n            `No tracking number should be visible on the ${product} line`);\n    } else {\n        assert(\n            !!elTrackingNumber, true,\n            `${expectedTrackingNumber} should be visible but there is no tracking number on the ${product} line`);\n        assert(\n            elTrackingNumber.innerText, expectedTrackingNumber,\n            `Not the expected tracking number for the ${product} line`);\n    }\n}\n\n/**\n * Checks each given lines match the corresponding tracking numbers.\n * The number of lines and tracking numbers has to be equals.\n * @param {HTMLElement[]} lines\n * @param {string[]} trackingNumbers\n */\nexport function assertLinesTrackingNumbers(lines, trackingNumbers) {\n    assert(lines.length, trackingNumbers.length, \"Not the same amount of lines and tracking numbers\");\n    for (const [index, line] of lines.entries()) {\n        assertLineTrackingNumber(line, trackingNumbers[index]);\n    }\n}\n\n/**\n * Checks if the given line is in the given location. Implies the location is visible.\n * @param {Element} lineOrIndex\n * @param {string} location\n */\nexport function assertLineSourceLocation(lineOrIndex, location) {\n    const line = _getLineOrFail(lineOrIndex, \"Can't check the line's source\");\n    const sourceElement = line.parentNode.querySelector('.o_barcode_location_line');\n    const product = line.querySelector('.o_product_label').innerText;\n    if (!sourceElement) {\n        fail(`The source (${location}) for line of the product ${product} is not visible`);\n    }\n    assert(\n        sourceElement.innerText, location,\n        `The source for line of product ${product} isn't in the right location`);\n}\n\nexport function assertFormLocationSrc(expected) {\n    const location = document.querySelector('.o_field_widget[name=\"location_id\"] input');\n    assert(location.value, expected, 'Wrong source location');\n}\n\nexport function assertFormLocationDest(expected) {\n    const location = document.querySelector('.o_field_widget[name=\"location_dest_id\"] input');\n    assert(location.value, expected, 'Wrong destination location');\n}\n\nexport function assertFormQuantity(expected) {\n    const quantityField = document.querySelector(\n        '.o_field_widget[name=\"inventory_quantity\"] input, .o_field_widget[name=\"qty_done\"] input');\n    assert(quantityField.value, expected, 'Wrong quantity');\n}\n\nexport function assertErrorMessage(expected) {\n    const errorMessage = document.querySelector('.o_notification:last-child .o_notification_content');\n    assert(errorMessage.innerText, expected, 'wrong or absent error message');\n}\n\nexport function assertKanbanRecordsCount(expected) {\n    const kanbanRecords = document.querySelectorAll(\n        '.o_kanban_view .o_kanban_record:not(.o_kanban_ghost)');\n    assert(kanbanRecords.length, expected, 'Wrong number of cards');\n}\n\nexport function assertLineIsFaulty(lineOrIndex, expected = true) {\n    const line = _getLineOrFail(lineOrIndex, \"Can't check if the line is faulty\");\n    const errorMessage = `line ${expected ? \"should\" : \"shouldn't\"} be faulty`;\n    assert(line.classList.contains('o_faulty'), expected, errorMessage);\n}\n", "/* @odoo-module */\n\nexport const stepUtils = {\n    confirmAddingUnreservedProduct() {\n        return [\n            {\n                trigger: \".modal:not(.o_inactive_modal) .modal-title:contains(Add extra product?)\",\n            },\n            {\n                trigger: \".modal:not(.o_inactive_modal) .btn-primary\",\n                run: \"click\",\n            },\n            {\n                trigger: \"body:not(:has(.modal))\",\n            },\n        ];\n    },\n    inputManuallyBarcode(barcode) {\n        return [\n            { trigger: '.o_barcode_actions', run: \"click\" },\n            { trigger: 'input#manual_barcode', run: \"click\" },\n            { trigger: 'input#manual_barcode', run: `edit ${barcode}` },\n            { trigger: 'input#manual_barcode+button', run: \"click\" },\n        ];\n    },\n    validateBarcodeOperation(trigger = \".o_barcode_client_action\") {\n        return [\n            {\n                trigger: \"body:not(:has(.modal))\",\n            },\n            {\n                trigger,\n                run: \"scan OBTVALI\",\n            },\n            {\n                trigger: \".o_notification_bar.bg-success\",\n            },\n        ];\n    },\n    discardBarcodeForm() {\n        return [\n            {\n                isActive: [\"auto\"],\n                content: \"discard barcode form\",\n                trigger: \".o_discard\",\n                run: \"click\",\n            },\n            {\n                isActive: [\"auto\"],\n                content: \"wait to be back on the barcode lines\",\n                trigger: \".o_add_line\",\n            },\n        ];\n    },\n};\n", "/** @odoo-module */\n\nimport * as helper from './tour_helper_stock_barcode';\nimport { registry } from \"@web/core/registry\";\nimport { stepUtils } from \"./tour_step_utils\";\n\nregistry.category(\"web_tour.tours\").add(\"test_inventory_adjustment\", {\n    steps: () => [\n\n    {\n        trigger: '.o_button_inventory',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_scan_message.o_scan_product',\n        run: function () {\n            helper.assertScanMessage('scan_product');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(false);\n        }\n    },\n\n    { trigger: '.o_barcode_client_action', run: 'scan product1' },\n    {\n        trigger: '.o_barcode_line',\n        run: function () {\n            // Checks the product code and name are on separate lines.\n            const line = helper.getLine({ barcode: 'product1' });\n            helper.assert(line.querySelectorAll(\".o_barcode_line_title > .o_product_ref + .o_product_label\").length, 1);\n        }\n    },\n\n    { trigger: '.o_barcode_client_action', run: 'scan product1' },\n    { trigger: '.o_barcode_line .qty-done:contains(2)' },\n\n    { trigger: '.o_edit', run: \"click\" },\n\n    {\n        trigger: '.o_field_widget[name=\"inventory_quantity\"]',\n        run: function () {\n            helper.assertFormQuantity('2');\n        }\n    },\n\n    {\n        trigger: '.o_save',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_barcode_line',\n        run: function () {\n            // Checks the product code and name are on separate lines.\n            const line = helper.getLine({ barcode: 'product1' });\n            helper.assert(line.querySelectorAll(\".o_barcode_line_title > .o_product_ref + .o_product_label\").length, 1);\n        }\n    },\n\n    {\n        trigger: '.o_add_line',\n        run: \"click\",\n    },\n\n    {\n        trigger: \".o_field_widget[name=product_id] input\",\n        run: \"edit product2\",\n    },\n\n    {\n        trigger: \".ui-menu-item > a:contains('product2')\",\n        run: \"click\",\n    },\n\n    {\n        trigger: \".o_field_widget[name=inventory_quantity] input\",\n        run: \"edit 2\",\n    },\n\n    {\n        trigger: '.o_save',\n        run: \"click\",\n    },\n    {\n        trigger: \".o_scan_message.o_scan_product\",\n    },\n    {\n        trigger: '.o_barcode_line',\n        run: 'scan OBTVALI',\n    },\n\n    {\n        trigger: '.o_stock_barcode_main_menu',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_notification_bar.bg-success',\n        run: function () {\n            helper.assertErrorMessage('The inventory adjustment has been validated');\n        },\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add(\"test_inventory_adjustment_dont_update_location\", {\n    steps: () => [\n    {\n        trigger: '.o_button_inventory',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: function () {\n            helper.assertLinesCount(2);\n            const [line1, line2] = helper.getLines({ barcode: 'product1' });\n            helper.assertLineQty(line1, '0/5');\n            helper.assertLineQty(line2, '0/5');\n            helper.assertLineSourceLocation(line1, \"WH/Stock\");\n            helper.assertLineSourceLocation(line2, \"WH/Stock/Section 2\");\n        }\n    },\n    // Scan WH/Stock/Section 1.\n    {\n        trigger: '.o_barcode_client_action',\n        run: \"scan LOC-01-01-00\"\n    },\n    { trigger: \".o_barcode_location_line[data-location='WH/Stock'].text-muted\" },\n    {\n        trigger: '.o_barcode_location_group:first-child .o_barcode_line',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line.o_selected button.o_add_quantity',\n        run: \"click\",\n    },\n    {\n        trigger: 'button.o_remove_unit:enabled',\n        run: function () {\n            helper.assertLinesCount(2);\n            const selectedLine = helper.getLine({ selected: true });\n            helper.assertLineQty(selectedLine, '1/5');\n            helper.assertLineSourceLocation(selectedLine, \"WH/Stock\");\n        }\n    },\n    // Scans product1 -> A new line for this product should be created in Section 1.\n    {\n        trigger: '.o_barcode_client_action',\n        run: \"scan product1\",\n    },\n    {\n        trigger: '.o_barcode_location_line[data-location=\"WH/Stock/Section 1\"] + .o_barcode_line',\n        run: function () {\n            helper.assertLinesCount(3);\n            const selectedLine = helper.getLine({ selected: true });\n            helper.assertLineQty(selectedLine, '1');\n            helper.assertLineSourceLocation(selectedLine, \"WH/Stock/Section 1\");\n        }\n    },\n    ...stepUtils.validateBarcodeOperation('.o_apply_page.btn-primary'),\n]});\n\nregistry.category(\"web_tour.tours\").add(\"test_inventory_adjustment_multi_company\", {\n    steps: () => [\n    // Open the company switcher.\n    {\n        trigger: \".o_switch_company_menu > button\",\n        run: \"click\",\n    },\n    // Ensure the first company is selected and open the Barcode App, then the Inventory Adjustment.\n    {\n        trigger: \".o_switch_company_menu .oe_topbar_name:contains('Comp A')\",\n    },\n    {\n        trigger: \"[data-menu-xmlid='stock_barcode.stock_barcode_menu'] > .o_app_icon\",\n        run: \"click\",\n    },\n    {\n        trigger: \"button.o_button_inventory\",\n        run: \"click\",\n    },\n    // Scan product1 and product_no_company, they should be added in the inventory adj.\n    {\n        trigger: \".o_barcode_client_action\",\n        run: \"scan product1\",\n    },\n    {\n        trigger: \".o_barcode_line[data-barcode='product1']\",\n        run: \"scan product_no_company\",\n    },\n    // Try to scan product2 who belongs to the second company -> Should not be found.\n    {\n        trigger: \".o_barcode_line[data-barcode='product_no_company']\",\n        run: \"scan product2\",\n    },\n    {\n        trigger: \".o_notification_bar.bg-danger\",\n    },\n    {\n        trigger: \".o_notification button.o_notification_close\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_barcode_client_action\",\n        run: function() {\n            helper.assertLinesCount(2);\n        }\n    },\n    // Validate the Inventory Adjustment.\n    {\n        trigger: \".o_apply_page.btn-primary\",\n        run: \"click\",\n    },\n\n    // Go back on the App Switcher and change the company.\n    {\n        trigger: \".o_stock_barcode_main_menu a.oi-apps\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_switch_company_menu > button\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o-dropdown--menu .company_label:contains('Comp B')\",\n        run: \"click\",\n    },\n    // Open again the Barcode App then the Inventory Adjustment.\n    {\n        trigger: \".o_switch_company_menu .oe_topbar_name:contains('Comp B')\",\n    },\n    {\n        trigger: \"[data-menu-xmlid='stock_barcode.stock_barcode_menu'] > .o_app_icon\",\n        run: \"click\",\n     },\n    {\n        trigger: \"button.o_button_inventory\",\n        run: \"click\",\n    },\n    // Scan product2 and product_no_company, they should be added in the inventory adj.\n    {\n        trigger: \".o_barcode_client_action\",\n        run: \"scan product2\",\n    },\n    {\n        trigger: \".o_barcode_line[data-barcode='product2']\",\n        run: \"scan product_no_company\",\n    },\n    // Try to scan product1 who belongs to the first company -> Should not be found.\n    {\n        trigger: \".o_barcode_line[data-barcode='product_no_company']\",\n        run: \"scan product1\",\n        },\n        {\n            trigger: \".o_notification_bar.bg-danger\",\n        },\n        {\n        trigger: \".o_notification button.o_notification_close\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_barcode_client_action\",\n        run: function() {\n            helper.assertLinesCount(2);\n        }\n    },\n    // Validate the Inventory Adjustment.\n    {\n        trigger: \".o_barcode_line\",\n        run: \"scan OBTVALI\",\n        },\n        {\n            trigger: \".o_notification_bar.bg-success\",\n        },\n        {\n        trigger: \".o_stock_barcode_main_menu\",\n        run: function () {\n            helper.assertErrorMessage(\"The inventory adjustment has been validated\");\n        },\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_inventory_adjustment_multi_location', { steps: () => [\n    {\n        trigger: '.o_button_inventory',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-00-00'\n    },\n    {\n        trigger: '.o_scan_message:contains(\"WH/Stock\")',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1',\n    },\n    ...stepUtils.inputManuallyBarcode(\"product1\"),\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product2',\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-01-00'\n    },\n    {\n        trigger: '.o_scan_message:contains(\"WH/Stock/Section 1\")',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product2',\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-02-00'\n    },\n    {\n        trigger: '.o_scan_message:contains(\"WH/Stock/Section 2\")',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1',\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan OBTVALI',\n    },\n    {\n        trigger: '.o_stock_barcode_main_menu',\n        run: function () {\n            helper.assertErrorMessage('The inventory adjustment has been validated');\n        },\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_inventory_adjustment_tracked_product', { steps: () => [\n    {\n        trigger: '.o_button_inventory',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan productlot1',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"productlot1\")',\n        run: 'scan lot1',\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot1',\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .qty-done:contains(2)',\n        run: 'scan productserial1',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"productserial1\")',\n        run: 'scan serial1',\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan serial1',\n    },\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: function () {\n            // Check that other lines is correct\n            let line = helper.getLine({ barcode: 'productserial1' });\n            helper.assertLineQty(line, \"1\");\n            helper.assert(line.querySelector('.o_line_lot_name').innerText.trim(), 'serial1');\n            line = helper.getLine({ barcode: 'productlot1' });\n            helper.assertLineQty(line, \"2\");\n            helper.assert(line.querySelector('.o_line_lot_name').innerText.trim(), 'lot1');\n            helper.assertErrorMessage('The scanned serial number serial1 is already used.');\n        },\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan serial2',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"serial2\")',\n        run: 'scan productlot1',\n    },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"productlot1\"].o_selected',\n        run: 'scan lot1',\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .qty-done:contains(3)',\n        run: 'scan productserial1',\n    },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"productserial1\"].o_selected',\n        run: 'scan serial3',\n    },\n    {\n        trigger: '[data-barcode=\"productserial1\"] .o_sublines .o_barcode_line:contains(\"serial3\")',\n        run: function () {\n            helper.assertLinesCount(2);\n            helper.assertSublinesCount(3);\n        },\n    },\n    // Add a new line (it also triggers a save.)\n    {\n        trigger: '.o_add_line',\n        run: \"click\",\n    },\n    {\n        trigger: \".o_field_widget[name=product_id] input\",\n        run: \"edit productserial1\",\n    },\n    {\n        trigger: \".ui-menu-item > a:contains('productserial1')\",\n        run: \"click\",\n    },\n    {\n        trigger: '.o_save',\n        run: \"click\",\n    },\n    // Scan tracked by lots product, then scan new lots.\n    {\n        trigger: '.o_sublines .o_barcode_line:nth-child(4)',\n        run: function () {\n            helper.assertLinesCount(2);\n            helper.assertSublinesCount(4);\n        },\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan productlot1',\n    },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"productlot1\"].o_selected',\n        run: 'scan lot2',\n    },\n    {\n        trigger: '.o_barcode_line .o_barcode_line.o_selected:contains(\"lot2\")',\n        run: 'scan lot3',\n    },\n    // Must have 6 lines in two groups: lot1, lot2, lot3 and serial1, serial2, serial3.\n    // Grouped lines for `productlot1` should be unfolded.\n    {\n        trigger: '.o_barcode_line:contains(\"productlot1\") .o_sublines>.o_barcode_line.o_selected:contains(\"lot3\")',\n        run: function () {\n            helper.assertLinesCount(2);\n            helper.assertSublinesCount(3);\n        }\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan OBTVALI',\n    },\n    // Confirm modal (because one of the line tracked by serial number has no SN.)\n    {\n        trigger: '.modal-header',\n        run: \"click\",\n    },\n    {\n        trigger: \"button[name=action_confirm]:enabled\",\n        run: \"click\",\n    },\n    {\n        trigger: '.o_notification_bar.bg-success',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_stock_barcode_main_menu',\n        run: function () {\n            helper.assertErrorMessage('The inventory adjustment has been validated');\n        },\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_inventory_adjustment_tracked_product_multilocation', { steps: () => [\n    {\n        trigger: '.o_button_inventory',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line',\n        run: function() {\n            helper.assertLinesCount(2);\n            helper.assertLineSourceLocation(0, \"WH/Stock/Section 1\");\n            helper.assertLineQty(0, \"3/3\");\n            helper.assertLineSourceLocation(1, \"WH/Stock/Section 2\");\n            helper.assertLineQty(1, \"0/5\");\n        }\n    },\n    // Scans Section 1 then scans productlot1 -> It should update the first productlot1's line.\n    {\n        trigger: '.o_barcode_line',\n        run: 'scan LOC-01-01-00',\n    },\n    {\n        trigger: \".o_barcode_location_line[data-location='WH/Stock/Section 1'].text-bg-800\",\n        run: 'scan lot1',\n    },\n    {\n        trigger: '.o_barcode_location_group:first-child .o_barcode_line.o_selected',\n        run: function() {\n            helper.assertLineSourceLocation(0, \"WH/Stock/Section 1\");\n            helper.assertLineQty(0, \"4/3\");\n            helper.assertLineSourceLocation(1, \"WH/Stock/Section 2\");\n            helper.assertLineQty(1, \"0/5\");\n        }\n    },\n    // Scans productserial1 -> As we are in Section 1, it should get sn1, sn2 and sn3.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan productserial1',\n    },\n    {\n        trigger: '.o_barcode_line:nth-child(3).o_selected',\n        run: function() {\n            helper.assertLinesCount(3);\n            const serialLine = helper.getLine({ barcode: \"productserial1\" });\n            helper.assertLineSourceLocation(serialLine, \"WH/Stock/Section 1\");\n            helper.assertLineQty(1, \"?/3\");\n            helper.assertSublinesCount(3)\n            const [subline1, subline2, subline3] = helper.getSublines();\n            helper.assertLineQty(subline1, \"?/1\");\n            helper.assertLineQty(subline2, \"?/1\");\n            helper.assertLineQty(subline3, \"?/1\");\n            helper.assert(subline1.querySelector('.o_line_lot_name').innerText, \"sn1\");\n            helper.assert(subline2.querySelector('.o_line_lot_name').innerText, \"sn2\");\n            helper.assert(subline3.querySelector('.o_line_lot_name').innerText, \"sn3\");\n        }\n    },\n    // Hides sublines.\n    {\n        trigger: '.o_barcode_line.o_selected .btn.o_toggle_sublines .fa-angle-up',\n        run: \"click\",\n    },\n    // Scans Section 2 then scans productlot1 -> It should update the second productlot1's line.\n    {\n        trigger: '.o_barcode_line',\n        run: 'scan LOC-01-02-00',\n    },\n    {\n        trigger: \".o_barcode_location_line[data-location='WH/Stock/Section 2'].text-bg-800\",\n        run: 'scan lot1',\n    },\n    {\n        trigger: '.o_barcode_location_group:nth-child(2) .o_barcode_line.o_selected',\n        run: function() {\n            const [lotLine1, lotLine2] = helper.getLines({ barcode: \"productlot1\" });\n            helper.assertLineSourceLocation(lotLine1, \"WH/Stock/Section 1\");\n            helper.assertLineQty(0, \"4/3\");\n            helper.assertLineSourceLocation(lotLine2, \"WH/Stock/Section 2\");\n            helper.assertLineQty(2, \"1/5\");\n        }\n    },\n    // Scans productserial1 -> No existing quant in Section 2 for this product so creates a new line.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan productserial1',\n    },\n    {\n        trigger: '.o_barcode_line:nth-child(3).o_selected',\n        run: function() {\n            helper.assertLinesCount(4);\n            const [serialLine1, serialLine2] = helper.getLines({ barcode: 'productserial1' });\n            helper.assertLineSourceLocation(serialLine1, \"WH/Stock/Section 1\");\n            helper.assertLineQty(serialLine1, \"?/3\");\n            helper.assertLineSourceLocation(serialLine2, \"WH/Stock/Section 2\");\n            helper.assertLineQty(serialLine2, \"0\");\n        }\n    },\n    ...stepUtils.validateBarcodeOperation(),\n    {\n        trigger: '.o_stock_barcode_main_menu',\n        run: function () {\n            helper.assertErrorMessage('The inventory adjustment has been validated');\n        },\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_inventory_adjustment_tracked_product_permissive_quants', { steps: () => [\n    {\n        trigger: '.o_button_inventory',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: function() {\n            helper.assertLinesCount(0);\n        }\n    },\n\n    // Scan a product tracked by lot that has a quant without lot_id, then scan a product's lot.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan productlot1',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"productlot1\")',\n        run: function() {\n            helper.assertLinesCount(1);\n            helper.assertSublinesCount(0);\n            const line = helper.getLine();\n            helper.assertLineQty(line, \"?/5\");\n            helper.assertLinesTrackingNumbers([line], [\"\"]);\n        }\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot1',\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot1',\n    },\n    // Must have 2 lines in one group: one without lot and one with lot1.\n    // Grouped lines for `productlot1` should be unfolded.\n    {\n        trigger: '.o_sublines .o_barcode_line.o_selected:contains(\"lot1\") .qty-done:contains(2)',\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertSublinesCount(2);\n            const [subline1, subline2] = helper.getSublines();\n            helper.assertLineQty(subline1, \"?/5\");\n            helper.assertLineQty(subline2, \"2\");\n        }\n    },\n\n    { trigger: '.o_sublines .o_barcode_line:first-child', run: \"click\" },\n    {\n        trigger: '.o_sublines .o_barcode_line:first-child.o_selected button.o_count_zero',\n        run: \"click\",\n    },\n    ...stepUtils.validateBarcodeOperation('.o_sublines .o_barcode_line:first-child button.o_unset'),\n\n    {\n        trigger: '.o_stock_barcode_main_menu',\n        run: function () {\n            helper.assertErrorMessage('The inventory adjustment has been validated');\n        },\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_inventory_create_quant', { steps: () => [\n    {\n        trigger: '.o_button_inventory',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: function () {\n            helper.assertLinesCount(0);\n        }\n    },\n\n    // Scans product 1: must have 1 quantity and buttons +1/-1 must be visible.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1',\n    },\n    {\n        trigger: '.o_barcode_client_action .o_barcode_line',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ barcode: \"product1\" });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, \"1\");\n            helper.assertButtonIsVisible(line, \"add_quantity\");\n            helper.assertButtonIsVisible(line, \"remove_unit\");\n        }\n    },\n\n    // Edits the line to set the counted quantity to zero.\n    {\n        trigger: '.o_edit',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_field_widget[name=\"product_id\"]',\n        run: function() {\n            helper.assertFormQuantity(\"1\");\n        },\n    },\n    {\n        trigger: '.o_field_widget[name=inventory_quantity] input',\n        run: \"edit 0\",\n    },\n    {\n        trigger: '.o_save',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_client_action .o_barcode_line',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ barcode: \"product1\" });\n            helper.assertLineQty(line, \"0\");\n        }\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add(\"test_inventory_dialog_not_counted_serial_numbers\", { steps: () => [\n    { trigger: \".o_button_inventory\", run: \"click\" },\n    {\n        trigger: \".o_barcode_client_action\",\n        run: () => {\n            helper.assertLinesCount(3);\n            helper.assertLineProduct(0, \"productserial1\");\n            helper.assertLineQty(0, \"?/3\");\n            helper.assertLineSourceLocation(0, \"WH/Stock/Section 1\");\n            helper.assertLineProduct(1, \"productserial1\");\n            helper.assertLineQty(1, \"?/3\");\n            helper.assertLineSourceLocation(1, \"WH/Stock/Section 2\");\n            helper.assertLineProduct(2, \"productserial2\");\n            helper.assertLineQty(2, \"?/3\");\n            helper.assertLineSourceLocation(2, \"WH/Stock/Section 2\");\n        }\n    },\n    // Scan 1 SN for productserial1 in Section 1 and apply => Dialog should be displayed.\n    { trigger: \".o_scan_message.o_scan_src\", run: \"scan LOC-01-01-00\" },\n    { trigger: \".o_scan_message.o_scan_product_or_src\", run: \"scan productserial1\" },\n    { trigger: \".o_barcode_line.o_selected\", run: \"scan sn1\" },\n    { trigger: \".o_apply_page:enabled\", run: \"click\" },\n    { trigger: \".o_stock_barcode_apply_quant_dialog\" },\n    // Apply only counted quant and reopen the Inv. Adjust. => other Section 1 quants are still here.\n    { trigger: \".o_dialog button.o_apply\", run: \"click\" },\n    { trigger: \".o_button_inventory\", run: \"click\" },\n    {\n        trigger: \".o_barcode_client_action\",\n        run: () => {\n            helper.assertLinesCount(3);\n            helper.assertLineProduct(0, \"productserial1\");\n            helper.assertLineQty(0, \"?/2\");\n            helper.assertLineSourceLocation(0, \"WH/Stock/Section 1\");\n            helper.assertLineProduct(1, \"productserial1\");\n            helper.assertLineQty(1, \"?/3\");\n            helper.assertLineSourceLocation(1, \"WH/Stock/Section 2\");\n            helper.assertLineProduct(2, \"productserial2\");\n            helper.assertLineQty(2, \"?/3\");\n            helper.assertLineSourceLocation(2, \"WH/Stock/Section 2\");\n        }\n    },\n    // Scan a SN for productserial1 in Section 1 and apply => Apply also not counted SN.\n    { trigger: \".o_scan_message.o_scan_src\", run: \"scan LOC-01-01-00\" },\n    { trigger: \".o_scan_message.o_scan_product_or_src\", run: \"scan productserial1\" },\n    { trigger: \".o_barcode_line.o_selected\", run: \"scan sn2\" },\n    { trigger: \".o_barcode_line.o_selected.o_line_completed\" },\n    { trigger: \".o_apply_page:enabled\", run: \"click\" },\n    { trigger: \".o_stock_barcode_apply_quant_dialog\" },\n    { trigger: \".o_dialog button.o_apply_all\", run: \"click\" },\n    // Reopen the Inventory Adjustment.\n    { trigger: \".o_button_inventory\", run: \"click\" },\n    {\n        trigger: \".o_barcode_client_action\",\n        run: () => {\n            helper.assertLinesCount(2);\n            helper.assertLineProduct(0, \"productserial1\");\n            helper.assertLineQty(0, \"?/3\");\n            helper.assertLineSourceLocation(0, \"WH/Stock/Section 2\");\n            helper.assertLineProduct(1, \"productserial2\");\n            helper.assertLineQty(1, \"?/3\");\n            helper.assertLineSourceLocation(1, \"WH/Stock/Section 2\");\n        }\n    },\n    // Scan all SN for productserial1 in Section 2 and apply => Dialog should\n    // not be displayed (remaining SN in this location is for another product.)\n    { trigger: \".o_scan_message.o_scan_src\", run: \"scan LOC-01-02-00\" },\n    { trigger: \".o_scan_message.o_scan_product_or_src\", run: \"scan productserial1\" },\n    { trigger: \".o_barcode_line.o_selected\", run: \"scan sn4,sn5,sn6\" },\n    { trigger: \".o_barcode_line.o_selected.o_line_completed\" },\n    { trigger: \".o_apply_page\", run: \"click\" },\n    // Reopen the Inventory Adjustment and scan remaining SN for productserial2.\n    { trigger: \".o_button_inventory\", run: \"click\" },\n    {\n        trigger: \".o_barcode_client_action\",\n        run: () => {\n            helper.assertLinesCount(1);\n            helper.assertLineProduct(0, \"productserial2\");\n            helper.assertLineQty(0, \"?/3\");\n            helper.assertLineSourceLocation(0, \"WH/Stock/Section 2\");\n        }\n    },\n    { trigger: \".o_scan_message.o_scan_src\", run: \"scan LOC-01-02-00\" },\n    { trigger: \".o_scan_message.o_scan_product_or_src\", run: \"scan productserial2\" },\n    { trigger: \".o_barcode_line.o_selected\", run: \"scan sn1,sn2,sn3\" },\n    // Apply => No dialog because all SN are counted.\n    ...stepUtils.validateBarcodeOperation(\".o_barcode_line.o_selected.o_line_completed\"),\n    { trigger: \".o_stock_barcode_main_menu\" },\n]});\n\nregistry.category(\"web_tour.tours\").add(\"test_inventory_image_visible_for_quant\", { steps: () => [\n    { trigger: \"button.o_button_inventory\", run: \"click\" },\n    { trigger: \".o_barcode_line:first-child button.o_edit\", run: \"click\" },\n    {\n        trigger: \".o_form_view\",\n        run: function() {\n            const imgEl = document.querySelector(\"div[name=image_1920] img\");\n            helper.assert(Boolean(imgEl), true, \"Product image should be visible\");\n        }\n    },\n    { trigger: \"button.o_discard\", run: \"click\" },\n    { trigger: \".o_barcode_line:nth-child(2) button.o_edit\", run: \"click\" },\n    {\n        trigger: \".o_form_view\",\n        run: function() {\n            const imgEl = document.querySelector(\"div[name=image_1920] img\");\n            helper.assert(Boolean(imgEl), false, \"Product has no image set\");\n        }\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_inventory_nomenclature', { steps: () => [\n    {\n        trigger: '.o_button_inventory',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: function() {\n            helper.assertScanMessage('scan_product');\n        },\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 2145631123457', // 12.345 kg\n    },\n    {\n        trigger: '.o_product_label:contains(\"product_weight\")',\n        run: \"click\",\n    },\n    ...stepUtils.validateBarcodeOperation(),\n    {\n        trigger: '.o_stock_barcode_main_menu',\n        run: function () {\n            helper.assertErrorMessage('The inventory adjustment has been validated');\n        },\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_inventory_package', { steps: () => [\n    {\n        trigger: '.o_button_inventory',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan PACK001',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"product2\") .o_edit',\n        run: \"click\",\n    },\n    {\n        trigger: '[name=\"inventory_quantity\"] input',\n        run: \"edit 21\",\n    },\n    {\n        trigger: '.o_save',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_apply_page',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_notification_bar.bg-success',\n        run: function () {\n            helper.assertErrorMessage('The inventory adjustment has been validated');\n        },\n    },\n\n    {\n        trigger: '.o_stock_barcode_main_menu',\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_inventory_packaging', { steps: () => [\n    {\n        trigger: '.o_button_inventory',\n        run: \"click\",\n    },\n    // Scans a packaging when there is no existing quant for its product.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan pack007',\n    },\n    {\n        trigger: '.o_barcode_line',\n        run: function() {\n            const $line = helper.getLine({ barcode: \"product1\"});\n            helper.assertLineQty($line, \"15\");\n        }\n    },\n    {\n        trigger: '.o_apply_page',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_notification_bar.bg-success',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_button_inventory',\n        run: \"click\",\n    },\n    // Scans a packaging when a quant for its product exists.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan pack007',\n    },\n    // Verifies it takes the packaging's quantity.\n    {\n        trigger: '.o_barcode_line .qty-done:contains(15)',\n    },\n    {\n        trigger: '.o_apply_page',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_notification_bar.bg-success',\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_inventory_serial_product_packaging', { steps: () => [\n    { trigger: '.o_button_inventory', run: \"click\" },\n    { trigger: '.o_barcode_client_action', run: 'scan PCK3' },\n    {\n        trigger: '.o_barcode_line.o_highlight',\n        run: function() {\n            helper.assertLinesCount(1);\n            helper.assertLineProduct(0, \"productserial1\");\n            helper.assertLineQty(0, \"3\");\n            helper.assertSublinesCount(3);\n            const [subline1, subline2, subline3] = helper.getSublines();\n            helper.assertLineQty(subline1, \"1\");\n            helper.assertLineQty(subline2, \"1\");\n            helper.assertLineQty(subline3, \"1\");\n        }\n    },\n    { trigger: '.o_barcode_client_action', run: \"scan sn1\" },\n    { trigger: '.o_barcode_client_action', run: 'scan sn2' },\n    { trigger: '.o_barcode_client_action', run: 'scan sn3' },\n    {\n        trigger: '.o_barcode_client_action:contains(\"sn3\")',\n        run: function() {\n            helper.assertSublinesCount(3);\n            const sublines = helper.getSublines();\n            helper.assertLinesTrackingNumbers(sublines, [\"sn3\", \"sn2\", \"sn1\"]);\n        }\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_inventory_packaging_button', {\n    steps: () => [\n        {\n            trigger: \".o_button_inventory\",\n            run: \"click\",\n        },\n        {\n            trigger: \"button.o_add_line\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_field_widget[name='product_id'] .o_input\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_field_widget[name='product_id'] .o_input\",\n            run: \"edit Lovely Product\",\n        },\n        {\n            trigger: \".dropdown-item:contains('Lovely Product')\",\n            run: \"click\",\n        },\n        {\n            content: \"check that the packaging buttons were updated.\",\n            trigger: \".o_digipad_digit_buttons button:contains(LP x15)\",\n        },\n        {\n            content: \"Add 15 units via the button.\",\n            trigger: \".o_digipad_digit_buttons button:contains(LP x15)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_save\",\n            run: \"click\",\n        },\n        {\n            content: \"Check that the inventory adjustment was registered.\",\n            trigger: \".o_barcode_lines .qty-done:contains(15)\",\n        },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_inventory_owner_scan_package', { steps: () => [\n    {\n        trigger: '.o_button_inventory',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan P00001',\n    },\n    {\n        trigger: '.o_barcode_client_action:contains(\"P00001\")',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_client_action:contains(\"Azure Interior\")',\n        run: \"click\",\n    },\n    ...stepUtils.validateBarcodeOperation(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_inventory_using_buttons', { steps: () => [\n    {\n        trigger: '.o_button_inventory',\n        run: \"click\",\n    },\n\n    // Scans product 1: must have 1 quantity and buttons +1/-1 must be visible.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1',\n    },\n    {\n        trigger: '.o_barcode_client_action .o_barcode_line',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ barcode: \"product1\" });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, \"1\");\n            helper.assertButtonIsVisible(line, \"add_quantity\");\n            helper.assertButtonIsVisible(line, \"remove_unit\");\n            helper.assertButtonIsVisible(line, \"delete_line\", false);\n        }\n    },\n    // Clicks on -1 button: must have 0 quantity, -1 button should be hidden.\n    {\n        trigger: '.o_remove_unit',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"0\")',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ barcode: \"product1\" });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, \"0\");\n            helper.assertButtonIsVisible(line, \"add_quantity\");\n            helper.assertButtonIsVisible(line, \"remove_unit\", false);\n            helper.assertButtonIsVisible(line, \"delete_line\");\n        }\n    },\n    // Clicks on +1 button: must have 1 quantity, -1 must be enabled now.\n    {\n        trigger: '.o_add_quantity',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line .qty-done:contains(\"1\")',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ barcode: \"product1\" });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, \"1\");\n            helper.assertButtonIsVisible(line, \"add_quantity\");\n            helper.assertButtonIsVisible(line, \"remove_unit\");\n            helper.assertButtonIsVisible(line, \"delete_line\", false);\n        }\n    },\n\n    // Scans productserial1: must have 0 quantity.\n    { trigger: '.o_barcode_client_action', run: 'scan productserial1' },\n    {\n        trigger: '.o_barcode_client_action .o_barcode_line:nth-child(2)',\n        run: function () {\n            helper.assertLinesCount(2);\n            const line = helper.getLine({ barcode: 'productserial1', selected: true });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, '0');\n            // Should be visible because quantity is set.\n            helper.assertButtonIsVisible(line, \"unset\");\n            // Should be hide because quantity already on 0.\n            helper.assertButtonIsVisible(line, \"count_zero\", false);\n            // For tracked by SN product, should be visible when quantity = 0.\n            helper.assertButtonIsVisible(line, \"add_quantity\");\n            // Should be hide because qty is not greater than 0.\n            helper.assertButtonIsVisible(line, \"remove_unit\", false);\n            // SHould be visible because a line with 0 qty can be deleted.\n            helper.assertButtonIsVisible(line, \"delete_line\");\n            // Should be hide for product tracked by SN.\n            helper.assertButtonIsVisible(line, \"add_remaining_quantity\", false);\n        }\n    },\n    // Scans a serial number: must have 1 quantity, check button must display a \"X\".\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan BNG-118',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"BNG-118\")',\n        run: function () {\n            helper.assertLinesCount(2);\n            const line = helper.getLine({ barcode: 'productserial1', selected: true });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, '1');\n            helper.assertButtonIsVisible(line, \"count_zero\", false);\n            helper.assertButtonIsVisible(line, \"unset\");\n            helper.assertButtonIsVisible(line, \"remove_unit\");\n            helper.assertButtonIsVisible(line, \"add_quantity\", false);\n        }\n    },\n    // Clicks on -1 button\n    {\n        trigger: '.o_barcode_line:contains(\"productserial1\") button.o_remove_unit',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line.o_selected button.o_add_quantity',\n        run: function () {\n            helper.assertLinesCount(2);\n            const line = helper.getLine({ barcode: 'productserial1' });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, '0');\n            helper.assertButtonIsVisible(line, \"count_zero\", false);\n            helper.assertButtonIsVisible(line, \"unset\");\n            helper.assertButtonIsVisible(line, \"remove_unit\", false);\n            helper.assertButtonIsVisible(line, \"add_quantity\");\n        }\n    },\n    // Clicks on unset button.\n    {\n        trigger: '.o_barcode_line:contains(\"productserial1\") button.o_unset',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"productserial1\"):contains(\"?\")',\n        run: function () {\n            helper.assertLinesCount(2);\n            const line = helper.getLine({ barcode: 'productserial1', selected: true });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, '?');\n            helper.assertButtonIsVisible(line, \"count_zero\");\n            helper.assertButtonIsVisible(line, \"unset\", false);\n            helper.assertButtonIsVisible(line, \"remove_unit\", false);\n            helper.assertButtonIsVisible(line, \"add_quantity\");\n        }\n    },\n\n    // Scans productlot1: must have 0 quantity, buttons should be visible.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan productlot1',\n    },\n    {\n        trigger: '.o_barcode_client_action .o_barcode_line:nth-child(3)',\n        run: function () {\n            helper.assertLinesCount(3);\n            const line = helper.getLine({ barcode: 'productlot1' });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, '0');\n            helper.assertButtonIsVisible(line, \"count_zero\", false);\n            helper.assertButtonIsVisible(line, \"unset\");\n            helper.assertButtonIsVisible(line, \"remove_unit\", false);\n            helper.assertButtonIsVisible(line, \"add_quantity\");\n        }\n    },\n    // Scans a lot number: must have 1 quantity, buttons should still be visible.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan toto-42',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"toto-42\")',\n        run: function () {\n            helper.assertLinesCount(3);\n            const line = helper.getLine({ barcode: 'productlot1' });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, '1');\n            helper.assertButtonIsVisible(line, \"count_zero\", false);\n            helper.assertButtonIsVisible(line, \"unset\");\n            helper.assertButtonIsVisible(line, \"remove_unit\");\n            helper.assertButtonIsVisible(line, \"add_quantity\");\n        }\n    },\n    // Clicks on -1 button: must have 0 quantity, button -1 must be hide again.\n    {\n        trigger: '.o_barcode_line:contains(\"productlot1\") .o_remove_unit',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"productlot1\") .qty-done:contains(\"0\")',\n        run: function () {\n            helper.assertLinesCount(3);\n            const line = helper.getLine({ barcode: 'productlot1' });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, '0');\n            helper.assertButtonIsVisible(line, \"count_zero\", false);\n            helper.assertButtonIsVisible(line, \"unset\");\n            helper.assertButtonIsVisible(line, \"remove_unit\", false);\n            helper.assertButtonIsVisible(line, \"add_quantity\");\n        }\n    },\n    // Clicks on +1 button: must have 1 quantity, buttons must be visible.\n    {\n        trigger: '.o_barcode_line:contains(\"productlot1\") .o_add_quantity',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"productlot1\") .qty-done:contains(1)',\n        run: function () {\n            helper.assertLinesCount(3);\n            const line = helper.getLine({ barcode: 'productlot1' });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, '1');\n            helper.assertButtonIsVisible(line, \"count_zero\", false);\n            helper.assertButtonIsVisible(line, \"unset\");\n            helper.assertButtonIsVisible(line, \"remove_unit\");\n            helper.assertButtonIsVisible(line, \"add_quantity\");\n        }\n    },\n\n    // Scans product2 => Should retrieve the quantity on hand and display 1/10.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product2',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"product2\")',\n        run: function () {\n            helper.assertLinesCount(4);\n            const line = helper.getLine({ barcode: 'product2', selected: true });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, '1/10');\n            helper.assertButtonIsVisible(line, \"count_zero\", false);\n            helper.assertButtonIsVisible(line, \"unset\");\n            helper.assertButtonIsVisible(line, \"remove_unit\");\n            helper.assertButtonIsVisible(line, \"add_quantity\");\n        }\n    },\n    // Clicks multiple time on the set quantity button and checks the save is rightly done.\n    {\n        trigger: '.o_selected button.o_unset',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"product2\"):contains(\"?\")',\n        run: function () {\n            const line = helper.getLine({ barcode: \"product2\", selected: true });\n            helper.assertLineQty(line, \"?/10\");\n        }\n    },\n    // Goes to the quant form view to trigger a save then go back.\n    {\n        trigger: '.o_selected .o_line_button.o_edit',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_discard',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"product2\"):contains(\"?\")',\n        run: function () {\n            const line = helper.getLine({ barcode: \"product2\" });\n            helper.assertLineQty(line, \"?/10\");\n        }\n    },\n\n    // Clicks again, should pass from  \"? / 10\" to \"10 / 10\"\n    {\n        trigger: '.o_barcode_line:contains(\"product2\") button.o_add_remaining_quantity',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"product2\") .qty-done:contains(\"10\")',\n        run: function () {\n            const line = helper.getLine({ barcode: \"product2\" });\n            helper.assertLineQty(line, \"10/10\");\n        }\n    },\n    // Goes to the quant form view to trigger a save then go back.\n    {\n        trigger: '.o_barcode_line:contains(\"product2\") .o_line_button.o_edit',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_discard',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"product2\") .qty-done:contains(\"10\")',\n        run: function () {\n            const line = helper.getLine({ barcode: \"product2\" });\n            helper.assertLineQty(line, \"10/10\");\n        }\n    },\n\n    // Clicks again, should pass from  \"10 / 10\" to \"? / 10\"\n    {\n        trigger: '.o_barcode_line:contains(\"product2\") button.o_unset',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"product2\"):contains(\"?\")',\n        run: function () {\n            const line = helper.getLine({ barcode: \"product2\" });\n            helper.assertLineQty(line, \"?/10\");\n        }\n    },\n\n    // Validates the inventory.\n    {\n        trigger: '.o_apply_page',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_notification_bar.bg-success',\n    }\n]});\n\nregistry.category(\"web_tour.tours\").add('test_inventory_setting_show_quantity_to_count_on', { steps: () => [\n    {\n        trigger: '.o_button_inventory',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-00-00',\n    },\n    {\n        trigger: '.o_scan_message.o_scan_product_or_src',\n        run: function () {\n            helper.assertLinesCount(3);\n            const [line1, line2, line3] = helper.getLines();\n            helper.assertLineProduct(line1, 'product1');\n            helper.assertLineProduct(line2, 'productlot1');\n            helper.assertLineProduct(line3, 'productserial1');\n            helper.assertButtonIsVisible(line1, \"count_zero\", false);\n            helper.assertButtonIsVisible(line1, \"add_quantity\", false);\n            helper.assertButtonIsVisible(line1, \"remove_unit\", false);\n            helper.assertButtonIsVisible(line1, \"add_remaining_quantity\");\n            helper.assertLineIsHighlighted(line1, false);\n            helper.assertLineIsHighlighted(line2, false);\n            helper.assertLineIsHighlighted(line3, false);\n            helper.assertLineQty(line1, \"?/5\");\n            helper.assertLineQty(line2, \"?/7\");\n            helper.assertLineQty(line3, \"?/3\");\n            helper.assertLineSourceLocation(line1, \"WH/Stock\");\n            helper.assertLineSourceLocation(line2, \"WH/Stock\");\n            helper.assertLineSourceLocation(line3, \"WH/Stock\");\n        }\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"product1\")',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"product1\").o_selected',\n        run: function () {\n            const line = helper.getLine({ barcode: \"product1\" });\n            helper.assertButtonIsVisible(line, \"count_zero\");\n            helper.assertButtonIsVisible(line, \"add_quantity\");\n            helper.assertButtonIsVisible(line, \"remove_unit\", false);\n            helper.assertButtonIsVisible(line, \"add_remaining_quantity\");\n        }\n    },\n    {\n        trigger: \".o_barcode_line.o_selected .o_line_button.o_edit\",\n        run: \"click\",\n    },\n    {\n        trigger: '.o_digipad_fufill:contains(\"5\")',\n    },\n    {\n        content: \"Check button to add expected quantity is visible\",\n        trigger: '.o_barcode_control .o_discard',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"productlot1\")',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .o_line_button.o_toggle_sublines',\n        run: function () {\n            helper.assertSublinesCount(2);\n            const sublines = helper.getSublines();\n            helper.assertLineQty(sublines[0], \"?/3\");\n            helper.assertLineQty(sublines[1], \"?/4\");\n            helper.assertLinesTrackingNumbers(sublines, [\"lot1\", \"lot2\"]);\n            helper.assertButtonIsVisible(sublines[0], \"count_zero\");\n            helper.assertButtonIsVisible(sublines[0], \"remove_unit\", false);\n            helper.assertButtonIsVisible(sublines[0], \"add_quantity\");\n            helper.assertButtonIsVisible(sublines[0], \"add_remaining_quantity\");\n            helper.assertButtonIsVisible(sublines[1], \"count_zero\", false);\n            helper.assertButtonIsVisible(sublines[1], \"remove_unit\", false);\n            helper.assertButtonIsVisible(sublines[1], \"add_quantity\", false);\n            helper.assertButtonIsVisible(sublines[1], \"add_remaining_quantity\");\n        }\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"productserial1\")',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .o_line_button.o_toggle_sublines .fa-angle-up',\n        run: function () {\n            helper.assertSublinesCount(3);\n            const sublines = helper.getSublines();\n            helper.assertLineQty(sublines[0], \"?/1\");\n            helper.assertLineQty(sublines[1], \"?/1\");\n            helper.assertLineQty(sublines[2], \"?/1\");\n            helper.assertLinesTrackingNumbers(sublines, [\"sn1\", \"sn2\", \"sn3\"]);\n            helper.assertButtonIsVisible(sublines[0], \"count_zero\");\n            helper.assertButtonIsVisible(sublines[0], \"remove_unit\", false);\n            helper.assertButtonIsVisible(sublines[0], \"add_quantity\", false);\n            helper.assertButtonIsVisible(sublines[0], \"add_remaining_quantity\");\n            helper.assertButtonIsVisible(sublines[1], \"count_zero\", false);\n            helper.assertButtonIsVisible(sublines[1], \"remove_unit\", false);\n            helper.assertButtonIsVisible(sublines[1], \"add_quantity\", false);\n            helper.assertButtonIsVisible(sublines[1], \"add_remaining_quantity\");\n            helper.assertButtonIsVisible(sublines[2], \"count_zero\", false);\n            helper.assertButtonIsVisible(sublines[2], \"remove_unit\", false);\n            helper.assertButtonIsVisible(sublines[2], \"add_quantity\", false);\n            helper.assertButtonIsVisible(sublines[2], \"add_remaining_quantity\");\n        }\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_inventory_setting_show_quantity_to_count_off', { steps: () => [\n    {\n        trigger: '.o_button_inventory',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-00-00',\n    },\n    {\n        trigger: '.o_scan_message.o_scan_product_or_src',\n        run: function () {\n            helper.assertLinesCount(3);\n            const [line1, line2, line3] = helper.getLines();\n            helper.assertLineProduct(line1, 'product1');\n            helper.assertLineProduct(line2, 'productlot1');\n            helper.assertLineProduct(line3, 'productserial1');\n            helper.assertButtonIsVisible(line1, \"set\", false);\n            helper.assertLineIsHighlighted(line1, false);\n            helper.assertLineIsHighlighted(line2, false);\n            helper.assertLineIsHighlighted(line3, false);\n            helper.assertLineQty(line1, \"?\");\n            helper.assertLineQty(line2, \"?\");\n            helper.assertLineQty(line3, \"?\");\n            helper.assertLineSourceLocation(line1, \"WH/Stock\");\n            helper.assertLineSourceLocation(line2, \"WH/Stock\");\n            helper.assertLineSourceLocation(line3, \"WH/Stock\");\n        }\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"product1\")',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"product1\").o_selected',\n        run: function () {\n            const line = helper.getLine({ barcode: \"product1\" });\n            helper.assertButtonIsVisible(line, \"count_zero\");\n            helper.assertButtonIsVisible(line, \"remove_unit\", false);\n            helper.assertButtonIsVisible(line, \"add_quantity\");\n            helper.assertButtonIsVisible(line, \"add_remaining_quantity\", false);\n        }\n    },\n    {\n        trigger: \".o_barcode_line.o_selected .o_line_button.o_edit\",\n        run: \"click\",\n    },\n    {\n        trigger: '.o_form_view_container',\n        run: function() {\n            helper.assert(\n                Boolean(document.querySelector(\".o_digipad_fufill\")), false,\n                \"Button to set counted quantity shouldn't be visible\");\n        },\n    },\n    {\n        trigger: '.o_barcode_control .o_discard',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_barcode_line:contains(\"productlot1\")',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .o_line_button.o_toggle_sublines',\n        run: function () {\n            helper.assertSublinesCount(2);\n            const [subline1, subline2] = helper.getSublines();\n            helper.assertLineQty(subline1, \"?\");\n            helper.assertLineQty(subline2, \"?\");\n            helper.assert(subline1.querySelector('.o_line_lot_name').innerText, \"lot1\");\n            helper.assert(subline2.querySelector('.o_line_lot_name').innerText, \"lot2\");\n            helper.assertButtonIsVisible(subline1, \"count_zero\");\n            helper.assertButtonIsVisible(subline1, \"remove_unit\", false);\n            helper.assertButtonIsVisible(subline1, \"add_quantity\");\n            helper.assertButtonIsVisible(subline1, \"add_remaining_quantity\", false);\n            helper.assertButtonIsVisible(subline2, \"count_zero\", false);\n            helper.assertButtonIsVisible(subline2, \"remove_unit\", false);\n            helper.assertButtonIsVisible(subline2, \"add_quantity\", false);\n            helper.assertButtonIsVisible(subline2, \"add_remaining_quantity\", false);\n        }\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"productserial1\")',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .o_line_button.o_toggle_sublines .fa-angle-up',\n        run: function () {\n            helper.assertSublinesCount(3);\n            const [subline1, subline2, subline3] = helper.getSublines();\n            helper.assertLineQty(subline1, \"?\");\n            helper.assertLineQty(subline2, \"?\");\n            helper.assertLineQty(subline3, \"?\");\n            helper.assert(subline1.querySelector('.o_line_lot_name').innerText, \"sn1\");\n            helper.assert(subline2.querySelector('.o_line_lot_name').innerText, \"sn2\");\n            helper.assert(subline3.querySelector('.o_line_lot_name').innerText, \"sn3\");\n            // For product tracked by SN, the set button should be visible no matter the parameter.\n            helper.assertButtonIsVisible(subline1, \"count_zero\");\n            helper.assertButtonIsVisible(subline1, \"remove_unit\", false);\n            helper.assertButtonIsVisible(subline1, \"add_quantity\");\n            helper.assertButtonIsVisible(subline1, \"add_remaining_quantity\", false);\n            helper.assertButtonIsVisible(subline2, \"count_zero\", false);\n            helper.assertButtonIsVisible(subline2, \"remove_unit\", false);\n            helper.assertButtonIsVisible(subline2, \"add_quantity\", false);\n            helper.assertButtonIsVisible(subline2, \"add_remaining_quantity\", false);\n            helper.assertButtonIsVisible(subline3, \"count_zero\", false);\n            helper.assertButtonIsVisible(subline3, \"remove_unit\", false);\n            helper.assertButtonIsVisible(subline3, \"add_quantity\", false);\n            helper.assertButtonIsVisible(subline3, \"add_remaining_quantity\", false);\n        }\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_inventory_setting_count_entire_locations_on', { steps: () => [\n    {\n        trigger: '.o_button_inventory',\n        run: \"click\",\n    },\n    // At first, only the marked as to count quant should be visible.\n    {\n        trigger: '.o_barcode_client_action',\n        run: function() {\n            helper.assertLinesCount(1);\n            const line = helper.getLine();\n            helper.assertLineProduct(line, 'product1');\n            helper.assertLineQty(line, \"10/10\");\n            helper.assertLineSourceLocation(line, \"WH/Stock/Section 1\");\n        }\n    },\n    // Scan WH/Stock/Section 1 => Should fetch all quants in this location.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-01-00',\n    },\n    // Check that all quants of WH/Stock/Section 1 are loaded with their respective information.\n    {\n        trigger: '.o_barcode_line:nth-child(4)',\n        run: function () {\n            helper.assertLinesCount(4);\n            const [line1, line2, line3, line4] = helper.getLines();\n            helper.assertLineProduct(line1, 'product1');\n            helper.assertLineProduct(line2, 'product2');\n            helper.assertLineProduct(line3, 'productlot1');\n            helper.assertLineProduct(line4, 'productserial1');\n            helper.assertLineQty(line1, \"10/10\");\n            helper.assertLineQty(line2, \"?/20\");\n            helper.assertLineQty(line3, \"?/7\");\n            helper.assertLineQty(line4, \"?/3\");\n            helper.assertLineSourceLocation(line1, \"WH/Stock/Section 1\");\n            helper.assertLineSourceLocation(line2, \"WH/Stock/Section 1\");\n            helper.assertLineSourceLocation(line3, \"WH/Stock/Section 1\");\n            helper.assertLineSourceLocation(line4, \"WH/Stock/Section 1\");\n        }\n    },\n\n    // Scan WH/Stock/Section 2 => Should fetch all quants in this location.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-02-00',\n    },\n    // Check that all quants of WH/Stock/Section 2 are loaded with their respective information.\n    {\n        trigger: '.o_barcode_location_line[data-location=\"WH/Stock/Section 2\"].text-bg-800',\n        run: function () {\n            helper.assertLinesCount(7);\n            helper.assertLineProduct(4, '[TEST] product1');\n            helper.assertLineQty(4, \"?/30\");\n            helper.assertLineSourceLocation(4, \"WH/Stock/Section 2\");\n            helper.assertLineProduct(5, 'productlot1');\n            helper.assertLineQty(5, \"?/7\");\n            helper.assertLineSourceLocation(5, \"WH/Stock/Section 2\");\n            helper.assertLineProduct(6, 'productserial1');\n            helper.assertLineQty(6, \"?/3\");\n            helper.assertLineSourceLocation(6, \"WH/Stock/Section 2\");\n        }\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_inventory_setting_count_entire_locations_off', { steps: () => [\n    {\n        trigger: '.o_button_inventory',\n        run: \"click\",\n    },\n    // Only the marked as to count quant should be visible.\n    {\n        trigger: '.o_barcode_client_action',\n        run: function() {\n            helper.assertLinesCount(1);\n            helper.assertLineProduct(0, 'product1');\n            helper.assertLineQty(0, \"10/10\");\n            helper.assertLineSourceLocation(0, \"WH/Stock/Section 1\");\n        }\n    },\n    // Scan WH/Stock/Section 1 => Should not fetch other quants.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-01-00',\n    },\n    {\n        trigger: '.o_barcode_location_line.text-bg-800',\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertLineProduct(0, 'product1');\n            helper.assertLineQty(0, \"10/10\");\n            helper.assertLineSourceLocation(0, \"WH/Stock/Section 1\");\n        }\n    },\n]});\n", "/** @odoo-module */\n\nimport * as helper from './tour_helper_stock_barcode';\nimport { registry } from \"@web/core/registry\";\nimport { stepUtils } from \"./tour_step_utils\";\n\nregistry.category(\"web_tour.tours\").add('test_internal_picking_from_scratch', { steps: () => [\n    // Move 2 product1 from WH/Stock/Section 1 to WH/Stock/Section 2.\n    {\n        trigger: '.o_add_line',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_field_widget[name=\"product_id\"]',\n    },\n    {\n        trigger: \".o_field_widget[name=qty_done] input\",\n        run: \"edit 2\",\n    },\n\n    {\n        trigger: \".o_field_widget[name=product_id] input\",\n        run: \"edit product1\",\n    },\n\n    {\n        trigger: \".ui-menu-item > a:contains('product1')\",\n        run: \"click\",\n    },\n\n    {\n        trigger: \".o_field_widget[name=location_id] input\",\n        run: \"edit Section 1\",\n    },\n\n    {\n        trigger: \".ui-menu-item > a:contains('Section 1')\",\n        run: \"click\",\n    },\n\n    {\n        trigger: \".o_field_widget[name=location_dest_id] input\",\n        run: \"edit Section 2\",\n    },\n\n    {\n        trigger: \".ui-menu-item > a:contains('Section 2')\",\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_save',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line .o_line_destination_location:contains(\"Section 2\")',\n    },\n    {\n        trigger: '.o_barcode_location_line[data-location=\"WH/Stock/Section 1\"] + .o_barcode_line',\n        run: function() {\n            helper.assertLinesCount(1);\n        },\n    },\n\n    // Move 1 product2 from WH/Stock/Section 1 to WH/Stock/Section 3.\n    {\n        trigger: '.o_add_line',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_field_widget[name=\"product_id\"]',\n    },\n    {\n        trigger: \".o_field_widget[name=product_id] input\",\n        run: \"edit product2\",\n    },\n\n    {\n        trigger: \".ui-menu-item > a:contains('product2')\",\n        run: \"click\",\n    },\n\n    {\n        trigger: \".o_field_widget[name=location_id] input\",\n        run: \"edit Section 1\",\n    },\n\n    {\n        trigger: \".ui-menu-item > a:contains('Section 1')\",\n        run: \"click\",\n    },\n\n    {\n        trigger: \".o_field_widget[name=location_dest_id] input\",\n        run: \"edit WH/Stock/Section 3\",\n    },\n\n    {\n        trigger: \".ui-menu-item > a:contains('Section 3')\",\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_save',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line .o_line_destination_location:contains(\"Section 3\")',\n    },\n    {\n        trigger: '.o_barcode_location_line[data-location=\"WH/Stock/Section 1\"] + .o_barcode_line + .o_barcode_line',\n        run: function() {\n            helper.assertLinesCount(2);\n            const lineProduct1 = helper.getLine({ barcode: \"product1\" });\n            helper.assertLineIsHighlighted(lineProduct1, false);\n            const lineProduct2 = helper.getLine({ barcode: \"product2\" });\n            helper.assertLineIsHighlighted(lineProduct2, true);\n        },\n    },\n\n    // Edits the first line to check the transaction doesn't crash and the form view is correctly filled.\n    {\n        trigger: '.o_barcode_line:nth-child(2) .o_edit',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_field_widget[name=\"product_id\"]',\n        run: function() {\n            helper.assertFormLocationSrc(\"WH/Stock/Section 1\");\n            helper.assertFormLocationDest(\"WH/Stock/Section 2\");\n            helper.assertFormQuantity(\"2\");\n        },\n    },\n\n    {\n        trigger: '.o_save',\n        run: \"click\",\n    },\n\n    // Move 1 product2 from WH/Stock/Section 1 to WH/Stock/Section 2.\n    {\n        trigger: '.o_add_line',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_field_widget[name=\"product_id\"]',\n    },\n    {\n        trigger: \".o_field_widget[name=product_id] input\",\n        run: \"edit product2\",\n    },\n\n    {\n        trigger: \".ui-menu-item > a:contains('product2')\",\n        run: \"click\",\n    },\n\n    {\n        trigger: \".o_field_widget[name=location_id] input\",\n        run: \"edit Section 1\",\n    },\n\n    {\n        trigger: \".ui-menu-item > a:contains('Section 1')\",\n        run: \"click\",\n    },\n\n    {\n        trigger: \".o_field_widget[name=location_dest_id] input\",\n        run: \"edit Section 2\",\n    },\n\n    {\n        trigger: \".ui-menu-item > a:contains('Section 2')\",\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_save',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .o_line_destination_location:contains(\"Section 2\")',\n    },\n    {\n        trigger: '.o_barcode_location_group .o_barcode_line:nth-child(4)',\n        run: function() {\n            helper.assertLinesCount(3);\n        },\n    },\n    // Scans the destination (Section 2) for the current line...\n    {\n        trigger: '.o_barcode_line:nth-child(3).o_selected',\n        run: 'scan LOC-01-02-00',\n    },\n    // ...then scans the source (Section 1) for the next line.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-01-00',\n    },\n    // On this page, scans product1 which will create a new line and then opens its edit form view.\n\n    {\n        trigger: '.o_barcode_location_line[data-location=\"WH/Stock/Section 1\"].text-bg-800',\n        run: 'scan product1'\n    },\n\n    { // First call to write.\n        trigger: '.o_barcode_line[data-barcode=\"product1\"].o_selected .o_edit',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_field_widget[name=\"product_id\"]:contains(\"product1\")',\n    },\n    {\n        trigger:'.o_save',\n        run: \"click\",\n    },\n    { // Scans the line's destination before to validate the picking.\n        trigger: '.o_barcode_line[data-barcode=\"product1\"].o_selected',\n        run: 'scan shelf3',\n    },\n    {\n        trigger: '.o_barcode_line:nth-child(2) .o_line_destination_location:contains(\"Section 3\")',\n    },\n    {\n        trigger: '.o_validate_page',\n        run: \"click\",\n    },\n    { // Second call to write (change the dest. location).\n        trigger: '.o_notification_bar.bg-success',\n    }\n]});\n\nregistry.category(\"web_tour.tours\").add('test_internal_picking_from_scratch_with_package', {  steps: () => [\n    // Creates a first internal transfert (Section 1 -> Section 2).\n    { trigger: '.o_stock_barcode_main_menu', run: 'scan WHINT' },\n    // Scans product1 and put it in P00001, then do the same for product2.\n    { trigger: '.o_barcode_client_action', run: 'scan product1' },\n    { trigger: '.o_barcode_line.o_selected', run: 'scan P00001' },\n    // Scans the destination.\n    { trigger: '.o_barcode_line .result-package', run: 'scan LOC-01-02-00' },\n    { trigger: '.o_barcode_line:not(.o_selected)', run: 'scan product2' },\n    { trigger: '.o_barcode_line[data-barcode=\"product2\"].o_selected', run: 'scan P00001' },\n    { // Scans the destination.\n        trigger: '.o_barcode_line[data-barcode=\"product2\"] .result-package', run: 'scan LOC-01-02-00',\n    },\n    { // Validates the internal picking.\n        trigger: '.o_barcode_line[data-barcode=\"product2\"] .o_line_destination_location',\n        run: 'scan OBTVALI',\n    },\n    {\n        trigger: '.o_notification_bar.bg-success',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_notification button.o_notification_close',\n        run: \"click\",\n    },\n\n    // Creates a second internal transfert (WH/Stock -> WH/Stock).\n    { trigger: '.o_stock_barcode_main_menu', run: 'scan WHINT' },\n    { trigger: '.o_barcode_client_action', run: () => helper.assertLinesCount(0) },\n    // Scans a package with some quants and checks lines was created for its content.\n    { trigger: '.o_barcode_client_action', run: 'scan P00002' },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"product1\"] + .o_barcode_line[data-barcode=\"product2\"]',\n        run: () => {\n            helper.assertLinesCount(2);\n            helper.assertLineQty(0, \"1\");\n            helper.assertLineQty(1, \"2\");\n        },\n    },\n    // Scans the destination location and validate the transfert.\n    { trigger: '.o_barcode_line.o_selected + .o_barcode_line.o_selected', run: 'scan LOC-01-02-00' },\n    { trigger: '.o_barcode_line:not(.o_selected)', run: 'scan OBTVALI' },\n    { trigger: '.o_notification_bar.bg-success'},\n]});\n\nregistry.category(\"web_tour.tours\").add('test_internal_picking_reserved_1', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function() {\n            helper.assertLinesCount(3);\n            helper.assertScanMessage('scan_src');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(true);\n            helper.assertLineIsHighlighted(0, false);\n            helper.assertLineLocations(0, 'WH/Stock/Section 1', '.../Section 2');\n            helper.assertLineIsHighlighted(1, false);\n            helper.assertLineLocations(1, 'WH/Stock/Section 1', '.../Section 2');\n            helper.assertLineIsHighlighted(2, false);\n            helper.assertLineLocations(2, 'WH/Stock/Section 3', '.../Section 4');\n        }\n    },\n\n    // We first move a product1 from shef3 to shelf2.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan shelf3'\n    },\n\n    {\n        trigger: '.o_barcode_location_line[data-location=\"WH/Stock/Section 3\"].text-bg-800',\n        run: function() {\n            helper.assertLinesCount(3);\n            helper.assertScanMessage('scan_product');\n            const locationInBold = document.querySelector('.o_barcode_location_line.text-bg-800');\n            const lineInSection3 = locationInBold.parentElement.querySelector('.o_barcode_line');\n            helper.assertLineLocations(lineInSection3, 'WH/Stock/Section 3', '.../Section 4');\n        }\n    },\n\n    // Scan product1 after scanned shelf3 will select the existing line but change its source.\n    { trigger: '.o_barcode_client_action', run: 'scan product1' },\n    {\n        trigger: '.o_barcode_line.o_selected.o_line_completed',\n        run: function() {\n            helper.assertLinesCount(3);\n            helper.assertScanMessage('scan_dest');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(true);\n            const lineproduct1 = helper.getLine({ selected: true });\n            helper.assertLineIsHighlighted(lineproduct1, true);\n            helper.assertLineLocations(lineproduct1, 'WH/Stock/Section 3', '.../Section 2');\n        }\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-02-00'\n    },\n\n    {\n        trigger: '.o_barcode_location_group:nth-child(2) .o_barcode_line:not(.o_selected):nth-child(2) .o_line_destination_location:contains(\".../Section 2\")',\n        run: function() {\n            helper.assertLinesCount(3);\n            helper.assertScanMessage('scan_src');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(true);\n            const lineproduct1 = helper.getLine({barcode: 'product1'});\n            helper.assertLineIsHighlighted(lineproduct1, false);\n            helper.assertLineLocations(lineproduct1, 'WH/Stock/Section 3', '.../Section 2');\n        }\n    },\n\n    // Scans Section 1 as source location.\n    { 'trigger': '.o_barcode_client_action', run: 'scan LOC-01-01-00' },\n\n    {\n        trigger: '.o_barcode_location_line[data-location=\"WH/Stock/Section 1\"].text-bg-800',\n        run: function() {\n            helper.assertLinesCount(3);\n            helper.assertScanMessage('scan_product');\n            helper.assertLineIsHighlighted(0, false);\n            helper.assertLineIsHighlighted(1, false);\n            helper.assertLineIsHighlighted(2, false);\n        }\n    },\n\n    // Process the reservation for product1 (create a new line as the previous one was overrided).\n    { trigger: '.o_barcode_client_action', run: 'scan product1' },\n    {\n        trigger: '.o_barcode_location_group:first-child .o_barcode_line.o_selected',\n        run: function() {\n            helper.assertLinesCount(4);\n            helper.assertScanMessage('scan_product_or_dest');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(true);\n            helper.assertLineIsHighlighted(0, false);\n            helper.assertLineIsHighlighted(1, true);\n            helper.assertLineLocations(1, 'WH/Stock/Section 1', 'WH/Stock');\n            helper.assertLineIsHighlighted(2, false);\n            helper.assertLineIsHighlighted(3, false);\n        }\n    },\n    { trigger: '.o_barcode_client_action', run: 'scan LOC-01-02-00' },\n\n    // Process the reservation for product2 (Section 1 to Section 2).\n    { trigger: '.o_scan_message.o_scan_src', run: 'scan LOC-01-01-00' },\n    { trigger: '.o_scan_message.o_scan_product', run: 'scan product2' },\n    {\n        trigger: '.o_barcode_location_group:first-child .o_barcode_line:nth-child(2).o_selected',\n        run: function() {\n            helper.assertLinesCount(4);\n            helper.assertScanMessage('scan_dest');\n            helper.assertValidateIsHighlighted(false);\n            helper.assertLineIsHighlighted(0, true);\n            helper.assertLineIsHighlighted(1, false);\n            helper.assertLineIsHighlighted(2, false);\n            helper.assertLineIsHighlighted(3, false);\n        }\n    },\n    { trigger: '.o_barcode_client_action', run: 'scan LOC-01-02-00' },\n\n    // Process the reservation for product2 (Section 3 to Section 4).\n    { trigger: '.o_scan_message.o_scan_src', run: 'scan shelf3' },\n    { trigger: '.o_scan_message.o_scan_product', run: 'scan product2' },\n    {\n        trigger: '.o_barcode_location_group:nth-child(2) .o_barcode_line:nth-child(3).o_selected',\n        run: function() {\n            helper.assertLinesCount(4);\n            helper.assertScanMessage('scan_dest');\n            helper.assertValidateIsHighlighted(false);\n            helper.assertLineIsHighlighted(0, false);\n            helper.assertLineIsHighlighted(1, false);\n            helper.assertLineIsHighlighted(2, false);\n            helper.assertLineIsHighlighted(3, true);\n        }\n    },\n    { trigger: '.o_scan_message.o_scan_dest', run: 'scan shelf4' },\n\n    {\n        trigger: '.o_scan_message.o_scan_validate',\n        run: function() {\n            helper.assertLinesCount(4);\n            helper.assertScanMessage('scan_validate');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(true);\n            helper.assertValidateEnabled(true);\n\n            helper.assertLineIsHighlighted(0, false);\n            helper.assertLineQty(0, '1/1');\n            helper.assertLineLocations(0, 'WH/Stock/Section 1', '.../Section 2');\n\n            helper.assertLineIsHighlighted(1, false);\n            helper.assertLineQty(1, '1');\n            helper.assertLineLocations(1, 'WH/Stock/Section 1', '.../Section 2');\n\n            helper.assertLineIsHighlighted(2, false);\n            helper.assertLineQty(2, '1/1');\n            helper.assertLineLocations(2, 'WH/Stock/Section 3', '.../Section 2');\n\n            helper.assertLineIsHighlighted(3, false);\n            helper.assertLineQty(3, '1/1');\n            helper.assertLineLocations(3, 'WH/Stock/Section 3', '.../Section 4');\n        }\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_procurement_backorder', { steps: () => [\n        { trigger: '.o_barcode_client_action', run: 'scan PB' },\n        { trigger: '.o_barcode_line:contains(\"PB\")', run: 'scan OBTVALI' },\n        { trigger: '.o_notification_bar.bg-success'},\n    ]\n});\n\nregistry.category(\"web_tour.tours\").add('test_receipt_reserved_1', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function() {\n            helper.assertLinesCount(2);\n            helper.assertScanMessage('scan_product');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(true);\n        }\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product2'\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product2'\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product2'\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product2'\n    },\n\n    // Try to scan WH/Stock 2 as the destination -> Should display an error notification.\n    { trigger: '.o_barcode_line.o_selected.o_line_completed', run: 'scan WHSTOCK-2' },\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: () => {\n            helper.assertErrorMessage(\"The scanned location doesn't belong to this operation's destination\");\n    }},\n    // Scan Shelf1 as scanned product2 destination.\n    { trigger: '.o_barcode_client_action', run: 'scan LOC-01-01-00' },\n\n    {\n        trigger: '.o_scan_message.o_scan_product',\n        run: 'scan product1'\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1'\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1'\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: function() {\n            helper.assertValidateIsHighlighted(false);\n        }\n    },\n\n    ...stepUtils.inputManuallyBarcode(\"product1\"),\n\n    {\n        trigger: '.o_barcode_line[data-barcode=\"product1\"] .qty-done:contains(\"4\")',\n        run: function() {\n            helper.assertValidateIsHighlighted(true);\n        }\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-01-00'\n    },\n\n    {\n        trigger: '.o_scan_message.o_scan_validate',\n        run: function() {\n            helper.assertLinesCount(2);\n            helper.assertScanMessage('scan_validate');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(true);\n            helper.assertValidateEnabled(true);\n        }\n    },\n\n    {\n        trigger: '.o_add_line',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_field_widget[name=\"product_id\"]',\n        run: function() {\n            helper.assertFormLocationDest('WH/Stock');\n        },\n    },\n    ...stepUtils.discardBarcodeForm(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_receipt_reserved_2_partial_put_in_pack', { steps: () => [\n    // Scan the picking's name to open it.\n    { trigger: '.o_stock_barcode_main_menu', run: 'scan receipt_test' },\n    {\n        trigger: '.o_barcode_line',\n        run: function() {\n            helper.assertLinesCount(2);\n            helper.assertLineProduct(0, \"product1\");\n            helper.assertLineQty(0, \"0/3\");\n            helper.assertLineProduct(1, \"product2\");\n            helper.assertLineQty(1, \"0/3\");\n        },\n    },\n\n    // Scan 2x product1 then put in pack.\n    { trigger: '.o_barcode_client_action', run: 'scan product1'},\n    { trigger: '.o_barcode_client_action', run: 'scan product1'},\n    {\n        trigger: '.o_barcode_line.o_selected .qty-done:contains(\"2\")',\n        run: function() {\n            helper.assertLinesCount(2);\n            helper.assertLineProduct(0, \"product1\");\n            helper.assertLineQty(0, \"2/3\");\n            helper.assertLineProduct(1, \"product2\");\n            helper.assertLineQty(1, \"0/3\");\n        },\n    },\n    { trigger: '.o_barcode_client_action', run: 'scan OBTPACK'},\n    {\n        trigger: '.o_barcode_line:contains(\"PACK0001000\")',\n        run: function() {\n            const lines = helper.getLines();\n            helper.assert(lines.length, 3);\n\n            helper.assertLineProduct(lines[0], \"product1\");\n            helper.assertLineQty(lines[0], \"0/1\");\n            helper.assert(lines[0].querySelector('.result-package'), null);\n\n            helper.assertLineProduct(lines[1], \"product2\");\n            helper.assertLineQty(lines[1], \"0/3\");\n            helper.assert(lines[1].querySelector('.result-package'), null);\n\n            helper.assertLineProduct(lines[2], \"product1\");\n            helper.assertLineQty(lines[2], \"2/2\");\n            helper.assert(lines[2].querySelector('.result-package').innerText, \"PACK0001000\");\n        },\n    },\n\n    // Scan product1 and product2 then put in pack.\n    { trigger: '.o_barcode_client_action', run: 'scan product1'},\n    { trigger: '.o_barcode_line:first-child.o_selected.o_line_completed', run: 'scan product2'},\n    {\n        trigger: '.o_barcode_line[data-barcode=\"product2\"].o_selected .qty-done:contains(\"1\")',\n        run: function() {\n            const lines = helper.getLines();\n            helper.assert(lines.length, 3);\n\n            helper.assertLineProduct(lines[0], \"product1\");\n            helper.assertLineQty(lines[0], \"1/1\");\n            helper.assert(lines[0].querySelector('.result-package'), null);\n\n            helper.assertLineProduct(lines[1], \"product2\");\n            helper.assertLineQty(lines[1], \"1/3\");\n            helper.assert(lines[1].querySelector('.result-package'), null);\n\n            helper.assertLineProduct(lines[2], \"product1\");\n            helper.assertLineQty(lines[2], \"2/2\");\n            helper.assert(lines[2].querySelector('.result-package').innerText, \"PACK0001000\");\n        },\n    },\n    {\n        trigger: '.o_put_in_pack',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"PACK0001001\")',\n        run: function() {\n            const lines = helper.getLines();\n            helper.assert(lines.length, 4);\n\n            helper.assertLineProduct(lines[0], \"product2\");\n            helper.assertLineQty(lines[0], \"0/2\");\n            helper.assert(lines[0].querySelector('.result-package'), null);\n\n            helper.assertLineProduct(lines[1], \"product1\");\n            helper.assertLineQty(lines[1], \"2/2\");\n            helper.assert(lines[1].querySelector('.result-package').innerText, \"PACK0001000\");\n\n            helper.assertLineProduct(lines[2], \"product1\");\n            helper.assertLineQty(lines[2], \"1/1\");\n            helper.assert(lines[2].querySelector('.result-package').innerText, \"PACK0001001\");\n\n            helper.assertLineProduct(lines[3], \"product2\");\n            helper.assertLineQty(lines[3], \"1/1\");\n            helper.assert(lines[3].querySelector('.result-package').innerText, \"PACK0001001\");\n        },\n    },\n    // Confirm the backorder, then close the receipt.\n    { trigger: '.btn.o_validate_page', run: 'click' },\n    { trigger: '.modal-dialog button.btn-primary', run: 'click' },\n    { trigger: '.o_stock_barcode_main_menu' },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_receipt_product_not_consecutively', { steps: () => [\n    // Scan two products (product1 - product2 - product1)\n    { trigger: '.o_barcode_client_action', run: 'scan product1' },\n    { trigger: '.o_barcode_line', run: 'scan product2' },\n    { trigger: '.o_barcode_line:contains(\"product2\")', run: 'scan product1' },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"product1\"] .qty-done:contains(\"2\")',\n        run: 'scan OBTVALI',\n    },\n    {\n        trigger: '.o_notification_bar.bg-success',\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add(\"test_delivery_source_location\", { steps: () => [\n    // FIRST DELIVERY (using stock from WH/Stock)\n    { trigger: \".o_stock_barcode_main_menu\", run: 'scan delivery_from_stock' },\n    // Tries to scan a location who doesn't belong to the delivery's source location.\n    { trigger: '.o_scan_message.o_scan_src', run: 'scan WH-SECOND-STOCK' },\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: () => {\n            helper.assertErrorMessage(\"The scanned location doesn't belong to this operation's location\");\n    }},\n    {\n        trigger: 'button.o_notification_close',\n        run: \"click\",\n    },\n    // Scans the right location now.\n    { trigger: '.o_barcode_client_action', run: 'scan LOC-01-00-00' },\n    { trigger: '.o_scan_message.o_scan_product', run: 'scan product1' },\n    { trigger: '.o_barcode_line.o_selected', run: 'scan product1' },\n    { trigger: '.o_barcode_line.o_selected', run: 'scan product1' },\n    { trigger: '.o_barcode_line.o_selected', run: 'scan product1' },\n    {\n        trigger: '.o_validate_page.btn-primary',\n        run: \"click\",\n    },\n\n    // SECOND DELIVERY (using stock from WH/Second Stock)\n    { trigger: \".o_stock_barcode_main_menu\", run: 'scan delivery_from_second_stock' },\n    // Tries to scan a location who doesn't belong to the delivery's source location.\n    { trigger: '.o_scan_message.o_scan_src', run: 'scan LOC-01-00-00' },\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: () => {\n            helper.assertErrorMessage(\"The scanned location doesn't belong to this operation's location\");\n    }},\n    {\n        trigger: 'button.o_notification_close',\n        run: \"click\",\n    },\n    // Scans the right location now.\n    { trigger: '.o_barcode_client_action', run: 'scan WH-SECOND-STOCK' },\n    { trigger: '.o_scan_message.o_scan_product', run: 'scan product1' },\n    { trigger: '.o_barcode_line.o_selected', run: 'scan product1' },\n    { trigger: '.o_barcode_line.o_selected', run: 'scan product1' },\n    { trigger: '.o_barcode_line.o_selected', run: 'scan product1' },\n    ...stepUtils.validateBarcodeOperation('.o_validate_page.btn-primary'),\n\n    // Create a delivery on the fly and try to use both locations as source.\n    // Since the delivery is not planned and there is no way for the user to set\n    // that from the Barcode app, it should be possible.\n    { trigger: \".o_stock_barcode_main_menu\", run: 'scan WHOUT' },\n    { trigger: '.o_barcode_client_action', run: 'scan WH-SECOND-STOCK' },\n    { trigger: '.o_scan_message.o_scan_product', run: 'scan product1' },\n    { trigger: '.o_barcode_line', run: 'scan LOC-01-00-00' },\n    { trigger: '.o_scan_message.o_scan_validate', run: 'scan product1' },\n    {\n        trigger: '.o_barcode_location_group + .o_barcode_location_group',\n        run: () => {\n            helper.assertLinesCount(2);\n            helper.assertLineSourceLocation(0, \"WH/Second Stock\");\n            helper.assertLineSourceLocation(1, \"WH/Stock\");\n        }\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add(\"test_delivery_lot_with_multi_companies\", { steps: () => [\n    // Scans tsn-002: should find nothing since this SN belongs to another company.\n    { trigger: \".o_barcode_client_action\", run: \"scan tsn-002\" },\n    // Checks a warning was displayed and scans tsn-001: a line should be added.\n    { trigger: \".o_notification_bar.bg-danger\", run: \"scan tsn-001\" },\n    {\n        trigger: \".o_barcode_line\",\n        run: function() {\n            const line = helper.getLine({ barcode: \"productserial1\" });\n            helper.assert(line.querySelector(\".o_line_lot_name\").innerText, \"tsn-001\");\n        },\n    },\n    // Scans tsn-003 then validate the delivery.\n    { trigger: \".o_barcode_client_action\", run: \"scan tsn-003\" },\n    {\n        trigger: \".o_toggle_sublines\", // Should have sublines since there is two SN.\n    },\n    {\n        trigger: \".o_validate_page\",\n        run: \"click\",\n    },\n    { trigger: \".o_notification_bar.bg-success\"},\n]});\n\nregistry.category(\"web_tour.tours\").add('test_delivery_lot_with_package', { steps: () => [\n    // Unfold grouped lines.\n    {\n        trigger: '.o_line_button.o_toggle_sublines',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_client_action:contains(\"sn2\")',\n        run: function() {\n            helper.assertLinesCount(1);\n            helper.assertSublinesCount(2);\n            helper.assertScanMessage('scan_serial');\n            const [ line1, line2 ] = helper.getSublines();\n            helper.assert(line1.querySelector('.o_line_lot_name').innerText, 'sn1');\n            helper.assert(line1.querySelector('.fa-archive').parentElement.innerText.includes(\"pack_sn_1\"), true);\n            helper.assert(line2.querySelector('.o_line_lot_name').innerText, 'sn2');\n            helper.assert(line2.querySelector('.fa-archive').parentElement.innerText.includes(\"pack_sn_1\"), true);\n        }\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan productserial1'\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan sn3'\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan sn4'\n    },\n\n    {\n        trigger: '.o_barcode_client_action:contains(\"sn4\")',\n        run: function() {\n            helper.assertLinesCount(1);\n            helper.assertSublinesCount(4);\n            helper.assertScanMessage('scan_validate');\n            const [ line1, line2, line3, line4 ] = helper.getSublines();\n            helper.assert(line1.querySelector('.o_line_lot_name').innerText, \"sn1\");\n            helper.assert(line1.querySelector('.o_line_owner'), null);\n            helper.assert(line1.querySelector('.result-package').innerText, \"pack_sn_1\");\n            helper.assert(line1.querySelector('.package').innerText, \"pack_sn_1\");\n            helper.assert(line2.querySelector('.o_line_lot_name').innerText, \"sn3\");\n            helper.assert(line2.querySelector('.o_line_owner'), null);\n            helper.assert(line2.querySelector('.package').innerText, \"pack_sn_2\");\n            helper.assert(line3.querySelector('.o_line_lot_name').innerText, \"sn4\");\n            helper.assert(line3.querySelector('.o_line_owner').innerText, \"Particulier\");\n            helper.assert(line3.querySelector('.package').innerText, \"pack_sn_2\");\n            helper.assert(line4.querySelector('.o_line_lot_name').innerText, \"sn2\");\n            helper.assert(line4.querySelector('.o_line_owner'), null);\n            helper.assert(line4.querySelector('.result-package').innerText, \"pack_sn_1\");\n            helper.assert(line4.querySelector('.package').innerText, \"pack_sn_1\");\n        }\n    },\n\n    // Open the form view to trigger a save\n    {\n        trigger: '.o_sublines .o_barcode_line:nth-child(3) .fa-pencil',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_field_widget[name=\"product_id\"]',\n        run: function() {\n            helper.assertFormQuantity(\"1\");\n            helper.assert(document.querySelector('div[name=\"package_id\"] input').value, \"pack_sn_2\");\n            helper.assert(document.querySelector('div[name=\"result_package_id\"] input').value, \"\");\n            helper.assert(document.querySelector('div[name=\"owner_id\"] input').value, \"Particulier\");\n            helper.assert(document.querySelector('div[name=\"lot_id\"] input').value, \"sn4\");\n        },\n    },\n    {\n        trigger: '.o_discard',\n        run: \"click\",\n    },\n    ...stepUtils.validateBarcodeOperation(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_delivery_lot_with_package_delivery_step', { steps: () => [\n    {\n        trigger: '.o_barcode_line',\n        run: 'scan LOC-01-02-00',\n    },\n    {\n        trigger: '.o_barcode_line',\n        run: 'scan productlot1',\n    },\n    {\n        trigger: '.o_barcode_line',\n        run: 'scan sn'\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"sn\")',\n        run: 'scan OBTVALI'\n    },\n    {\n        trigger: '.o_notification_bar.bg-success',\n        run: function () {\n            helper.assertErrorMessage('The transfer has been validated');\n        },\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_delivery_reserved_1', { steps: () => [\n    // test that picking note properly pops up + close it\n    {\n        trigger: '.alert:contains(\"A Test Note\")',\n        run: \"click\",\n    },\n    {\n        trigger: '.alert button.btn-close',\n        run: \"click\",\n    },\n    // Opens and close the line's form view to be sure the note is still hidden.\n    {\n        trigger: '.o_add_line',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_discard',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_lines',\n        run: function() {\n            const note = document.querySelector('.alert.alert-warning');\n            helper.assert(Boolean(note), false, \"Note must not be present\");\n        }\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: function() {\n            helper.assertLinesCount(2);\n            helper.assertScanMessage('scan_src');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(true);\n        }\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-00-00'\n    },\n\n    {\n        trigger: '.o_scan_message.o_scan_product',\n        run: function() {\n            helper.assertLinesCount(2);\n            helper.assertScanMessage('scan_product');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(true);\n        }\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1'\n    },\n\n    {\n        trigger: '.o_barcode_line:nth-child(2).o_selected',\n        run: 'scan product2'\n    },\n    { trigger: '.o_barcode_line:nth-child(3).o_selected' },\n\n    // Display the picking's information to trigger a save.\n    {\n        trigger: '.o_barcode_header .o_title',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_control .btn.o_discard',\n        run: \"click\",\n    },\n    { trigger: '.o_barcode_line' },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_delivery_reserved_2', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function() {\n            helper.assertLinesCount(2);\n            helper.assertScanMessage('scan_product');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(true);\n        }\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1'\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1'\n    },\n\n    {\n        trigger: '.o_barcode_line.o_selected.o_line_completed',\n        run: 'scan product2'\n    },\n    ...stepUtils.confirmAddingUnreservedProduct(),\n\n    {\n        trigger: '.o_barcode_line.o_selected:contains(\"product2\")',\n        run: function() {\n            helper.assertLinesCount(3);\n            helper.assertScanMessage('scan_product');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(true);\n            const p1_lines = helper.getLines({ barcode: 'product1' });\n            helper.assertLineIsFaulty(p1_lines[0], false);\n            helper.assertLineIsFaulty(p1_lines[1], false);\n            const p2_line = helper.getLine({ barcode: 'product2' });\n            helper.assertLineIsFaulty(p2_line, true);\n        }\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1'\n    },\n\n    {\n        trigger: '.o_barcode_line.o_selected:not(.o_line_completed)',\n        run: 'scan product1'\n    },\n\n    {\n        trigger: '.o_barcode_line.o_selected.o_line_completed',\n        run: function() {\n            helper.assertLinesCount(3);\n            helper.assertScanMessage('scan_validate');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(true);\n            helper.assertValidateEnabled(true);\n            const lines = helper.getLines({ barcode: 'product1' });\n            [0, 1].map(i => helper.assertLineQty(lines[i], \"2/2\"));\n        }\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1'\n    },\n\n    {\n        trigger: '.o_barcode_line:nth-child(4)',\n        run: function () {\n            helper.assertLinesCount(4);\n            helper.assertScanMessage('scan_validate');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(true);\n            helper.assertValidateEnabled(true);\n            const p1_lines = helper.getLines({ barcode: 'product1' });\n            helper.assertLineIsFaulty(p1_lines[0], false);\n            helper.assertLineIsFaulty(p1_lines[1], false);\n            helper.assertLineIsFaulty(p1_lines[2], true);\n            const p2_line = helper.getLine({ barcode: 'product2' });\n            helper.assertLineIsFaulty(p2_line, true);\n        }\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_delivery_reserved_3', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function() {\n            helper.assertLinesCount(1);\n            helper.assertScanMessage('scan_product');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(true);\n        }\n    },\n    { trigger: '.o_barcode_client_action', run: 'scan product1' },\n    { trigger: '.o_barcode_client_action', run: 'scan this_is_not_a_barcode_dude' },\n    {\n        trigger: '.o_barcode_line.o_highlight',\n        run: function() {\n            helper.assertLinesCount(1);\n            helper.assertScanMessage('scan_validate');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(true);\n            helper.assertValidateEnabled(true);\n            helper.assertLineIsHighlighted(0, true);\n            helper.assertLineQty(0, \"1/1\");\n        }\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add(\"test_delivery_reserved_4_backorder\", {  steps: () => [\n    {\n        trigger: \".o_barcode_client_action\",\n        run: function() {\n            // The picking has 3 moves but only 2 barcode lines because the move for product3\n            // has no reservation, so no move line, so no barcode line neither.\n            helper.assertLinesCount(2);\n            helper.assertLineQty(0, \"0/4\"); // 4 demand, 4 reserved.\n            helper.assertLineQty(1, \"0/2\"); // 4 demand but only 2 reserved.\n        }\n    },\n    // Scans product1 then tries to validate again -> Should display the backorder dialog.\n    { trigger: \".o_barcode_client_action\", run: \"scan product1\" },\n    {\n        trigger: \".o_barcode_line.o_selected\",\n    },\n    {\n        trigger: \".o_validate_page\",\n        run: \"click\",\n    },\n    {\n        trigger: \".modal-content.o_barcode_backorder_dialog\",\n        run: function() {\n            const incompleteLines = document.querySelectorAll(\".o_barcode_backorder_product_row\");\n            helper.assert(incompleteLines.length, 2);\n            const [line1, line2] = incompleteLines;\n            helper.assert(line1.querySelector(\"[name='qty-done']\").innerText, \"1\");\n            helper.assert(line1.querySelector(\"[name='reserved-qty']\").innerText, \"4\");\n            helper.assert(line1.querySelector(\"[name='backorder-qty']\").innerText, \"3\");\n            helper.assert(line2.querySelector(\"[name='qty-done']\").innerText, \"0\");\n            helper.assert(line2.querySelector(\"[name='reserved-qty']\").innerText, \"2\");\n            helper.assert(line2.querySelector(\"[name='backorder-qty']\").innerText, \"2\");\n        },\n    },\n    {\n        trigger: \".modal-dialog button.btn-secondary\",\n        run: \"click\",\n    }, // Cancel -> Stay on the delivery.\n    // Scans 3 more times product1 to complete the line then clicks on validate again.\n    { trigger: \".o_barcode_client_action\", run: \"scan product1\" },\n    { trigger: \".o_barcode_client_action\", run: \"scan product1\" },\n    { trigger: \".o_barcode_client_action\", run: \"scan product1\" },\n    {\n        trigger: \".o_barcode_line.o_selected.o_line_completed\",\n    },\n    {\n        trigger: \".o_validate_page\",\n        run: \"click\",\n    },\n    {\n        trigger: \".modal-content.o_barcode_backorder_dialog\",\n        run: function() {\n            const incompleteLines = document.querySelectorAll(\".o_barcode_backorder_product_row\");\n            helper.assert(incompleteLines.length, 1);\n            const [incompleteLine] = incompleteLines;\n            helper.assert(incompleteLine.querySelector(\"[name='qty-done']\").innerText, \"0\");\n            helper.assert(incompleteLine.querySelector(\"[name='reserved-qty']\").innerText, \"2\");\n            helper.assert(incompleteLine.querySelector(\"[name='backorder-qty']\").innerText, \"2\");\n        },\n    },\n    {\n        trigger: \".modal-dialog button.btn-primary\",\n        run: \"click\",\n    }, // Validate -> Should create a backorder.\n    {\n        trigger: \".o_notification\",\n        run: function() {\n            const backorderLink = document.querySelector(\".o_notification_buttons span\");\n            helper.assert(\n                backorderLink.innerText.includes(\"WH/OUT/\"), true,\n                \"The notification should contain a link to the created backorder.\"\n            );\n        },\n    }\n]});\n\nregistry.category(\"web_tour.tours\").add(\"test_delivery_reserved_5_dont_show_reserved_sn\", {  steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function() {\n            helper.assertScanMessage('scan_product');\n            helper.assertLinesCount(1);\n            helper.assertLineQty(0, \"0/4\");\n            helper.assertLineProduct(0, \"productserial1\");\n            helper.assertLineTrackingNumber(0, \"\");\n            helper.assertButtonIsVisible(0, \"toggle_sublines\", false);\n            helper.assertButtonIsVisible(0, \"edit\");\n        }\n    },\n    { trigger: '.o_barcode_client_action', run: 'scan productserial1' },\n    // Increases qty via the form view. Since it's a move line form view, a SN is already set.\n    { trigger: '.o_barcode_line.o_selected .btn.o_edit', run: \"click\" },\n    {\n        trigger: \".o_form_view_container\",\n        run: () => {\n            const lotField = document.querySelector('.o_field_widget[name=\"lot_id\"] input');\n            helper.assert(lotField.value, \"sn1\", \"Should display move line for sn1\");\n        }\n    },\n    { trigger: '.o_field_widget[name=qty_done] input', run: \"clear\" },\n    { trigger: '.o_field_widget[name=qty_done] input', run: \"edit 1\" },\n    { trigger: '.o_save', run: \"click\" },\n    // Now there is at least 1 qty for a specific SN, this SN should be visible.\n    {\n        trigger: '.o_barcode_line',\n        run: () => {\n            helper.assertScanMessage('scan_serial');\n            helper.assertLinesCount(1);\n            helper.assertLineQty(0, \"1/4\");\n            helper.assertLineTrackingNumber(0, \"sn1\");\n            helper.assertButtonIsVisible(0, \"toggle_sublines\", false);\n        }\n    },\n    // Opens it again to checks it still the same move line who is displayed.\n    { trigger: '.o_barcode_line.o_selected .btn.o_edit', run: \"click\" },\n    {\n        trigger: \".o_form_view_container\",\n        run: () => {\n            const lotField = document.querySelector('.o_field_widget[name=\"lot_id\"] input');\n            helper.assert(lotField.value, \"sn1\", \"Should still display move line for sn1\");\n        }\n    },\n    { trigger: '.o_discard', run: \"click\" },\n\n    // Scans sn5 (not reserved). As soon there is at least two scanned SN,\n    // the button to display sublines should be visible.\n    { trigger: '.o_barcode_client_action', run: 'scan sn5' },\n    {\n        trigger: '.o_line_button.o_toggle_sublines',\n        run: function() {\n            helper.assertLineQty(0, \"2/4\");\n        }\n    },\n    // Scans 2 more SN to complete the delivery and validates it.\n    { trigger: '.o_barcode_client_action', run: 'scan sn2' },\n    { trigger: '.o_barcode_client_action', run: 'scan sn3' },\n    ...stepUtils.validateBarcodeOperation(\".o_barcode_line.o_selected.o_line_completed\"),\n]});\n\nregistry.category(\"web_tour.tours\").add(\"test_delivery_reserved_6_dont_show_reserved_lots\", {  steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function() {\n            helper.assertScanMessage('scan_product');\n            helper.assertLinesCount(1);\n            helper.assertLineQty(0, \"0/12\");\n            helper.assertLineProduct(0, \"productlot1\");\n            helper.assertButtonIsVisible(0, \"toggle_sublines\", false);\n            helper.assertButtonIsVisible(0, \"edit\");\n            helper.assertLineTrackingNumber(0, \"\");\n        }\n    },\n    { trigger: '.o_barcode_client_action', run: 'scan productlot1' },\n    {\n        trigger: '.o_barcode_line.o_selected',\n        run: function() {\n            helper.assertScanMessage('scan_lot');\n            helper.assertLinesCount(1);\n            helper.assertLineQty(0, \"0/12\");\n            helper.assertLineProduct(0, \"productlot1\");\n            helper.assertButtonIsVisible(0, \"toggle_sublines\", false);\n            helper.assertButtonIsVisible(0, \"edit\");\n            helper.assertLineTrackingNumber(0, \"\");\n        }\n    },\n    { trigger: '.o_barcode_client_action', run: 'scan lot-001' },\n    {\n        trigger: '.o_line_lot_name:contains(\"lot\")',\n        run: function() {\n            helper.assertScanMessage('scan_lot');\n            helper.assertLinesCount(1);\n            helper.assertLineQty(0, \"1/12\");\n            helper.assertLineProduct(0, \"productlot1\");\n            helper.assertButtonIsVisible(0, \"toggle_sublines\", false);\n            helper.assertButtonIsVisible(0, \"edit\");\n            helper.assertLineTrackingNumber(0, \"lot-001\");\n        }\n    },\n    // Scan a second lot, the scanned lots should be visible in sublines.\n    { trigger: '.o_barcode_client_action', run: 'scan lot-002' },\n    {\n        trigger: '.o_line_button.o_toggle_sublines',\n        run: function() {\n            helper.assertScanMessage('scan_lot');\n            helper.assertLinesCount(1);\n            helper.assertLineQty(0, \"2/12\");\n            helper.assertLineProduct(0, \"productlot1\");\n            helper.assertButtonIsVisible(0, \"toggle_sublines\");\n            helper.assertButtonIsVisible(0, \"edit\", false);\n            helper.assertLineTrackingNumber(0, false);\n        }\n    },\n    // Display sublines.\n    { trigger: '.o_line_button.o_toggle_sublines', run: \"click\" },\n    {\n        trigger: '.o_sublines .o_barcode_line',\n        run: function() {\n            const sublines = helper.getSublines();\n            helper.assertLinesCount(1);\n            helper.assertSublinesCount(2);\n            helper.assertLineQty(sublines[0], \"1/4\");\n            helper.assertLineQty(sublines[1], \"1/4\");\n            helper.assertLinesTrackingNumbers(sublines, [\"lot-001\", \"lot-002\"]);\n        }\n    },\n    // Scan unreserved lot (lot-005).\n    { trigger: '.o_barcode_client_action', run: 'scan lot-005' },\n    {\n        trigger: '.o_sublines .o_barcode_line:nth-child(3)',\n        run: function() {\n            const sublines = helper.getSublines();\n            helper.assertLinesCount(1);\n            helper.assertSublinesCount(3);\n            helper.assertLineQty(sublines[0], \"1/4\");\n            helper.assertLineQty(sublines[1], \"1/4\");\n            helper.assertLineQty(sublines[2], \"1\");\n            helper.assertLinesTrackingNumbers(sublines, [\"lot-001\", \"lot-002\", \"lot-005\"]);\n        }\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_delivery_using_buttons', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function() {\n            helper.assertLinesCount(3);\n            helper.assertScanMessage('scan_product');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(true);\n            helper.assertLineQty(0, \"0/2\");\n            helper.assertLineQty(1, \"0/3\");\n            helper.assertLineQty(2, \"0/4\");\n            helper.assertButtonIsVisible(0, \"add_quantity\", false);\n            helper.assertButtonIsVisible(1, \"add_quantity\", false);\n            helper.assertButtonIsVisible(2, \"add_quantity\", false);\n            helper.assertButtonIsVisible(0, \"add_remaining_quantity\");\n            helper.assertButtonIsVisible(1, \"add_remaining_quantity\");\n            helper.assertButtonIsVisible(2, \"add_remaining_quantity\");\n        }\n    },\n\n    // On the first line, goes on the form view and press digipad +1 button.\n    {\n        trigger: '.o_barcode_line:first-child .o_edit',\n        run: \"click\",\n    },\n    {\n        trigger: 'button.o_digipad_increment',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_save',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_lines',\n        run: function() {\n            helper.assertButtonIsVisible(0, \"remove_unit\");\n            helper.assertButtonIsVisible(0, \"add_quantity\", false);\n            helper.assertButtonIsVisible(0, \"add_remaining_quantity\");\n            helper.assertLineQty(0, '1/2');\n            helper.assertLineIsHighlighted(0, true);\n            helper.assertLineIsHighlighted(1, false);\n            helper.assertLineIsHighlighted(2, false);\n        }\n    },\n    // Press +1 button again, now its buttons must be hidden.\n    {\n        trigger: '.o_barcode_line:first-child .o_add_remaining_quantity',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:first-child.o_selected.o_line_completed',\n        run: function() {\n            helper.assertLineQty(0, '2/2');\n            helper.assertButtonIsVisible(0, \"remove_unit\");\n            helper.assertButtonIsVisible(0, \"add_quantity\", false);\n            helper.assertButtonIsVisible(0, \"add_remaining_quantity\", false);\n            helper.assertLineQty(1, '0/3');\n            helper.assertButtonIsVisible(1, \"add_quantity\", false);\n            helper.assertButtonIsVisible(1, \"add_remaining_quantity\");\n        }\n    },\n    // Press the add remaining quantity button.\n    {\n        trigger: '.o_barcode_line:nth-child(2) .o_add_remaining_quantity',\n        run: \"click\",\n    },\n    // Product2 is now done, its button must be hidden.\n    {\n        trigger: '.o_barcode_line:nth-child(2).o_selected.o_line_completed',\n        run: function() {\n            helper.assertLineIsHighlighted(0, false);\n            helper.assertLineIsHighlighted(1, true);\n            helper.assertLineIsHighlighted(2, false);\n            helper.assertLineQty(1, '3/3');\n            helper.assertButtonIsVisible(1, \"remove_unit\");\n            helper.assertButtonIsVisible(1, \"add_quantity\", false);\n            helper.assertButtonIsVisible(1, \"add_remaining_quantity\", false);\n            helper.assertLineQty(2, \"0/4\");\n            helper.assertButtonIsVisible(2, \"add_quantity\", false);\n            helper.assertButtonIsVisible(2, \"add_remaining_quantity\");\n        }\n    },\n    // Scan product3 one time, then checks the quantities.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product3',\n    },\n    {\n        trigger: '.o_barcode_line:last-child.o_selected .qty-done:contains(\"1\")',\n        run: function() {\n            helper.assertLineQty(2, \"1/4\");\n            helper.assertButtonIsVisible(2, \"add_quantity\");\n            helper.assertButtonIsVisible(2, \"add_remaining_quantity\");\n            helper.assertLineIsHighlighted(0, false);\n            helper.assertLineIsHighlighted(1, false);\n            helper.assertLineIsHighlighted(2, true);\n        }\n    },\n    // Goes on the form view and press digipad +1 button.\n    {\n        trigger: '.o_barcode_line:last-child .o_edit',\n        run: \"click\",\n    },\n    {\n        trigger: 'button.o_digipad_increment',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_save',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_lines',\n        run: function() {\n            helper.assertLineQty(0, \"2/4\");\n            helper.assertButtonIsVisible(0, \"add_quantity\");\n            helper.assertButtonIsVisible(0, \"add_remaining_quantity\");\n        }\n    },\n    // Press the add remaining quantity button, then the button must be hidden.\n    {\n        trigger: '.o_barcode_line:first-child .o_add_remaining_quantity',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:first-child .qty-done:contains(\"4\")',\n        run: function() {\n            helper.assertLineQty(0, \"4/4\");\n            helper.assertButtonIsVisible(0, \"add_quantity\", false);\n            helper.assertButtonIsVisible(0, \"add_remaining_quantity\", false);\n            helper.assertValidateIsHighlighted(true);\n        }\n    },\n\n    // Now, scan one more time the product3 to create a new line (its +1 button must be visible).\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product3',\n    },\n    // The new line is created at the second position (directly below the previous selected line).\n    {\n        trigger: '.o_barcode_line:nth-child(2).o_selected',\n        run: function() {\n            helper.assertLinesCount(4);\n            helper.assertLineIsHighlighted(0, false);\n            helper.assertLineIsHighlighted(1, true);\n            helper.assertLineIsHighlighted(2, false);\n            helper.assertLineIsHighlighted(3, false);\n            helper.assertLineQty(1, \"1\");\n            // +1 button must be present on new line.\n            helper.assertButtonIsVisible(1, \"remove_unit\");\n            helper.assertButtonIsVisible(1, \"add_quantity\");\n            helper.assertButtonIsVisible(1, \"add_remaining_quantity\", false);\n            helper.assertButtonIsVisible(1, \"delete_line\");\n        }\n    },\n    // Press +1 button of the new line.\n    {\n        trigger: '.o_barcode_line:nth-child(2) .o_add_quantity',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:nth-child(2) .qty-done:contains(\"2\")',\n        run: function() {\n            helper.assertLineIsHighlighted(0, false);\n            helper.assertLineIsHighlighted(1, true);\n            helper.assertLineIsHighlighted(2, false);\n            helper.assertLineIsHighlighted(3, false);\n            helper.assertLineQty(1, '2');\n            // +1 button must still be present.\n            helper.assertButtonIsVisible(1, 'add_quantity');\n        }\n    },\n\n    // Validate the delivery.\n    {\n        trigger: '.o_validate_page',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_notification_bar.bg-success',\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_remaining_decimal_accuracy', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function() {\n            helper.assertScanMessage('scan_product');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(true);\n            helper.assertLineQty(0, \"0/4\");\n            helper.assertLineQty(1, \"0/0.12\");\n            helper.assertLineQty(2, \"0/4\");\n            helper.assertButtonIsVisible(0, \"add_remaining_quantity\");\n            helper.assertButtonIsVisible(1, \"add_remaining_quantity\");\n        }\n    },\n\n    // Goes on the first line form view and add 2.2 .\n    {\n        trigger: '.o_barcode_line[data-barcode=\"product1\"] .o_edit',\n        run: \"click\",\n    },\n    {\n        trigger: 'div[name=qty_done] input',\n        run() {\n            //input type number not supported by tour helpers.\n            // It would work if the clipboard was mocked in tours the same way it is in unit tests.\n            this.anchor.value = \"2.2\";\n            this.anchor.dispatchEvent(new InputEvent(\"input\", { bubbles: true }));\n        }\n    },\n    {\n        trigger: '.o_save',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_lines',\n        run: function() {\n            helper.assertButtonIsVisible(0, \"add_remaining_quantity\");\n            helper.assertLineQty(0, '2.2/4');\n            const buttonAddQty = document.querySelector(\".o_barcode_line:first-child .o_add_remaining_quantity\");\n            helper.assert(buttonAddQty.innerText, \"+1.8\", \"Something wrong with the quantities\");\n        }\n    },\n    // Adds 0.12 (entire demand, less than 1) of the second product\n    {\n        trigger: '.o_barcode_line:last-child .o_add_remaining_quantity:contains(\"0.12\")',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_lines',\n        run: function() {\n            helper.assertButtonIsVisible(1, 'add_remaining_quantity', false);\n            helper.assertLineQty(1, '0.12/0.12');\n        }\n    },\n\n    // test qty buttons are correct for grouped lines\n    {\n        trigger: '.o_line_button.o_toggle_sublines',\n        run: \"click\",\n    },\n    // Go on the form view and update the lot1 with 2.345 .\n    {\n        trigger: '.o_sublines .o_barcode_line:first-child .fa-pencil',\n        run: \"click\",\n    },\n    {\n        trigger: 'div[name=qty_done] input',\n        run() {\n            this.anchor.value = \"2.345\";\n        }\n    },\n    {\n        trigger: '.o_save',\n        run: \"click\",\n    },\n    // Check the lot2 qty button display \"+1.65\"\n    {\n        trigger: '.o_sublines .o_barcode_line:first-child',\n        run: function() {\n            const buttonAddQty = document.querySelector(\".o_sublines .o_barcode_line:first-child .o_add_remaining_quantity\");\n            helper.assert(buttonAddQty.innerText, \"+1.65\", \"Something wrong with the quantities\");\n        }\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_receipt_from_scratch_with_lots_1', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot1',\n    },\n\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: function () {\n            helper.assertErrorMessage(\"This product doesn't exist.\");\n        },\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan productserial1'\n    },\n\n    {\n        trigger: '.o_barcode_line',\n        run: 'scan lot1',\n    },\n\n    {\n        trigger: '.o_line_lot_name:contains(\"lot1\")',\n        run: 'scan LOC-01-00-00'\n    },\n\n    {\n        trigger: '.o_scan_message.o_scan_validate',\n        run: 'scan productserial1'\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .btn.o_toggle_sublines .fa-angle-down',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_barcode_line:nth-child(2)',\n        run: 'scan lot2',\n    },\n\n    {\n        trigger: '.o_line_lot_name:contains(\"lot2\")',\n        run: 'scan LOC-01-01-00'\n    },\n\n    { trigger: '.o_scan_message.o_scan_validate', run: 'scan productserial1' },\n    { trigger: '.o_scan_message.o_scan_serial', run: 'scan lot3' },\n    { trigger: '.o_scan_message.o_scan_product_or_dest', run: 'scan WHSTOCK-2' },\n    {\n        trigger: '.o_scan_message.o_scan_validate',\n        run: () => {\n            helper.assertLinesCount(3);\n            helper.assertLineDestinationLocation(0, \"WH/Stock\");\n            helper.assertLineDestinationLocation(1, \".../Section 1\");\n            helper.assertLineDestinationLocation(2, \"WH/Stock 2\");\n        }\n    },\n    ...stepUtils.validateBarcodeOperation(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_receipt_from_scratch_with_lots_2', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan productlot1'\n    },\n\n    {\n        trigger: '.o_barcode_line .o_edit',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_input[id=lot_id_0]',\n        run: function () {\n            // Check if the lot_name is invisible\n            helper.assert(document.querySelectorAll('#lot_name_0').length, 0);\n        }\n    },\n\n    {\n        trigger: '.o_save',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_barcode_line',\n        run: 'scan lot1',\n    },\n\n    {\n        trigger: '.o_line_lot_name:contains(lot1)',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_barcode_line .o_edit',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_input[id=\"lot_name_0\"]',\n        run: function () {\n            // check that the lot_id is invisible\n            helper.assert(document.querySelectorAll('#lot_id_0').length, 0);\n         }\n    },\n\n    {\n        trigger: '.o_save',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_line_lot_name:contains(lot1)',\n        run: 'scan lot1',\n    },\n\n    {\n        trigger: '.qty-done:contains(2)',\n        run: 'scan lot2',\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .btn.o_toggle_sublines .fa-angle-down',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_barcode_line:nth-child(2)',\n        run: 'scan lot2',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-01-00'\n    },\n    ...stepUtils.validateBarcodeOperation(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_receipt_from_scratch_with_lots_3', { steps: () => [\n    { trigger: '.o_barcode_client_action', run: 'scan product1' },\n    {\n        trigger: '.o_barcode_line',\n        run: function() {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({barcode: 'product1'});\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, \"1\");\n        }\n    },\n\n    // Scans a second time product1 after going through the edit form view.\n    {\n        trigger: '.o_barcode_line.o_selected .btn.o_edit',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_discard',\n        run: \"click\",\n    },\n    { trigger: '.o_barcode_client_action', run: 'scan product1' },\n\n    {\n        trigger: '.o_barcode_line .qty-done:contains(\"2\")',\n        run: 'scan productlot1'\n    },\n\n    {\n        trigger: '.o_barcode_line:nth-child(2)',\n        run: function() {\n            helper.assertLinesCount(2);\n            const line1 = helper.getLine({ barcode: 'product1' });\n            const line2 = helper.getLine({ barcode: 'productlot1' });\n            helper.assertLineIsHighlighted(line1, false);\n            helper.assertLineQty(line1, \"2\");\n            helper.assertLineIsHighlighted(line2, true);\n            helper.assertLineQty(line2, \"0\");\n        }\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot1',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot1',\n    },\n\n    {\n        trigger: '.o_selected .qty-done:contains(2)',\n        run: function() {\n            helper.assertLinesCount(2);\n            const line1 = helper.getLine({ barcode: 'product1' });\n            const line2 = helper.getLine({ barcode: 'productlot1' });\n            helper.assertLineIsHighlighted(line1, false);\n            helper.assertLineQty(line1, \"2\");\n            helper.assertLineIsHighlighted(line2, true);\n            helper.assertLineQty(line2, \"2\");\n        }\n    },\n    ...stepUtils.validateBarcodeOperation(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_receipt_from_scratch_with_lots_4', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan productserial1',\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan productserial1',\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan productserial1',\n    },\n    {\n        trigger: '.qty-done:contains(\"3\")',\n    },\n    {\n        trigger: '.o_add_line',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_field_widget[name=\"product_id\"]',\n        run: \"click\",\n    },\n    ...stepUtils.discardBarcodeForm(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_delivery_from_scratch_with_lots_1', { steps: () => [\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot1',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot1',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot2',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot2',\n    },\n    // Open the form view to trigger a save\n    {\n        trigger: '.o_barcode_line.o_selected .btn.o_toggle_sublines .fa-angle-down',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:nth-child(2)',\n    },\n    {\n        trigger: '.o_add_line',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_field_widget[name=\"product_id\"]',\n        run: \"click\",\n    },\n    ...stepUtils.discardBarcodeForm(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_delivery_from_scratch_with_incompatible_lot', { steps: () => [\n    { trigger: '.o_barcode_client_action', run: 'scan 0000000001' },\n    {\n        trigger: '.o_barcode_line:first-child .o_edit',\n        run: \"click\",\n    },\n    ...stepUtils.discardBarcodeForm(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_delivery_from_scratch_with_common_lots_name', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1',\n    },\n    {\n        trigger: '.o_barcode_line',\n        run: 'scan LOT01',\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOT01',\n    },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"product1\"] .qty-done:contains(\"2\")',\n        run: 'scan product2',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"product2\")',\n        run: 'scan LOT01',\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOT01',\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOT01',\n    },\n    {\n        trigger: '.qty-done:contains(\"3\")',\n        run: 'scan SUPERSN',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"productserial1\")',\n        run: \"click\",\n    },\n    // Open the form view to trigger a save\n    {\n        trigger: '.o_barcode_line:first-child .o_edit',\n        run: \"click\",\n    },\n    ...stepUtils.discardBarcodeForm(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_receipt_with_sn_1', { steps: () => [\n    { trigger: '.o_barcode_client_action', run: 'scan sn1' },\n    ...stepUtils.validateBarcodeOperation(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_delivery_from_scratch_with_sn_1', { steps: () => [\n    /* scan a product tracked by serial number. Then scan 4 a its serial numbers.\n    */\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan productserial1',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan sn1',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan sn1',\n    },\n\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: function () {\n            helper.assertErrorMessage('The scanned serial number sn1 is already used.');\n        },\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan sn2',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan sn3',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan sn4',\n    },\n    // Open the form view to trigger a save\n    {\n        trigger: '.o_add_line',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_field_widget[name=\"product_id\"]',\n        run: \"click\",\n    },\n\n]});\n\nregistry.category(\"web_tour.tours\").add('test_delivery_reserved_lots_1', { steps: () => [\n    { trigger: '.o_barcode_client_action', run: 'scan productlot1' },\n    { trigger: '.o_barcode_line.o_selected .o_toggle_sublines', run: 'click' },\n    {\n        trigger: '.o_sublines',\n        run: function() {\n            helper.assertLinesCount(1);\n            helper.assertSublinesCount(2);\n            const sublines = helper.getSublines();\n            helper.assertLineQty(sublines[0], \"0/2\");\n            helper.assert(sublines[0].querySelector('button.o_add_remaining_quantity').innerText, \"+2\");\n            helper.assertLineQty(sublines[1], \"0/3\");\n            helper.assert(sublines[1].querySelector('button.o_add_remaining_quantity').innerText, \"+3\");\n        },\n    },\n    { trigger: '.o_barcode_client_action', run: 'scan lot1' },\n    { trigger: '.o_barcode_line.o_selected', run: 'scan lot3' },\n    {\n        trigger: '.o_sublines .o_barcode_line:nth-child(3)',\n        run: function() {\n            helper.assertLinesCount(1);\n            helper.assertSublinesCount(3);\n            const sublines = helper.getSublines();\n            // Check lines and \"Add quantity\" buttons quantities are correctly updated.\n            helper.assertLineQty(sublines[0], \"1/2\");\n            helper.assert(sublines[0].querySelector('button.o_add_remaining_quantity').innerText, \"+1\");\n            helper.assertLineQty(sublines[1], \"1\");\n            helper.assert(sublines[1].querySelector('button.o_add_remaining_quantity').innerText, \"+1\");\n            helper.assertLineQty(sublines[2], \"0/3\");\n            helper.assert(sublines[2].querySelector('button.o_add_remaining_quantity').innerText, \"+3\");\n        },\n    },\n    { trigger: '.o_barcode_client_action', run: 'scan lot1' },\n    {\n        trigger: '.o_sublines .o_barcode_line.o_selected.o_line_completed',\n        run: function() {\n            helper.assertLinesCount(1);\n            helper.assertSublinesCount(3);\n            const sublines = helper.getSublines();\n            helper.assertLineQty(sublines[0], \"2/2\");\n            helper.assert(sublines[0].querySelector('button.o_add_remaining_quantity').innerText, \"+1\");\n            helper.assertLineQty(sublines[1], \"1\");\n            helper.assert(sublines[1].querySelector('button.o_add_remaining_quantity').innerText, \"+1\");\n            helper.assertLineQty(sublines[2], \"0/3\");\n            helper.assert(sublines[2].querySelector('button.o_add_remaining_quantity').innerText, \"+2\");\n        },\n    },\n    { trigger: '.o_barcode_client_action', run: 'scan lot2' },\n    { trigger: '.o_barcode_line.o_selected:not(.o_line_completed)', run: 'scan lot2' },\n    {\n        trigger: '.o_barcode_location_group > .o_barcode_line.o_line_completed',\n        run: function() {\n            helper.assertLinesCount(1);\n            helper.assertSublinesCount(3);\n            const sublines = helper.getSublines();\n            // Since the reservation is completed, no \"Add Quantity\" buttons should be displayed.\n            helper.assertLineQty(sublines[0], \"2/2\");\n            helper.assertButtonIsVisible(sublines[0], \"add_quantity\", false);\n            helper.assertButtonIsVisible(sublines[0], \"o_add_remaining_quantity\", false);\n            helper.assertLineQty(sublines[1], \"1\");\n            helper.assertButtonIsVisible(sublines[1], \"add_quantity\", false);\n            helper.assertButtonIsVisible(sublines[1], \"o_add_remaining_quantity\", false);\n            helper.assertLineQty(sublines[2], \"2/3\");\n            helper.assertButtonIsVisible(sublines[2], \"add_quantity\", false);\n            helper.assertButtonIsVisible(sublines[2], \"o_add_remaining_quantity\", false);\n        },\n    },\n    // Open the form view to trigger a save.\n    { trigger: '.o_add_line', run: \"click\" },\n    { trigger: '.o_field_widget[name=\"product_id\"]' },\n    ...stepUtils.discardBarcodeForm(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_delivery_different_products_with_same_lot_name', { steps: () => [\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan productlot1',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot1',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot1',\n    },\n\n    {\n        trigger: '.o_barcode_line:has(.o_product_label:contains(productlot1)) .qty-done:contains(2)',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan productlot2',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot1',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot1',\n    },\n\n    {\n        trigger: '.o_barcode_line:has(.o_product_label:contains(productlot2)) .qty-done:contains(2)',\n    },\n    // Open the form view to trigger a save\n    {\n        trigger: '.o_add_line',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_field_widget[name=\"product_id\"]',\n        run: \"click\",\n    },\n    ...stepUtils.discardBarcodeForm(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_delivery_reserved_with_sn_1', { steps: () => [\n    /* scan a product tracked by serial number. Then scan 4 a its serial numbers.\n    */\n    { trigger: '.o_barcode_client_action', run: 'scan productserial1' },\n    { trigger: '.o_barcode_line.o_selected', run: 'scan sn3' },\n    { trigger: '.o_barcode_client_action', run: 'scan sn3' },\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: function () {\n            helper.assertErrorMessage('The scanned serial number sn3 is already used.');\n        },\n    },\n\n    { trigger: '.o_barcode_client_action', run: 'scan sn1' },\n    { trigger: '.o_barcode_client_action', run: 'scan sn4' },\n    { trigger: '.o_barcode_client_action', run: 'scan sn2' },\n    { trigger: '.o_barcode_line .qty-done:contains(\"4\")' },\n    // Open the form view to trigger a save\n    { trigger: '.o_add_line', run: \"click\" },\n    {\n        trigger: '.o_field_widget[name=\"product_id\"]',\n        run: \"click\",\n    },\n    ...stepUtils.discardBarcodeForm(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_nomenclature_alias_and_conversion', { steps: () => [\n    // Before all, create a new receipt on the fly.\n    { trigger: '.o_stock_barcode_main_menu', run: 'scan WHIN' },\n    // First, scan the alias and check the product was found (a line was then created.)\n    { trigger: '.o_barcode_client_action', run: 'scan alias_for_upca' },\n    {\n        trigger: '.o_barcode_line.o_selected',\n        run: function () {\n            const line = helper.getLine({ barcode: '123123123125' });\n            helper.assertLineQty(line, \"1\");\n        }\n    },\n    // Secondly, scan the product's barcode but as a EAN-13.\n    { trigger: '.o_barcode_line.o_selected .qty-done:contains(1)', run: 'scan 0123123123125' },\n\n    // Then scan the second alias (who will be replaced by an EAN-13) and check the product is find\n    // in that case too (the EAN-13 should be converted into a UPC-A even if it comes from an alias,\n    // that's where the rules order is important since the alias rule should be used before the rule\n    // who convert an EAN-13 into an UPC-A).\n    { trigger: '.o_barcode_line.o_selected .qty-done:contains(2)', run: 'scan alias_for_ean13' },\n\n    // Finally, checks we can still scan the raw product's barcode :)\n    { trigger: '.o_barcode_line.o_selected .qty-done:contains(3)', run: 'scan 123123123125' },\n    {\n        trigger: '.o_barcode_line.o_selected .qty-done:contains(4)',\n        run: function () {\n            const line = helper.getLine({ barcode: '123123123125' });\n            helper.assertLineQty(line, \"4\");\n        }\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_receipt_reserved_lots_multiloc_1', { steps: () => [\n    /* Receipt of a product tracked by lots. Open an existing picking with 4\n    * units initial demands. Scan 2 units in lot1 in location WH/Stock. Then scan\n    * 2 unit in lot2 in location WH/Stock/Section 2\n    */\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan productlot1',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot1',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot1',\n    },\n\n    {\n        trigger: '.o_barcode_line .qty-done:contains(\"2\")',\n        run: 'scan LOC-01-02-00',\n    },\n\n    {\n        trigger: '.o_barcode_line .o_line_destination_location:contains(\"Section 2\")',\n        run: 'scan productlot1',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot2',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot2',\n    },\n\n    {\n        trigger: '.o_barcode_line.o_selected:contains(\"lot2\") .qty-done:contains(\"2\")',\n        run: 'scan LOC-01-01-00',\n    },\n    ...stepUtils.validateBarcodeOperation(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_receipt_duplicate_serial_number', { steps: () => [\n    // Create a receipt. Try to scan twice the same serial in different locations.\n    { trigger: '.o_stock_barcode_main_menu:contains(\"Scan or tap\")', run: \"click\" },\n    { trigger: '.o_stock_barcode_main_menu', run: 'scan WHIN' },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan productserial1',\n    },\n\n    {\n        trigger: '.o_barcode_line',\n        run: 'scan sn1',\n    },\n\n    {\n        trigger: '.o_barcode_line .o_line_lot_name:contains(\"sn1\")',\n        run: 'scan LOC-01-01-00',\n    },\n\n    {\n        trigger: '.o_barcode_line .o_line_destination_location:contains(\"../Section 1\")',\n        run: 'scan productserial1',\n    },\n\n    {\n        trigger: '.o_barcode_line:nth-child(2)',\n        run: 'scan sn1',\n    },\n\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: function () {\n            helper.assertErrorMessage('The scanned serial number sn1 is already used.');\n        },\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan sn2',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-02-00',\n    },\n    {\n        trigger: '.o_barcode_line:nth-child(2) .o_line_destination_location:contains(\"../Section 2\")',\n        run: 'scan OBTVALI'\n    },\n    {\n        trigger: '.o_notification_bar.bg-success',\n        run: function () {\n            helper.assertErrorMessage('The transfer has been validated');\n        },\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_delivery_duplicate_serial_number', { steps: () => [\n    // Create a delivery. Try to scan twice the same serial in different locations.\n    { trigger: '.o_stock_barcode_main_menu', run: 'scan WHOUT' },\n    { trigger: '.o_scan_message.o_scan_src', run: 'scan LOC-01-01-00' },\n\n    {\n        trigger: '.o_scan_message.o_scan_product',\n        run: 'scan productserial1',\n    },\n\n    {\n        trigger: '.o_barcode_line:contains(\"productserial1\")',\n        run: 'scan sn1',\n    },\n    // Changes the location and scans again the same serial number.\n    {\n        trigger: '.o_barcode_line .o_line_lot_name:contains(\"sn1\")',\n        run: 'scan LOC-01-02-00',\n    },\n\n    {\n        trigger: '.o_scan_message.o_scan_validate',\n        run: 'scan productserial1',\n    },\n\n    {\n        trigger: '.o_barcode_line:contains(\"productserial1\")',\n        run: 'scan sn1',\n    },\n\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: function () {\n            helper.assertErrorMessage('The scanned serial number sn1 is already used.');\n        },\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan sn2',\n    },\n    {\n        trigger: '.o_barcode_line.o_selected:nth-child(2)',\n        run: \"click\",\n    },\n    ...stepUtils.validateBarcodeOperation(),\n\n    {\n        trigger: '.o_stock_barcode_main_menu',\n        run: function () {\n            helper.assertErrorMessage('The transfer has been validated');\n        },\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_bypass_source_scan', { steps: () => [\n    /* Scan directly a serial number, a package or a lot in delivery order.\n    * It should implicitely trigger the same action than a source location\n    * scan with the state location.\n    */\n    {\n        trigger: '.o_barcode_client_action',\n        run: function () {\n            helper.assertLinesCount(3);\n            helper.assertScanMessage('scan_product');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(true);\n        }\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan serial1',\n    },\n\n    {\n        trigger: '.o_barcode_line[data-barcode=\"productserial1\"] .o_edit',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_field_widget[name=\"qty_done\"]',\n    },\n    {\n        trigger: '.o_field_many2one[name=lot_id] input',\n        tooltipPosition: \"bottom\",\n        run: \"clear\",\n    },\n\n    {\n        trigger: '.o_field_widget[name=qty_done] input',\n        run: \"edit 0\",\n    },\n\n    {\n        trigger: '.o_save',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_barcode_line',\n        run: 'scan lot1',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot1',\n    },\n\n    // Tries to scan a pack in a location the delivery shouldn't have access.\n    { trigger: '.o_scan_message.o_scan_product', run: 'scan SUSPACK' },\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: function () {\n            helper.assertErrorMessage(\"You are expected to scan one or more products or a package available at the picking location\");\n        },\n    },\n    {\n        trigger: 'button.o_notification_close',\n        run: \"click\",\n    },\n    // Scans a package in the right location now.\n    {\n        trigger: '.o_scan_message.o_scan_product',\n        run: 'scan THEPACK',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan productserial1',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan serial1',\n    },\n    ...stepUtils.validateBarcodeOperation(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_picking_type_mandatory_scan_settings_pick_int_1', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function () {\n            helper.assertLinesCount(2);\n            helper.assertScanMessage('scan_product');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(false); // Can't validate until product with barcode was scanned.\n            const [ lineProductNoBarcode, lineProduct1 ] = helper.getLines();\n            helper.assert(\n                lineProduct1.querySelector('.btn.o_edit').disabled, true,\n                \"Edit button should be disabled until the product was scanned\");\n            // No button to automatically add the quantity if the product scan is mandatory.\n            helper.assertButtonIsVisible(lineProduct1, \"add_quantity\", false);\n            helper.assert(\n                lineProductNoBarcode.querySelector('.btn.o_edit').disabled, false,\n                \"Edit button is always enabled if the product has no barcode (it can't be scanned')\");\n            // Add quantity button is always displayed if the product has no barcode.\n            helper.assertButtonIsVisible(lineProductNoBarcode, \"add_remaining_quantity\");\n            // Checks that locations are still shown despite scanning set to 'no'.\n            helper.assertLineLocations(lineProductNoBarcode, \"WH/Stock/Section 1\", \"WH/Stock\");\n            helper.assertLineLocations(lineProduct1, \"WH/Stock/Section 1\", \"WH/Stock\");\n\n        }\n    },\n    // Scans the source location, it should display an error.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-01-00',\n    },\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: function() {\n            helper.assertErrorMessage(\"You must scan a product\");\n        },\n    },\n\n    // Scans product1, its buttons should be displayed/enabled.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1',\n    },\n    {\n        trigger: '.o_barcode_line.o_selected',\n        run: function() {\n            const lineProduct1 = helper.getLine({ barcode: \"product1\" });\n            helper.assert(\n                lineProduct1.querySelector('.btn.o_edit').disabled, false,\n                \"product1 was scanned, the edit button should now be enabled\");\n            // product1 was scanned, the add quantity button should be visible.\n            helper.assertButtonIsVisible(lineProduct1, \"add_quantity\");\n            helper.assertValidateIsHighlighted(false);\n            // Since the only product with a barcode was scanned, the validate button is enabled.\n            helper.assertValidateEnabled(true);\n        }\n    },\n    // Uses buttons to complete the lines.\n    {\n        trigger: '.o_barcode_line.o_selected .btn.o_add_remaining_quantity',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line .btn.o_add_remaining_quantity',\n        run: \"click\",\n    },\n    // Lines are completed, the message should ask to validate the operation and that's what we do.\n    {\n        trigger: 'div[name=\"barcode_messages\"] .fa-check-square',\n    },\n    {\n        trigger: '.btn.o_validate_page.btn-primary',\n        run: \"click\",\n    },\n    { trigger: '.o_notification_bar.bg-success'},\n]});\n\nregistry.category(\"web_tour.tours\").add('test_picking_type_mandatory_scan_settings_pick_int_2', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function () {\n            helper.assertLinesCount(2);\n            helper.assertScanMessage('scan_src');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(false); // Can't validate until product with barcode was scanned.\n            const [ lineProductNoBarcode, lineProduct1 ] = helper.getLines();\n            helper.assert(\n                lineProduct1.querySelector('.btn.o_edit').disabled, true,\n                \"Edit button should be disabled until the product was scanned\");\n            // No button to automatically add the quantity if the product scan is mandatory.\n            helper.assertButtonIsVisible(lineProduct1, \"add_quantity\", false);\n            helper.assert(\n                lineProductNoBarcode.querySelector('.btn.o_edit').disabled, true,\n                \"All lines' buttons are disabled until a source location was scanned\");\n            helper.assert(\n                lineProductNoBarcode.querySelector('.btn.o_add_remaining_quantity').disabled, true,\n                \"All lines' buttons are disabled until a source location was scanned\");\n        }\n    },\n    // Scans a product, it should display an error.\n    { trigger: '.o_barcode_client_action', run: 'scan product1' },\n    {\n        trigger: '.o_notification:has(.o_notification_bar.bg-danger)',\n        run: function() {\n            helper.assertErrorMessage(\n                \"You are supposed to scan WH/Stock or another source location\");\n        },\n    },\n    {\n        trigger: '.btn-close.o_notification_close',\n        run: \"click\",\n    },\n\n    // Scans the source location, the buttons for the product without barcode should be enabled.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-00-00',\n    },\n    {\n        trigger: '.o_scan_message.o_scan_product',\n        run: function () {\n            const [ lineProductNoBarcode, lineProduct1 ] = helper.getLines();\n            helper.assert(\n                lineProduct1.querySelector('.btn.o_edit').disabled, true,\n                \"Edit button should be disabled until the product was scanned\");\n            helper.assert(\n                lineProduct1.querySelector('.btn.o_add_remaining_quantity').disabled, true,\n                \"Button to automatically add the quantity is disabled if the product scan is mandatory\");\n            helper.assert(\n                lineProductNoBarcode.querySelector('.btn.o_edit').disabled, false,\n                \"Since the source of this line was scanned and it has no barcode, its buttons should be enabled\");\n            helper.assert(\n                lineProductNoBarcode.querySelector('.btn.o_add_remaining_quantity').disabled, false,\n                \"Since the source of this line was scanned and it has no barcode, its buttons should be enabled\");\n        }\n    },\n    // Scans another location, it replaces the previous scanned source as no product was scanned yet.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-01-00',\n    },\n\n    // Scans product1.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1',\n    },\n    {\n        trigger: '.o_barcode_line.o_selected',\n        run: function() {\n            const lineProduct1 = helper.getLine({ barcode: \"product1\" });\n            helper.assert(\n                lineProduct1.querySelector('.btn.o_edit').disabled, false,\n                \"product1 was scanned, the edit button should now be enabled\");\n            // product1 was scanned, the add quantity button should be visible.\n            helper.assertButtonIsVisible(lineProduct1, \"add_quantity\");\n            helper.assertValidateIsHighlighted(false);\n            // Since the only product with a barcode was scanned, the validate button is enabled.\n            helper.assertValidateEnabled(true);\n        }\n    },\n    // Scans another product: it should raise an error as the destination should be scanned between each product.\n    { trigger: '.o_barcode_client_action', run: 'scan product2' },\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: function() {\n            helper.assertErrorMessage(\n                \"Please scan destination location for product1 before scanning other product\");\n        },\n    },\n    {\n        trigger: '.btn-close.o_notification_close',\n        run: \"click\",\n    },\n\n    // Uses button to complete the line, then scan the destination.\n    {\n        trigger: '.o_barcode_line.o_selected .btn.o_add_remaining_quantity',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line.o_selected.o_line_completed',\n        run: 'scan LOC-01-00-00',\n    },\n    // Scans again product1: should raise an error as it expects the source (should be scanned after each product).\n    { trigger: '.o_barcode_client_action', run: 'scan product1' },\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: function() {\n            helper.assertErrorMessage(\n                \"You are supposed to scan WH/Stock or another source location\");\n        },\n    },\n    {\n        trigger: '.btn-close.o_notification_close',\n        run: \"click\",\n    },\n\n    // Scans the source and updates the remaining product qty with its button (because no barcode).\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-01-00',\n    },\n    {\n        trigger: '.o_scan_message.o_scan_product',\n    },\n    {\n        trigger: '.o_barcode_line .btn.o_add_remaining_quantity',\n        run: \"click\",\n    },\n    // Tries to validate without scanning the destination: display a warning.\n    {\n        trigger: '.o_barcode_line.o_selected.o_line_completed',\n        run: 'scan OBTVALI',\n    },\n    {\n        trigger: '.o_notification:has(.o_notification_bar.bg-danger) .o_notification_close.btn-close',\n        run: \"click\",\n    },\n\n    // Scans the destination location than validate the operation.\n    {\n        trigger: 'div[name=\"barcode_messages\"] .fa-sign-in', // \"Scan dest. loc.\" message's icon.\n        run: 'scan LOC-01-00-00',\n    },\n    {\n        trigger: 'div[name=\"barcode_messages\"] .fa-check-square',\n    },\n    {\n        trigger: '.btn.o_validate_page.btn-primary',\n        run: \"click\",\n    },\n    { trigger: '.o_notification_bar.bg-success'},\n]});\n\nregistry.category(\"web_tour.tours\").add('test_receipt_scan_package_and_location_after_group_of_product', { steps: () => [\n    {\n        trigger: \".o_barcode_client_action\",\n        run: function () {\n            helper.assertLinesCount(3);\n            helper.assertLineProduct(0, \"Barcodeless Product\");\n            helper.assertLineProduct(1, \"product1\");\n            helper.assertLineProduct(2, \"productlot1\");\n        }\n    },\n    // Scans 2x product1...\n    { trigger: \".o_barcode_line\", run: \"scan product1\" },\n    { trigger: \".o_barcode_line.o_selected\", run: \"scan product1\" },\n    // ... process all the products with no barcode and 3 productlot1 (2 differents lots).\n    {\n        trigger: \".o_barcode_line.o_selected .qty-done:contains(2)\",\n    },\n    {\n        trigger: \".o_barcode_line:not([data-barcode]) .o_line_button.o_add_remaining_quantity\",\n        run: \"click\",\n    },\n    // ... and scans 3 productlot1 from 2 differents lots.\n    { trigger: \".o_barcode_line.o_selected.o_line_completed\", run: \"scan productlot1\" },\n    { trigger: \".o_barcode_line[data-barcode='productlot1'].o_selected\", run: \"scan lot-01\" },\n    { trigger: \".o_barcode_line\", run: \"scan lot-01\" },\n    { trigger: \".o_barcode_line\", run: \"scan lot-02\" },\n\n    // Scans Section 1, the destination should be applied to all previous scanned lines and\n    // the edited line. For the uncompleted lines, they should be split in two:\n    // - one line with the processed quantity going to the scanned location;\n    // - one line with the remaining quantity going to the picking's location.\n    { trigger: \".o_barcode_line.o_selected .o_toggle_sublines\", run: \"scan LOC-01-01-00\" },\n    {\n        trigger: \".o_barcode_line:nth-child(5)\",\n        run: function () {\n            helper.assertLinesCount(5);\n\n            helper.assertLineProduct(0, \"Barcodeless Product\");\n            helper.assertLineQty(0, \"4/4\");\n            helper.assertLineDestinationLocation(0, \".../Section 1\");\n\n            helper.assertLineProduct(1, \"product1\");\n            helper.assertLineQty(1, \"2/2\");\n            helper.assertLineDestinationLocation(1, \".../Section 1\");\n\n            helper.assertLineProduct(2, \"product1\");\n            helper.assertLineQty(2, \"0/2\");\n            helper.assertLineDestinationLocation(2, \"WH/Stock\");\n\n            helper.assertLineProduct(3, \"productlot1\");\n            helper.assertLineQty(3, \"3/3\");\n            helper.assertLineDestinationLocation(3, \".../Section 1\");\n\n            helper.assertLineProduct(4, \"productlot1\");\n            helper.assertLineQty(4, \"0/3\");\n            helper.assertLineDestinationLocation(4, \"WH/Stock\");\n        }\n    },\n\n    // Scan only one lot then another destination. Only this lot should be moved to this location.\n    { trigger: \".o_barcode_client_action\", run: \"scan productlot1\" },\n    { trigger: \".o_scan_message.o_scan_lot\", run: \"scan lot-02\" },\n    {\n        trigger: \".o_barcode_line.o_selected .o_line_lot_name:contains('lot-02')\",\n        run: \"scan LOC-01-02-00\",\n    },\n\n    {\n        trigger: \".o_scan_message.o_scan_product\",\n        run: function () {\n            helper.assertLinesCount(6);\n\n            helper.assertLineProduct(0, \"Barcodeless Product\");\n            helper.assertLineQty(0, \"4/4\");\n            helper.assertLineDestinationLocation(0, \".../Section 1\");\n\n            helper.assertLineProduct(1, \"product1\");\n            helper.assertLineQty(1, \"2/2\");\n            helper.assertLineDestinationLocation(1, \".../Section 1\");\n\n            helper.assertLineProduct(2, \"product1\");\n            helper.assertLineQty(2, \"0/2\");\n            helper.assertLineDestinationLocation(2, \"WH/Stock\");\n\n            helper.assertLineProduct(3, \"productlot1\");\n            helper.assertLineQty(3, \"3/3\");\n            helper.assertLineDestinationLocation(3, \".../Section 1\");\n\n            helper.assertLineProduct(4, \"productlot1\");\n            helper.assertLineQty(4, \"1/1\");\n            helper.assertLineDestinationLocation(4, \".../Section 2\");\n\n            helper.assertLineProduct(5, \"productlot1\");\n            helper.assertLineQty(5, \"0/2\");\n            helper.assertLineDestinationLocation(5, \"WH/Stock\");\n        }\n    },\n\n    // Process the remaining quantity then scans an existing package: only those lines should be packed.\n    { trigger: \".o_barcode_client_action\", run: \"scan product1\" },\n    { trigger: \".o_barcode_line.o_selected\", run: \"scan product1\" },\n    { trigger: \".o_barcode_line.o_selected.o_line_completed\", run: \"scan productlot1\" },\n    { trigger: \".o_barcode_line.o_selected:not(.o_line_completed)\", run: \"scan lot-03\" },\n    { trigger: \".o_barcode_line.o_selected:not(.o_line_completed)\", run: \"scan lot-03\" },\n    { trigger: \".o_barcode_line.o_selected.o_line_completed\", run: \"scan pack-128\" },\n    // Scans another destination: only the packaged lines should go to this location.\n    { trigger: \".o_barcode_line [name='package']\", run: \"scan shelf3\" },\n    {\n        trigger: \".o_scan_message.o_scan_validate\",\n        run: function () {\n            helper.assertLinesCount(6);\n\n            helper.assertLineProduct(0, \"Barcodeless Product\");\n            helper.assertLineQty(0, \"4/4\");\n            helper.assertLineDestinationLocation(0, \".../Section 1\");\n\n            helper.assertLineProduct(1, \"product1\");\n            helper.assertLineQty(1, \"2/2\");\n            helper.assertLineDestinationLocation(1, \".../Section 1\");\n\n            let line = helper.getLine({ index: 2 });\n            helper.assertLineProduct(line, \"product1\");\n            helper.assertLineQty(line, \"2/2\");\n            helper.assertLineDestinationLocation(line, \".../Section 3\");\n            helper.assert(line.querySelector('[name=\"package\"]').innerText, \"pack-128\");\n\n            helper.assertLineProduct(3, \"productlot1\");\n            helper.assertLineQty(3, \"3/3\");\n            helper.assertLineTrackingNumber(3, false);\n            helper.assertLineDestinationLocation(3, \".../Section 1\");\n\n            helper.assertLineProduct(4, \"productlot1\");\n            helper.assertLineQty(4, \"1/1\");\n            helper.assertLineTrackingNumber(4, \"lot-02\");\n            helper.assertLineDestinationLocation(4, \".../Section 2\");\n\n            line = helper.getLine({ index: 5 });\n            helper.assertLineProduct(5, \"productlot1\");\n            helper.assertLineQty(5, \"2/2\");\n            helper.assertLineTrackingNumber(5, \"lot-03\");\n            helper.assertLineDestinationLocation(5, \".../Section 3\");\n            helper.assert(line.querySelector('[name=\"package\"]').innerText, \"pack-128\");\n        }\n    },\n    ...stepUtils.validateBarcodeOperation(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_receipt_assign_sibling_reservation_no_empty_line', { steps: () => [\n    {\n        trigger: \".o_barcode_client_action\",\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertLineProduct(0, \"productlot1\");\n        }\n    },\n    { trigger: \".o_barcode_line\", run: \"scan productlot1\" },\n    { trigger: \".o_barcode_line[data-barcode='productlot1']\", run: \"scan lot-01\" },\n    { trigger: \".o_barcode_line[data-barcode='productlot1']\", run: \"scan lot-02\" },\n\n    // Select first line to ensure that the dest location change is done on the line with reserved quantity\n    { trigger: \"button.o_line_button.o_toggle_sublines\", run: \"click\" },\n    { trigger: \".o_sublines > .o_barcode_line[data-barcode='productlot1']:first-child\", run: \"click\" },\n\n    // Change dest location, this should re-assign the reserved quantity\n    { trigger: \".o_barcode_line.o_selected\", run: \"scan LOC-01-01-00\" },\n    {\n        trigger: \".o_barcode_location_group>.o_barcode_line .o_line_destination_location:contains('Section 1')\",\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertLineProduct(0, \"productlot1\");\n            helper.assertLineQty(0, \"2/2\");\n            helper.assertLineDestinationLocation(0, \".../Section 1\");\n        }\n    },\n    ...stepUtils.validateBarcodeOperation(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_picking_type_mandatory_scan_product_packaging', { steps: () => [\n    {\n        trigger: '.o_barcode_line',\n        run: () => {\n            helper.assertScanMessage(\"scan_product\");\n            helper.assert(Boolean(document.querySelector('button.o_edit[disabled]')), true,\n                \"Edit button should be visible but disabled\");\n        }\n    },\n    { trigger: '.o_barcode_line', run: \"scan product1x10\" },\n    { trigger: '.o_barcode_line.o_selected.o_line_completed' },\n\n]});\n\nregistry.category(\"web_tour.tours\").add('test_picking_type_mandatory_scan_complete_flux_receipt', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function() {\n            helper.assertLinesCount(5);\n            helper.assertScanMessage('scan_product');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(false); // Can't validate until product with barcode was scanned.\n        }\n    },\n    // Scans product1 two times to complete the lines.\n    { trigger: '.o_barcode_client_action', run: 'scan product1' },\n    { trigger: '.o_barcode_client_action', run: 'scan product1' },\n    {\n        trigger: '.o_barcode_line.o_selected.o_line_completed',\n        run: function() {\n            helper.assertScanMessage('scan_product_or_dest');\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(true); // Can't validate until product with barcode was scanned.\n        }\n    },\n    // Process product2 and product with no barcode with the button.\n    {\n        trigger: '.o_barcode_line[data-barcode=\"product2\"] .btn.o_add_remaining_quantity',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"product2\"].o_line_completed',\n    },\n    {\n        trigger: '.o_barcode_line:not([data-barcode]) .btn.o_add_remaining_quantity',\n        run: \"click\",\n    },\n    // Before to scan remaining product, scans a first time the destination.\n    {\n        trigger: '.o_barcode_line:not([data-barcode]).o_line_completed',\n        run: 'scan WHINPUT'\n    },\n    // The message should ask to scan a product, so scans product tracked by lots.\n    {\n        trigger: '.o_scan_message.o_scan_product',\n        run: 'scan productlot1'\n    },\n    // Scans lot-001 x2, lot-002 x2 and lot-003 x2.\n    {\n        trigger: '.o_barcode_line[data-barcode=\"productlot1\"].o_selected',\n        run: 'scan lot-001'\n    },\n    { trigger: '.o_barcode_line', run: 'scan lot-001' },\n    { trigger: '.o_barcode_line', run: 'scan lot-002' },\n    { trigger: '.o_barcode_line', run: 'scan lot-002' },\n    { trigger: '.o_barcode_line', run: 'scan lot-003' },\n    { trigger: '.o_barcode_line', run: 'scan lot-003' },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"productlot1\"].o_selected.o_line_completed',\n        run: function() {\n            helper.assertScanMessage('scan_product_or_dest');\n        }\n    },\n    // Scans the product tracked by serial numbers and scans three serials.\n    {\n        trigger: '.o_scan_message.o_scan_product_or_dest',\n        run: 'scan productserial1'\n    },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"productserial1\"].o_selected',\n        run: 'scan sn-001'\n    },\n    { trigger: '.o_barcode_line', run: 'scan sn-002' },\n    { trigger: '.o_barcode_line', run: 'scan sn-003' },\n    // It should ask to scan the destination, so scans it.\n    {\n        trigger: 'div[name=\"barcode_messages\"] .o_scan_product_or_dest',\n        run: 'scan WHINPUT',\n    },\n    // Now the destination was scanned, it should say the operation can be validate.\n    {\n        trigger: 'div[name=\"barcode_messages\"] .o_scan_validate',\n    },\n    {\n        trigger: '.o_validate_page.btn-primary',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_notification_bar.bg-success',\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_picking_type_mandatory_scan_complete_flux_internal', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function() {\n            helper.assertLinesCount(5);\n            helper.assertScanMessage('scan_product');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(false); // Can't validate until product with barcode was scanned.\n        }\n    },\n    // Scans one product1 to move in Section 1, but scans another product between.\n    { trigger: '.o_barcode_client_action', run: 'scan product1' },\n    { trigger: '.o_barcode_line.o_selected', run: 'scan product2' }, // Should raise an error.\n    {\n        trigger: '.o_notification:has(.o_notification_bar.bg-danger)',\n        run: function() {\n            helper.assertErrorMessage(\n                \"Please scan destination location for product1 before scanning other product\");\n        },\n    },\n    {\n        trigger: '.btn-close.o_notification_close',\n        run: \"click\",\n    },\n\n    { // Scans the destination (Section 1).\n        trigger: '.o_barcode_line.o_selected',\n        run: 'scan LOC-01-01-00'\n    },\n    // product1 line is split, 1 qty moves to Section 1, the rest is left as default\n    {\n        trigger: '.o_barcode_line.o_line_completed .o_line_destination_location .fw-bold:contains(\"Section 1\")',\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: function() {\n            helper.assertLinesCount(6);\n            helper.assertScanMessage('scan_product');\n        },\n    },\n\n    // Scans product1 again and move it to Section 3.\n    {\n        trigger: '.o_scan_message.o_scan_product',\n        run: 'scan product1'\n    },\n    {\n        trigger: '.o_barcode_line.o_selected',\n        run: 'scan shelf3'\n    },\n\n    // Scans product2 and moves it into Section 2.\n    {\n        trigger: '.o_scan_message.o_scan_product',\n        run: 'scan product2'\n    },\n    {\n        trigger: '.o_barcode_line.o_selected',\n        run: 'scan LOC-01-02-00'\n    },\n\n    // Process quantities for the product with no barcode and move it to Section 1.\n    {\n        trigger: '.o_scan_message.o_scan_product',\n    },\n    {\n        trigger: '.o_barcode_line:not([data-barcode]) .btn.o_add_remaining_quantity',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line.o_selected.o_line_completed',\n        run: 'scan LOC-01-01-00'\n    },\n\n    // The message should ask to scan a product, so scans product tracked by lots.\n    {\n        trigger: '.o_scan_message.o_scan_product',\n        run: 'scan productlot1'\n    },\n    // Scans lot-001 x2, lot-002 x2 and moves them in Section 3.\n    {\n        trigger: '.o_barcode_line[data-barcode=\"productlot1\"].o_selected',\n        run: 'scan lot-001'\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .btn.o_toggle_sublines .fa-angle-down',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_sublines .o_barcode_line.o_selected',\n        run: 'scan lot-001'\n    },\n    {\n        trigger: '.o_sublines .o_barcode_line.o_selected.o_line_completed',\n        run: 'scan lot-002'\n    },\n    {\n        trigger: '.o_sublines .o_barcode_line.o_selected:not(.o_line_completed)',\n        run: 'scan lot-002'\n    },\n    {\n        trigger: '.o_sublines .o_barcode_line.o_selected.o_line_completed',\n        run: 'scan shelf3'\n    },\n\n    // Scans lot-003 x2 and moves them in Section 4.\n    {\n        trigger: '.o_scan_message.o_scan_product',\n        run: 'scan lot-003'\n    },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"productlot1\"].o_selected',\n        run: 'scan lot-003'\n    },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"productlot1\"].o_selected',\n        run: 'scan shelf4'\n    },\n\n    // Scans the product tracked by serial numbers and scans three serials.\n    {\n        trigger: '.o_scan_message.o_scan_product',\n        run: 'scan productserial1'\n    },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"productserial1\"].o_selected',\n        run: 'scan sn-001'\n    },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"productserial1\"].o_selected',\n        run: 'scan sn-002'\n    },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"productserial1\"].o_selected',\n        run: 'scan sn-003'\n    },\n    { // Moves it to Section 4.\n        trigger: '.o_barcode_line[data-barcode=\"productserial1\"].o_selected.o_line_completed',\n        run: 'scan shelf4'\n    },\n    // It should say the operation can be validate.\n    {\n        trigger: 'div[name=\"barcode_messages\"] .fa-check-square', // \"Press validate\" message icon.\n    },\n    {\n        trigger: '.o_validate_page.btn-primary',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_notification_bar.bg-success',\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_picking_type_mandatory_scan_complete_flux_pick', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function() {\n            helper.assertLinesCount(7);\n            helper.assertScanMessage('scan_src');\n            helper.assertValidateVisible(true);\n            helper.assertValidateEnabled(false);\n            const lineButtons = document.querySelectorAll('.btn.o_edit,.btn.o_add_remaining_quantity');\n            helper.assert(lineButtons.length, 10, \"Should have 1 edit & 1 add qty. buttons on 5 lines\");\n            for (const button of lineButtons) {\n                helper.assert(button.disabled, true,\n                    \"All lines' buttons are disabled until a source location was scanned\");\n            }\n        }\n    },\n    // Scans product1 -> raise an error because it expects the source location.\n    {\n        trigger: '.o_scan_message.o_scan_src',\n        run: 'scan product1'\n    },\n    {\n        trigger: '.o_notification',\n        run: function() {\n            helper.assertErrorMessage(\n                \"You are supposed to scan WH/Stock or another source location\");\n        },\n    },\n    {\n        trigger: '.btn-close.o_notification_close',\n        run: \"click\",\n    },\n\n    // Scan another location (Section 2 for the instance).\n    {\n        trigger: '.o_scan_message.o_scan_src',\n        run: 'scan LOC-01-02-00'\n    },\n    {\n        trigger: '.o_barcode_location_line[data-location=\"WH/Stock/Section 2\"].text-bg-800',\n        run: function() {\n            helper.assertLinesCount(7);\n            helper.assertScanMessage('scan_product');\n            const lineProduct2 = document.querySelector('.o_barcode_line');\n            helper.assert(\n                lineProduct2.querySelector('.btn.o_edit').disabled, false,\n                \"Since the source location was scanned, its buttons should be enabled\");\n            helper.assert(\n                lineProduct2.querySelector('.btn.o_add_remaining_quantity').disabled, false,\n                \"Since the source location was scanned, its buttons should be enabled\");\n        }\n    },\n    // Scans product2 then scans another source location (Section 3) => Should raise a warning.\n    { trigger: '.o_barcode_client_action', run: 'scan product2' },\n    { trigger: '.o_barcode_line.o_line_completed', run: 'scan shelf3' },\n    {\n        trigger: '.o_notification',\n        run: function() {\n            helper.assertErrorMessage(\"You must scan a package or put in pack\");\n        },\n    },\n    {\n        trigger: '.btn-close.o_notification_close',\n        run: \"click\",\n    },\n\n    // Scans a pack then scans Section 1.\n    { trigger: '.o_barcode_line.o_line_completed', run: 'scan cluster-pack-01' },\n    { trigger: '.o_barcode_line.o_selected .result-package', run: 'scan LOC-01-01-00' },\n    {\n        trigger: '.o_barcode_location_line[data-location=\"WH/Stock/Section 1\"].text-bg-800',\n        run: function() {\n            helper.assertLinesCount(7);\n            helper.assertScanMessage('scan_product');\n        }\n    },\n    // Scans product1 from Section 1, pack it.\n    { trigger: '.o_barcode_client_action', run: 'scan product1' },\n    { trigger: '.o_barcode_line[data-barcode=\"product1\"].o_selected', run: 'scan cluster-pack-01' },\n    // Do the same from Section 3\n    { trigger: '.o_barcode_line.o_line_completed .result-package', run: 'scan shelf3' },\n    { trigger: '.o_barcode_location_line[data-location=\"WH/Stock/Section 3\"].text-bg-800', run: 'scan product1' },\n    {\n        trigger: '.o_barcode_location_line[data-location=\"WH/Stock/Section 3\"] + .o_barcode_line.o_selected.o_line_completed',\n        run: 'scan productlot1'\n    },\n    {\n        trigger: '.o_notification',\n        run: function() {\n            helper.assertErrorMessage(\"You must scan a package or put in pack\");\n        },\n    },\n    {\n        trigger: '.btn-close.o_notification_close',\n        run: \"click\",\n    },\n    { trigger: '.o_barcode_client_action', run: 'scan cluster-pack-01' },\n    // scans lot-001 and lot-002\n    {\n        trigger: '.o_barcode_line.o_selected.o_line_completed .result-package',\n        run: 'scan productlot1'\n    },\n    // Checks we can't edit a line for a tracked product until the tracking number was scan.\n    {\n        trigger: '.o_barcode_line.o_selected .btn.o_toggle_sublines .fa-angle-down',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .o_sublines',\n        run: function() {\n            const [ lot001Line, lot002Line ] = helper.getSublines();\n            helper.assert(lot001Line.querySelector('.btn.o_add_quantity').disabled, true,\n                \"Lot number not scanned yet, so line's buttons are disabled.\");\n            helper.assert(lot001Line.querySelector('.btn.o_add_remaining_quantity').disabled, true,\n                \"Lot number not scanned yet, so line's buttons are disabled.\");\n            helper.assert(lot002Line.querySelector('.btn.o_add_remaining_quantity').disabled, true,\n                \"Lot number not scanned yet, so line's buttons are disabled.\");\n        }\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot-001',\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .qty-done:contains(1)',\n        run: function() {\n            const [ lot001Line, lot002Line ] = helper.getSublines();\n            helper.assert(lot001Line.querySelector('.btn.o_remove_unit').disabled, false,\n                \"lot-001 was scanned, its line's buttons should be enable\");\n            helper.assert(lot001Line.querySelector('.btn.o_add_remaining_quantity').disabled, false,\n                \"lot-001 was scanned, its line's buttons should be enable\");\n            helper.assert(lot002Line.querySelector('.btn.o_add_remaining_quantity').disabled, true,\n                \"Lot number not scanned yet, so line's buttons are disabled.\");\n        }\n    },\n    {\n        trigger: '.o_barcode_line.o_selected:not(.o_line_completed)',\n        run: 'scan lot-001',\n    },\n    {\n        trigger: '.o_barcode_line.o_selected.o_line_completed',\n        run: function() {\n            const lot001Line = helper.getSubline({ completed: true });\n            const lot002Line = helper.getSubline({ completed: false });\n            helper.assertButtonIsVisible(lot001Line, \"remove_unit\");\n            helper.assertButtonIsVisible(lot001Line, \"add_quantity\", false);\n            helper.assertButtonIsVisible(lot001Line, \"add_remaining_quantity\");\n            helper.assert(lot002Line.querySelector('.btn.o_add_remaining_quantity').disabled, true);\n        }\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot-002',\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .qty-done:contains(1)',\n        run: function() {\n            const lot002Line = helper.getSubline({ selected: true, completed: false});\n            helper.assert(lot002Line.querySelector('.btn.o_remove_unit').disabled, false,\n                \"lot-002 was scanned, the button to remove quantity should be enabled.\");\n            helper.assert(lot002Line.querySelector('.btn.o_add_remaining_quantity').disabled, false,\n                \"lot-002 was scanned, the button to add quantity should be enabled.\");\n        }\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan lot-002',\n    },\n\n    {\n        trigger: '.o_barcode_line.o_selected.o_line_completed',\n        run: function() {\n            const lot002Line = helper.getSubline({ selected: true, completed: true});\n            helper.assertButtonIsVisible(lot002Line, \"remove_unit\");\n            helper.assertButtonIsVisible(lot002Line, \"add_quantity\", false);\n            helper.assertButtonIsVisible(lot002Line, \"add_remaining_quantity\", false);\n        }\n    },\n    { trigger: '.o_barcode_client_action', run: 'scan cluster-pack-02' },\n\n    // Scans Section 1 (source) and processes the remaining products.\n    {\n        trigger: '.o_barcode_line.o_selected.o_line_completed .result-package',\n        run: 'scan LOC-01-01-00',\n    },\n    {\n        trigger: '.o_barcode_location_line[data-location=\"WH/Stock/Section 1\"].text-bg-800',\n    },\n    {\n        trigger: '.o_barcode_line:not([data-barcode]) .btn.o_add_remaining_quantity',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line.o_selected.o_line_completed:not([data-barcode])',\n        run: 'scan cluster-pack-01'\n    },\n\n    // Scans Section 4 (source).\n    {\n        trigger: '.o_barcode_line.o_selected.o_line_completed .result-package',\n        run: 'scan shelf4'\n    },\n    // Scans the remaining lot and the serial numbers.\n    {\n        trigger: '.o_scan_message.o_scan_product',\n    },\n    {\n        trigger: '.o_barcode_location_line[data-location=\"WH/Stock/Section 4\"].text-bg-800',\n        run: 'scan lot-003',\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan sn-001',\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan sn-003',\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan sn-002',\n    },\n    {\n        trigger: '.o_scan_message.o_scan_package',\n    },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"productserial1\"].o_selected.o_line_completed',\n        run: 'scan cluster-pack-02'\n    },\n    {\n        trigger: '.o_scan_message.o_scan_product',\n        run: 'scan lot-003',\n    },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"productlot1\"].o_selected.o_line_completed',\n        run: 'scan cluster-pack-02'\n    },\n    // It should say the operation can be validate.\n    {\n        trigger: 'div[name=\"barcode_messages\"] .fa-check-square', // \"Press validate\" message icon.\n    },\n    {\n        trigger: '.o_validate_page.btn-primary',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_notification_bar.bg-success',\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_picking_type_mandatory_scan_complete_flux_pack', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function() {\n            helper.assertLinesCount(5);\n            helper.assertScanMessage('scan_product');\n            helper.assertValidateVisible(true);\n            helper.assertValidateEnabled(true);\n            helper.assertValidateIsHighlighted(false);\n        }\n    },\n    // Scans first cluster pack.\n    { trigger: '.o_barcode_client_action', run: 'scan cluster-pack-01'},\n    // Scans second cluster pack.\n    { trigger: '.o_barcode_client_action', run: 'scan cluster-pack-02'},\n    // Tries to validate: it should ask to put in pack.\n    {\n        trigger: '.o_validate_page.btn-primary',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_notification',\n        run: function() {\n            helper.assertErrorMessage(\"All products need to be packed\");\n        },\n    },\n    {\n        trigger: '.btn-close.o_notification_close',\n        run: \"click\",\n    },\n    // Puts in pack.\n    { trigger: '.o_barcode_client_action', run: 'scan OBTPACK'},\n    // Validates the operation.\n    {\n        trigger: '.o_scan_message.o_scan_validate',\n    },\n    {\n        trigger: '.o_validate_page.btn-primary',\n        run: \"click\",\n    },\n    { trigger: '.o_notification_bar.bg-success'},\n]});\n\nregistry.category(\"web_tour.tours\").add('test_picking_type_mandatory_scan_complete_flux_delivery', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function() {\n            helper.assertLinesCount(1);\n            helper.assertScanMessage('scan_product_or_package');\n        }\n    },\n    // Scans the pack, then validate.\n    {\n        trigger: '.o_barcode_line:contains(\"PACK0000001\")',\n        run: 'scan PACK0000001'\n    },\n    // It should say the operation can be validate.\n    {\n        trigger: '.o_scan_message.o_scan_validate', // \"Press validate\" message icon.\n    },\n    {\n        trigger: '.o_barcode_line.o_line_completed',\n        run: 'scan OBTVALI',\n    },\n    { trigger: '.o_notification_bar.bg-success'},\n]});\n\nregistry.category(\"web_tour.tours\").add('test_pack_multiple_scan', { steps: () => [\n    // Create a receipt, scan product1 and product2, pack them and validate the receipt.\n    { trigger: '.o_stock_barcode_main_menu', run: 'scan WHIN' },\n    { trigger: '.o_barcode_client_action', run: 'scan product1' },\n    { trigger: '.o_barcode_client_action', run: 'scan product2' },\n    { trigger: '.o_barcode_line + .o_barcode_line', run: 'scan OBTPACK' },\n    ...stepUtils.validateBarcodeOperation(),\n    {\n        trigger: '.o_stock_barcode_main_menu',\n        run: function () {\n            helper.assertErrorMessage('The transfer has been validated');\n        },\n    },\n    { trigger: \".o_notification_close\", run: \"click\" },\n\n    // Create a delivery, scan two times the same package and check the error message.\n    { trigger: '.o_stock_barcode_main_menu', run: 'scan WHOUT' },\n    { trigger: '.o_barcode_client_action', run: 'scan PACK0001000' },\n    {\n        trigger: '.o_barcode_line',\n        run: function () {\n            const line1 = helper.getLine({ barcode: \"product1\" });\n            helper.assertLineIsHighlighted(line1, true);\n            const line2 = helper.getLine({ barcode: \"product2\" });\n            helper.assertLineIsHighlighted(line2, true);\n        },\n    },\n    { trigger: '.o_barcode_line:nth-child(2)', run: 'scan PACK0001000' },\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: function () {\n            helper.assertErrorMessage('This package is already scanned.');\n            helper.assertLineIsHighlighted(0, false);\n            helper.assertLineIsHighlighted(0, false);\n        },\n    },\n    ...stepUtils.validateBarcodeOperation(),\n    {\n        trigger: '.o_stock_barcode_main_menu',\n        run: function () {\n            helper.assertErrorMessage('The transfer has been validated');\n        },\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_pack_common_content_scan', { steps: () => [\n    /* Scan 2 packages PACK1 and PACK2 that contains both product1 and\n     * product 2. It also scan a single product1 before scanning both packages.\n     * The purpose is to check that lines with a same product are not merged\n     * together. For product 1, we should have 3 lines. One with PACK 1, one\n     * with PACK2 and the last without package.\n     */\n    {\n        trigger: '.o_stock_barcode_main_menu',\n        run: 'scan WHOUT',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan PACK1',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan PACK2',\n    },\n\n    {\n        trigger: '.o_barcode_client_action:contains(\"PACK2\")',\n        run: function () {\n            helper.assertLinesCount(5);\n        },\n    },\n    ...stepUtils.validateBarcodeOperation(),\n\n    {\n        trigger: '.o_stock_barcode_main_menu',\n        run: function () {\n            helper.assertErrorMessage('The transfer has been validated');\n        },\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_pack_multiple_location', { steps: () => [\n    {\n        trigger: '.o_stock_barcode_main_menu',\n        run: 'scan WHINT',\n    },\n\n    {\n        trigger: '.o_barcode_client_action .o_scan_message.o_scan_src',\n        run: 'scan LOC-01-01-00'\n    },\n\n    {\n        trigger: '.o_scan_message.o_scan_product',\n        run: 'scan PACK0000666',\n    },\n\n    {\n        trigger: '.o_package_content',\n        run: () => helper.assertLineQty(0, \"1\")\n    },\n\n    { // Scan a second time the same package => Should raise a warning.\n        trigger: '.o_barcode_client_action',\n        run: 'scan PACK0000666',\n    },\n    { // A notification is shown and the package's qty. should be unchanged.\n        trigger: '.o_notification_bar.bg-danger',\n        run: () => helper.assertLineQty(0, \"1\")\n    },\n\n    {\n        trigger: '.o_package_content',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_kanban_view:contains(\"product1\")',\n        run: function () {\n            helper.assertKanbanRecordsCount(2);\n        },\n    },\n    {\n        trigger: '.o_close',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_scan_message.o_scan_dest',\n        run: 'scan LOC-01-02-00',\n    },\n\n    ...stepUtils.validateBarcodeOperation(\".o_scan_message.o_scan_validate\"),\n\n    {\n        trigger: '.o_stock_barcode_main_menu',\n        run: function () {\n            helper.assertErrorMessage('The transfer has been validated');\n        },\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_pack_multiple_location_02', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-01-00'\n    },\n\n    {\n        trigger: '.o_scan_message.o_scan_product',\n        run: 'scan PACK0002020',\n    },\n    {\n        trigger: '.o_barcode_line.o_selected',\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-02-00',\n    },\n\n    {\n        trigger: '.o_barcode_line .o_line_destination_location:contains(\"WH/Stock/Section 2\")',\n        run: 'scan OBTVALI',\n    },\n\n    {\n        trigger: '.o_notification_bar.bg-success',\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_pack_multiple_location_03', { steps: () => [\n    {trigger: '.o_barcode_client_action', run: 'scan shelf3'},\n    {\n        trigger: '.o_barcode_line',\n        run: function() {\n            helper.assertLinesCount(1);\n            helper.assert(document.querySelector('.o_barcode_line .package').textContent, \"PACK000666\");\n        }\n    },\n    {trigger: '.o_barcode_client_action', run: 'scan product1'},\n    {\n        trigger: '.qty-done:contains(1)',\n        run: function() {\n            helper.assertLinesCount(1);\n            helper.assert(document.querySelectorAll('.o_barcode_lines .o_barcode_line .package').length, 0);\n        }\n    },\n    ...stepUtils.validateBarcodeOperation(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_put_in_pack_from_multiple_pages', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function () {\n            helper.assertLinesCount(4);\n            helper.assertScanMessage('scan_src');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(true);\n        },\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-01-00'\n    },\n\n    {\n        trigger: '.o_scan_message.o_scan_product',\n        run: function () {\n            helper.assertLinesCount(4);\n            helper.assertScanMessage('scan_product');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(true);\n        },\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product2',\n    },\n\n    {\n        trigger: '.o_barcode_line:nth-child(2).o_line_completed',\n        run: 'scan LOC-01-02-00',\n    },\n\n    {\n        trigger: '.o_scan_message:contains(\"Scan a product from Section 2\")',\n        run: 'scan product1',\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product2',\n    },\n\n    {\n        trigger: '.o_validate_page.btn-primary',\n        run: 'scan OBTPACK',\n    },\n\n    ...stepUtils.validateBarcodeOperation('.o_barcode_line:contains(\"PACK\")'),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_put_in_pack_no_freeze', { steps: () => [\n    { trigger: 'button.o_button_operations', run: 'click' },\n\n    { trigger: '.o_kanban_record:contains(Receipts)', run: 'click' },\n\n    { trigger: 'button[name=\"action_open_picking_client_action\"]:last', run: 'click' },\n\n    { trigger: '.o_edit', run: \"click\" },\n\n    {\n        trigger: '.o_field_widget[name=qty_done] input',\n        run() {\n            //input type number not supported by tour helpers.\n            this.anchor.value = \"5.66\";\n        }\n    },\n\n    { trigger: '.o_save', run: 'click' },\n\n    { trigger: '.o_put_in_pack', run: 'click' },\n\n    { trigger: 'button.o_exit', run: 'click' },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_reload_flow', { steps: () => [\n    {\n        trigger: '.o_stock_barcode_main_menu',\n        run: 'scan WHIN'\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1'\n    },\n\n    {\n        trigger: '.o_edit',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_field_widget[name=\"product_id\"]',\n    },\n    {\n        trigger: '.o_field_widget[name=qty_done] input',\n        run: \"edit 2\",\n    },\n\n    {\n        trigger: '.o_save',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_add_line',\n        run: \"click\",\n    },\n\n    {\n        trigger: \".o_field_widget[name=product_id] input\",\n        run: \"edit product2\",\n    },\n\n    {\n        trigger: \".ui-menu-item > a:contains('product2')\",\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_save',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_barcode_line:nth-child(2).o_selected',\n        run: function () {\n            helper.assertScanMessage('scan_product_or_dest');\n        },\n    },\n\n    { trigger: '.o_barcode_client_action', run: 'scan LOC-01-01-00' },\n    // Select first line and scans Section 1 to move it to this location.\n    {\n        trigger: '.o_barcode_line:nth-child(2) .o_line_destination_location:contains(\".../Section 1\")',\n    },\n    {\n        trigger: '.o_barcode_line:first-child',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:first-child.o_selected',\n        run: 'scan LOC-01-01-00'\n    },\n    {\n        trigger: '.o_barcode_line:nth-child(1) .o_line_destination_location:contains(\".../Section 1\")',\n        run: \"click\",\n    },\n    ...stepUtils.validateBarcodeOperation(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_highlight_packs', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertScanMessage('scan_product_or_package');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(true);\n            helper.assertLineIsHighlighted(0, false);\n\n        },\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan PACK002',\n    },\n\n    {\n        trigger: '.o_barcode_client_action:contains(\"PACK002\")',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.o_barcode_client_action',\n        run: function () {\n            helper.assertLinesCount(2);\n            helper.assertScanMessage('scan_product_or_package');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(true);\n            const packageLine = document.querySelector('.o_barcode_line[data-package=\"PACK002\"]');\n            helper.assertLineIsHighlighted(packageLine, true);\n        },\n    },\n\n]});\n\nregistry.category(\"web_tour.tours\").add('test_put_in_pack_from_different_location', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1',\n    },\n\n    {\n        trigger: '.o_barcode_line.o_selected.o_line_completed',\n        run: 'scan product2',\n    },\n\n    {\n        trigger: '.o_validate_page.btn-primary',\n        run: 'scan OBTPACK',\n    },\n\n    {\n        trigger: '.o_barcode_line:contains(\"PACK\")',\n        run: function() {\n            const lines = helper.getLines();\n            helper.assert(lines[0].querySelector('.result-package').innerText, \"PACK0000001\");\n            helper.assert(lines[1].querySelector('.result-package').innerText, \"PACK0000001\");\n        },\n    },\n    // Scans dest. location.\n    {\n        trigger: '.o_scan_message.o_scan_product_or_dest',\n        run: 'scan LOC-01-02-00',\n    },\n    ...stepUtils.validateBarcodeOperation('.o_scan_message.o_scan_validate'),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_put_in_pack_before_dest', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-01-00',\n    },\n\n    {\n        trigger: '.o_barcode_location_line[data-location=\"WH/Stock/Section 1\"].text-bg-800',\n        run: 'scan product1',\n    },\n    { trigger: '.o_scan_message.o_scan_product_or_dest', run: 'scan LOC-01-02-00' },\n\n    {\n        trigger: '.o_scan_message.o_scan_src',\n        run: 'scan shelf3',\n    },\n\n    {\n        trigger: '.o_barcode_location_line[data-location=\"WH/Stock/Section 3\"].text-bg-800',\n        run: 'scan product2',\n    },\n\n    {\n        trigger: '.o_barcode_line.o_selected .qty-done:contains(\"1\")',\n        run: 'scan shelf4',\n    },\n\n    {\n        trigger: '.o_scan_message.o_scan_validate',\n        run: 'scan OBTPACK'\n    },\n\n    {\n        trigger: '.modal .modal-title:contains(\"Choose destination location\")',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.modal .o_field_widget[name=\"location_dest_id\"] input',\n        run: 'click',\n    },\n    {\n        isActive: [\"auto\"],\n        trigger: '.modal .ui-menu-item > a:contains(\"Section 2\")',\n        run: \"click\",\n    },\n\n    {\n        trigger: '.modal .o_field_widget[name=\"location_dest_id\"]',\n        run: function () {\n            helper.assert(\n                document.querySelector('.o_field_widget[name=\"location_dest_id\"] input').value,\n                'WH/Stock/Section 2'\n            );\n        },\n    },\n\n    {\n        trigger: '.modal .btn-primary',\n        run: \"click\",\n    },\n    {\n        trigger: \"body:not(:has(.modal))\",\n    },\n    ...stepUtils.validateBarcodeOperation(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_put_in_pack_scan_package', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function() {\n            helper.assertLinesCount(3);\n        }\n    },\n    {\n        trigger: '.o_scan_message.o_scan_src',\n        run: 'scan LOC-01-01-00'\n    },\n\n    {\n        trigger: '.o_scan_message.o_scan_product',\n        run: 'scan product1',\n    },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"product1\"] .qty-done:contains(\"1\")',\n        run: 'scan OBTPACK',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"product1\"):contains(\"PACK0000001\")',\n        run: function() {\n            const line1 = helper.getLine({ barcode: \"product1\", selected: true });\n            const product1_package = line1.querySelector('div[name=\"package\"]').innerText;\n            helper.assert(product1_package, 'PACK0000001');\n        }\n    },\n\n    // Scans product2 then scans the package.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product2',\n    },\n    {\n        trigger: '.o_barcode_line.o_highlight:contains(\"product2\")',\n        run: 'scan PACK0000001',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"product2\"):contains(\"PACK0000001\")',\n        run: function() {\n            const line1 = helper.getLine({ barcode: \"product1\", completed: true });\n            const line2 = helper.getLine({ barcode: \"product2\", selected: true });\n            const product1_package = line1.querySelector('div[name=\"package\"]').innerText;\n            const product2_package = line2.querySelector('div[name=\"package\"]').innerText;\n            helper.assert(product1_package, 'PACK0000001');\n            helper.assert(product2_package, 'PACK0000001');\n        }\n    },\n\n    // Scans next location then scans again product1 and PACK0000001.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-02-00',\n    },\n    {\n        trigger: '.o_barcode_location_line[data-location=\"WH/Stock/Section 2\"].text-bg-800',\n        run: 'scan product1',\n    },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"product1\"].o_selected .qty-done:contains(\"1\")',\n        run: 'scan PACK0000001',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"product1\").o_selected:contains(\"PACK0000001\")',\n        run: function() {\n            const line1 = helper.getLine({ barcode: \"product1\", selected: true });\n            const product1_package = line1.querySelector('div[name=\"package\"]').innerText;\n            helper.assert(product1_package, 'PACK0000001');\n        }\n    },\n    ...stepUtils.validateBarcodeOperation(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_put_in_pack_new_lines', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan P00001',\n    },\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"product1\")',\n        run: 'scan P00001',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"product1\"):contains(\"P00001\")',\n        run: 'scan OBTVALI',\n    },\n    {\n        trigger: '.o_notification_bar.bg-success',\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_picking_owner_scan_package', { steps: () => [\n    {\n        trigger: '.o_stock_barcode_main_menu',\n        run: 'scan WHOUT',\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan P00001',\n    },\n    { trigger: '.o_barcode_client_action:contains(\"P00001\")' },\n    { trigger: '.o_barcode_client_action:contains(\"Azure Interior\")' },\n    ...stepUtils.validateBarcodeOperation(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_receipt_delete_button', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1',\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product2',\n    },\n    ...stepUtils.confirmAddingUnreservedProduct(),\n    // ensure receipt's extra product CAN be deleted\n    {\n        trigger: '.o_barcode_line[data-barcode=\"product2\"] .o_edit',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_field_widget[name=\"product_id\"]',\n        run: function () {\n            helper.assert(document.querySelectorAll('.o_delete').length, 1);\n        },\n    },\n    {\n        trigger: '.o_discard',\n        run: \"click\",\n    },\n    // ensure receipt's original move CANNOT be deleted\n    {\n        trigger: '.o_barcode_line:nth-child(2) .o_edit',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_field_widget[name=\"product_id\"]',\n        run: function () {\n            helper.assert(document.querySelectorAll('.o_delete').length, 0);\n        },\n    },\n    {\n        trigger: '.o_discard',\n        run: \"click\",\n    },\n    // add extra product not in original move + delete it\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product3',\n    },\n    ...stepUtils.confirmAddingUnreservedProduct(),\n    {\n        trigger: '.o_barcode_line[data-barcode=\"product3\"] .o_edit',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_delete',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_validate_page',\n        run: 'scan OBTVALI',\n    }, {\n        content: \"check the picking is validated\",\n        trigger: '.o_notification_bar.bg-success',\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add(\"test_scan_aggregate_barcode\", { steps: () => [\n    { trigger: '.o_stock_barcode_main_menu', run: 'scan WHIN' },\n    // Scan 3x product1 (using ',' as separator).\n    { trigger: '.o_barcode_client_action', run: 'scan product1,product1,product1' },\n    {\n        trigger: '.o_barcode_line.o_selected .qty-done:contains(3)',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ barcode: 'product1' });\n            helper.assertLineQty(line, \"3\");\n        }\n    },\n    // Scan 1x product1 and 2x product2 (using '|' as separator).\n    { trigger: '.o_barcode_client_action', run: 'scan product1|product2|product2' },\n    {\n        trigger: '.o_barcode_line.o_selected .qty-done:contains(2)',\n        run: function () {\n            helper.assertLinesCount(2);\n            const notSelectedLine = helper.getLine({ selected: false });\n            const selectedLine = helper.getLine({ selected: true });\n            helper.assertLineProduct(notSelectedLine, \"product1\");\n            helper.assertLineQty(notSelectedLine, \"4\");\n            helper.assertLineProduct(selectedLine, \"product2\");\n            helper.assertLineQty(selectedLine, \"2\");\n        }\n    },\n    {\n        content: \"Scan a tracked product and all of its SNs\",\n        trigger: '.o_barcode_client_action',\n        run: 'scan productserial1|sn01,sn02,sn05,sn04,sn03,sn06,sn10,sn07,sn08,sn09',\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .qty-done:contains(10)',\n    },\n    {\n        content: \"Unfold grouped lines (productserial1)\",\n        trigger: '.o_line_button.o_toggle_sublines',\n        run: \"click\",\n    },\n    ...stepUtils.validateBarcodeOperation(),\n]});\n\nregistry.category(\"web_tour.tours\").add(\"test_scrap\", { steps: () => [\n    // Opens the receipt and checks we can't scrap if not done.\n    { trigger: \".o_stock_barcode_main_menu\", run: \"scan receipt_scrap_test\" },\n    {\n        trigger: \".o_barcode_actions\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_barcode_settings\",\n        run: function() {\n            const scrapButton = document.querySelector(\"button.o_scrap\");\n            helper.assert(Boolean(scrapButton), false, \"Scrap button shouldn't be displayed\");\n        },\n    },\n    {\n        trigger: \"button.o_close\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_barcode_lines\",\n        run: \"scan OBTSCRA\",\n    },\n    {\n        trigger: \".o_notification:has(.o_notification_bar.bg-warning):contains('You can\\\\'t register scrap')\",\n        run: \"click\",\n    },\n    // Process the receipt then re-opens it again.\n    {\n        trigger: \".o_line_button.o_add_remaining_quantity\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_validate_page.btn-primary\",\n        run: \"click\",\n    },\n    { trigger: \".o_stock_barcode_main_menu\", run: \"scan receipt_scrap_test\" },\n    {\n        trigger: \".o_scan_message.o_picking_already_done\",\n        run: \"scan OBTSCRA\",\n    },\n    {\n        trigger: \".o_field_widget[name='scrap_qty']\",\n    },\n    {\n        trigger: \".btn[special='cancel']\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_barcode_actions\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_barcode_settings\",\n        run: function() {\n            const scrapButton = document.querySelector(\"button.o_scrap\");\n            helper.assert(Boolean(scrapButton), true, \"Scrap button should be displayed\");\n        },\n    },\n    // Exits the receipt and opens the delivery.\n    {\n        trigger: \"button.o_exit\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_barcode_lines_header\",\n    },\n    {\n        trigger: \"button.o_exit\",\n        run: \"click\",\n    },\n    { trigger: \".o_stock_barcode_main_menu\", run: \"scan delivery_scrap_test\" },\n    // Checks we can scrap for a delivery.\n    {\n        trigger: \".o_barcode_actions\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_barcode_settings\",\n        run: function() {\n            const scrapButton = document.querySelector(\"button.o_scrap\");\n            helper.assert(Boolean(scrapButton), true, \"Scrap button should be displayed\");\n        },\n    },\n    {\n        trigger: \"button.o_close\",\n        run: \"click\",\n    },\n    { trigger: \".o_barcode_lines\", run: \"scan OBTSCRA\" },\n    {\n        trigger: \".o_field_widget[name='scrap_qty']\",\n    },\n    {\n        trigger: \".btn[special='cancel']\",\n        run: \"click\",\n    },\n    // Process the delivery then re-opens it again.\n    {\n        trigger: \".o_line_button.o_add_remaining_quantity\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_validate_page.btn-primary\",\n        run: \"click\",\n    },\n    { trigger: \".o_stock_barcode_main_menu\", run: \"scan delivery_scrap_test\" },\n    { trigger: \".o_barcode_lines_header\", run: \"scan OBTSCRA\" },\n    {\n        trigger: \".o_notification:has(.o_notification_bar.bg-warning):contains('You can\\\\'t register scrap')\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_barcode_actions\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_barcode_settings\",\n        run: function() {\n            const scrapButton = document.querySelector(\"button.o_scrap\");\n            helper.assert(Boolean(scrapButton), false, \"Scrap button shouldn't be displayed\");\n        },\n    },\n    // Exits the delivery and opens the receipt, checks if the digipad scrap view is used\n    {\n        trigger: \"button.o_exit\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_barcode_lines_header\",\n    },\n    {\n        trigger: \"button.o_exit\",\n        run: \"click\",\n    },\n    { trigger: \".o_stock_barcode_main_menu\", run: \"scan receipt_scrap_test\" },\n    {\n        trigger: \".o_barcode_actions\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_barcode_settings\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_scrap\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_qty_done_field_not_completed\",\n        run: function() {\n            const digipadView = document.querySelector(\".o_digipad_widget\");\n            helper.assert(Boolean(digipadView), true, \"Scrap view should use the digipad widget.\");\n        },\n    },\n    {\n        content: \"Select SN product from the dropdown\",\n        trigger: \".o_input_dropdown .o_input#product_id_0\",\n        run: \"click\",\n    },\n    {\n        content: \"Product `productserial1` should be available, despite not having any move lines\",\n        trigger: \"a.dropdown-item:contains('productserial1')\",\n        run: \"click\",\n    },\n    {\n        content: \"Digipad should be hidden after selecting the SN product\",\n        trigger: \"body:not(:has(.o_digipad_widget))\",\n    },\n    {\n        content: \"Check available lots\",\n        trigger: \".o_input_dropdown .o_input#lot_id_0\",\n        run: \"click\",\n    },\n    {\n        trigger: \"ul.o-autocomplete--dropdown-menu\",\n    },\n    {\n        trigger: \"a.dropdown-item:contains('SN')\",\n        run: function() {\n            const dropdownContent = document.querySelector(\".dropdown-menu\").textContent;\n            helper.assert(\n                dropdownContent.includes(\"SN0001\") && dropdownContent.includes(\"SN0002\"), true,\n                \"All SN lots for productserial1 should be available in the dropdown.\"\n            );\n        },\n    },\n    {\n        trigger: \"input#should_replenish_0\",\n        run: \"click\",\n    },\n    // Exits the form\n    {\n        trigger: \"button.o_discard\",\n        run: \"click\",\n    },\n    {\n        trigger: \"button.o_exit\",\n        run: \"click\",\n    },\n    {\n        trigger: 'button.o_button_operations',\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add(\"test_picking_scan_package_confirmation\", { steps: () => [\n    // Scan product 1\n    { trigger: '.o_barcode_client_action', run: 'scan product1' },\n    // Scan Package 1 to trigger the scan confirmation\n    { trigger: '.o_barcode_line .qty-done:contains(\"1\")', run: 'scan package001' },\n    // Cancel the package scan\n    {\n        trigger: \".modal-content button.btn-secondary\",\n        run: \"click\",\n    },\n    // Scan Package 1 to trigger the scan confirmation\n    { trigger: '.o_barcode_line .qty-done:contains(\"1\")', run: 'scan package001' },\n    // Confirm the package scan, thus the line quantity will be increased\n    {\n        trigger: \".modal-content button.btn-primary\",\n        run: \"click\",\n    },\n    { trigger: '.o_barcode_line .qty-done:contains(\"2\")'},\n]});\n\nregistry.category(\"web_tour.tours\").add('test_show_entire_package', { steps: () => [\n    {\n        trigger: 'button.o_button_operations',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_kanban_record:contains(Delivery Orders)',\n        run: \"click\",\n    },\n\n    // Opens picking with the package level.\n    {\n        trigger: '.o_kanban_record:contains(Delivery with Package Level)',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertScanMessage('scan_product_or_package');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(true);\n            const line = helper.getLine();\n            helper.assertLineIsHighlighted(line, false);\n            helper.assertButtonIsVisible(line, \"package_content\");\n            helper.assert(line.querySelector('[name=\"package\"]').innerText, \"package001package001\");\n            helper.assertLineQty(line, \"0/1\");\n        },\n    },\n    {\n        trigger: '.o_line_button.o_package_content',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_kanban_view .o_kanban_record',\n        run: function () {\n            helper.assertKanbanRecordsCount(1);\n        },\n    },\n    {\n        trigger: 'button.o_close',\n        run: \"click\",\n    },\n    // Scans package001 to be sure no moves will be created but the package line will be done.\n    { trigger: '.o_barcode_lines', run: 'scan package001' },\n    {\n        trigger: '.o_barcode_line.o_line_completed',\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertScanMessage('scan_validate');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(true);\n            helper.assertValidateEnabled(true);\n            const line = helper.getLine();\n            helper.assertLineIsHighlighted(line, false);\n            helper.assertButtonIsVisible(line, \"package_content\");\n            helper.assert(line.querySelector('[name=\"package\"]').innerText, \"package001package001\");\n            helper.assertLineQty(line, \"1/1\");\n        },\n    },\n    {\n        trigger: 'button.o_exit',\n        run: \"click\",\n    },\n\n    // Opens picking with the move.\n    {\n        trigger: '.o_kanban_record:contains(Delivery with Stock Move)',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertScanMessage('scan_product');\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(false);\n            helper.assertValidateEnabled(true);\n            const line = helper.getLine();\n            helper.assertLineIsHighlighted(line, false);\n            helper.assertButtonIsVisible(line, \"package_content\", false);\n            helper.assert(line.querySelector('[name=\"package\"]').innerText, \"package002\");\n            helper.assertLineQty(0, '0/2');\n        },\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_define_the_destination_package', { steps: () => [\n    {\n        trigger: '.o_line_button.o_add_remaining_quantity',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line .qty-done:contains(\"1\")',\n        run: 'scan PACK02',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"PACK02\")',\n    },\n    {\n        trigger: '.btn.o_validate_page',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_notification_bar.bg-success',\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('stock_barcode_package_with_lot', { steps: () => [\n    {\n        trigger: \"[data-menu-xmlid='stock_barcode.stock_barcode_menu']\", // open barcode app\n        run: \"click\",\n    },\n    {\n        trigger: \".o_button_inventory\",\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan Lot-test' // scan lot on a new location\n    },\n    {\n        trigger: '.o_barcode_line .package:contains(Package-test)', // verify it takes the right quantity\n    },\n    {\n        trigger: '.o_apply_page',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_notification_bar.bg-success',\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_scan_same_lot_different_products', { steps: () => [\n    // Scanning 123 will fetch the lot 123 for the 'aaa' product and add them\n    // both in the cache (the 'aaa' product and its lot.)\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 123',\n    },\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: 'scan bbb',\n    },\n    // Scanning again 123 should now fetch the lot for selected product ('bbb')\n    // even if the lot 123 for 'aaa' product is already in the cache.\n    {\n        trigger: '.o_barcode_line:contains(\"bbb\")',\n        run: 'scan 123',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"bbb\"):contains(\"123\")',\n        run: function () {\n            helper.assertLinesCount(1);\n        },\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_avoid_useless_line_creation', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOT01',\n    },\n    {\n        trigger: '.o_barcode_line',\n        run: 'scan LOREM',\n    },\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: function () {\n            helper.assertErrorMessage(\"This product doesn't exist.\");\n        },\n    },\n    // Open the form view to trigger a save\n    {\n        trigger: '.o_barcode_line:first-child .o_edit',\n        run: \"click\",\n    },\n    ...stepUtils.discardBarcodeForm(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_setting_barcode_allow_extra_product', { steps: () => [\n    // Scans the delivery to open it.\n    { trigger: '.o_stock_barcode_main_menu', run: 'scan delivery_test' },\n    // Scans the reserved product.\n    { trigger: '.o_barcode_line', run: 'scan product1' },\n    // Try to scan a not-reserved product -> Display a warning.\n    { trigger: '.o_barcode_line.o_selected.o_line_completed', run: 'scan product2' },\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: function() {\n            helper.assertErrorMessage(\"The product product2 should not be picked in this operation.\");\n        }\n    },\n    // Valid the delivery, then create another one. Checks any product can be scanned regardless the delivery type config.\n    { trigger: '.o_barcode_client_action', run: 'scan OBTVALI' },\n    { trigger: '.o_stock_barcode_main_menu', run: 'scan WHOUT' },\n    { trigger: '.o_barcode_client_action', run: 'scan product1' },\n    { trigger: '.o_barcode_line', run: 'scan product2' },\n    { trigger: '.o_barcode_line:nth-child(2)', run: function() {\n        const lines = helper.getLines();\n        helper.assert(lines.length, 2);\n        helper.assertLineProduct(lines[0], \"product1\");\n        helper.assertLineProduct(lines[1], \"product2\");\n    }},\n]});\n\nregistry.category(\"web_tour.tours\").add('test_split_line_reservation', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function () {\n            helper.assertLinesCount(3);\n            helper.assertScanMessage('scan_src');\n        },\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-00-00'\n    },\n    {\n        trigger: '.o_scan_message.o_scan_product',\n        run: 'scan productlot1'\n    },\n    {\n        trigger: '.o_scan_message.o_scan_lot',\n        run: 'scan LOT01'\n    },\n    {\n        trigger: '.o_scan_message.o_scan_lot',\n        run: 'scan LOT02'\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .qty-done:contains(2)',\n        run: 'scan LOC-01-01-00'\n    },\n    {\n        trigger: '.o_barcode_location_line[data-location=\"WH/Stock/Section 1\"].text-bg-800',\n        run: function () {\n            helper.assertLinesCount(4);\n            let line = helper.getLine({ barcode: 'productlot1', completed: true });\n            helper.assertLineSourceLocation(line, 'WH/Stock');\n            helper.assertLineQty(line, '2/2');\n            line = helper.getLine({ barcode: 'productlot1', completed: false });\n            helper.assertLineSourceLocation(line, 'WH/Stock/Section 1');\n            helper.assertLineQty(line, '0/3');\n        },\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan productlot1'\n    },\n    {\n        trigger: '.o_barcode_line.o_selected',\n        run: 'scan LOT02'\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOT02'\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .qty-done:contains(2)',\n        run: 'scan LOC-01-02-00'\n    },\n    {\n        trigger: '.o_barcode_location_line[data-location=\"WH/Stock/Section 2\"].text-bg-800',\n        run: function () {\n            helper.assertLinesCount(5);\n        },\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan productlot1'\n    },\n    {\n        trigger: '.o_barcode_line.o_selected',\n        run: 'scan LOT03'\n    },\n    {\n        trigger: '.o_barcode_line .o_line_lot_name:contains(\"LOT03\")',\n        run: function () {\n            const lines = helper.getLines({ barcode: 'productlot1' });\n            [0, 1, 2].map(i => helper.assertLineQty(lines[i], [\"2/2\", \"2/2\", \"1/1\"][i]));\n        },\n    },\n    // Scan product1 x2 from WH/Stock.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-00-00'\n    },\n    {\n        trigger: '.o_barcode_location_line.text-bg-800[data-location=\"WH/Stock\"]',\n        run: 'scan product1'\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1'\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .qty-done:contains(2)',\n        run: 'scan LOC-01-01-00'\n    },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"product1\"].o_line_completed',\n        run: function () {\n            helper.assertLinesCount(6);\n            const lines = helper.getLines({ barcode: 'product1' });\n            [0, 1].map(i => helper.assertLineQty(lines[i], [\"2/2\", \"0/2\"][i]));\n        },\n    },\n    // scan product1 x2 from Section 1\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product1'\n    },\n    {\n        trigger: '.o_barcode_line.o_selected:not(.o_line_completed)',\n        run: 'scan product1'\n    },\n    {\n        trigger: '.o_barcode_line.o_selected.o_line_completed',\n        run: 'scan LOC-01-00-00'\n    },\n    // scan product2 x2 from WH/Stock\n    {\n        trigger: '.o_barcode_location_line.text-bg-800[data-location=\"WH/Stock\"]',\n        run: 'scan product2'\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product2'\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .qty-done:contains(2)',\n        run: 'scan LOC-01-01-00'\n    },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"product2\"].o_line_completed',\n        run: function () {\n            helper.assertLinesCount(7);\n            const lines = helper.getLines({ barcode: 'product2' });\n            [0, 1].map(i => helper.assertLineQty(lines[i], [\"2/2\", \"0/1\"][i]));\n        },\n    },\n    // scan product2 x1 from Section 1\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product2'\n    },\n    { trigger: \".o_validate_page.btn-primary\"},\n    // Open a line form view to trigger a save.\n    { trigger: '.o_barcode_line .o_edit', run: \"click\" },\n    { trigger: '.o_discard', run: \"click\" },\n    {\n        trigger: '.o_barcode_line',\n        run: function () {\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(true);\n            helper.assertValidateEnabled(true);\n            // Check lines' quantity didn't change.\n            let lines = helper.getLines({ barcode: 'product1' });\n            [0, 1].map(i => helper.assertLineQty(lines[i], [\"2/2\", \"2/2\"][i]));\n            lines = helper.getLines({ barcode: 'product2' });\n            [0, 1].map(i => helper.assertLineQty(lines[i], [\"2/2\", \"1/1\"][i]));\n            lines = helper.getLines({ barcode: 'productlot1' });\n            [0, 1, 2].map(i => helper.assertLineQty(lines[i], [\"2/2\", \"2/2\", \"1/1\"][i]));\n        },\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_split_line_on_destination_scan', { steps: () => [\n    // Open the receipt then scans 2x product1.\n    { trigger: '.o_stock_barcode_main_menu', run: \"scan receipt_split_line_on_destination_scan\" },\n    { trigger: '.o_barcode_line', run: \"scan product1\" },\n    { trigger: '.o_barcode_line', run: \"scan product1\" },\n    {\n        trigger: '.o_barcode_line.o_selected .qty-done:contains(\"2\")',\n        run: () => {\n            helper.assertLinesCount(1);\n            helper.assertLineDestinationLocation(0, \"WH/Stock\");\n            helper.assertLineQty(0, \"2/4\");\n        }\n    },\n    // Scans the line's destination -> The line should be splitted in two.\n    { trigger: '.o_barcode_line', run: \"scan LOC-01-00-00\" },\n    {\n        trigger: '.o_barcode_line:nth-child(2)',\n        run: () => {\n            helper.assertLinesCount(2);\n            helper.assertLineDestinationLocation(0, \"WH/Stock\");\n            helper.assertLineDestinationLocation(1, \"WH/Stock\");\n            helper.assertLineQty(0, \"2/2\");\n            helper.assertLineQty(1, \"0/2\");\n        }\n    },\n    // Scans remaining quantity, then shelf1 as the destination and close the receipt.\n    { trigger: '.o_barcode_line', run: \"scan product1\" },\n    { trigger: '.o_barcode_line.o_selected:not(.o_line_completed)', run: \"scan product1\" },\n    { trigger: '.o_barcode_line.o_selected.o_line_completed', run: \"scan LOC-01-01-00\" },\n    {\n        trigger: '.o_validate_page.btn-primary',\n        run: () => {\n            helper.assertLinesCount(2);\n            helper.assertLineDestinationLocation(0, \"WH/Stock\");\n            helper.assertLineDestinationLocation(1, \".../Section 1\");\n            helper.assertLineQty(0, \"2/2\");\n            helper.assertLineQty(1, \"2/2\");\n        }\n    },\n    ...stepUtils.validateBarcodeOperation(),\n    // Now, open the internal transfer and scan Section 1 as the source location.\n    { trigger: '.o_stock_barcode_main_menu', run: \"scan internal_split_line_on_destination_scan\" },\n    {\n        trigger: '.o_barcode_line',\n        run: () => {\n            helper.assertLinesCount(2);\n            helper.assertLineLocations(0, \"WH/Stock/Section 1\", \"WH/Stock\");\n            helper.assertLineQty(0, \"0/3\");\n            helper.assertLineLocations(1, \"WH/Stock/Section 2\", \"WH/Stock\");\n            helper.assertLineQty(1, \"0/4\");\n        }\n    },\n    { trigger: '.o_scan_message.o_scan_src', run: \"scan LOC-01-01-00\" },\n    // Scan 2x product2 then scan Section 3 as the destination.\n    { trigger: '.o_barcode_line', run: \"scan product2\" },\n    { trigger: '.o_barcode_line', run: \"scan product2\" },\n    { trigger: '.o_barcode_line.o_selected .qty-done:contains(\"2\")', run: \"scan shelf3\" },\n    {\n        trigger: '.o_scan_message.o_scan_src',\n        run: () => {\n            helper.assertLinesCount(3);\n            helper.assertLineLocations(0, \"WH/Stock/Section 1\", \".../Section 3\");\n            helper.assertLineQty(0, \"2/2\");\n            helper.assertLineLocations(1, \"WH/Stock/Section 1\", \"WH/Stock\");\n            helper.assertLineQty(1, \"0/1\");\n            helper.assertLineLocations(2, \"WH/Stock/Section 2\", \"WH/Stock\");\n            helper.assertLineQty(2, \"0/4\");\n        }\n    },\n    // Scan 1x product2 then scan WH/Stock as the destination.\n    { trigger: '.o_barcode_line', run: \"scan LOC-01-01-00\" },\n    { trigger: '.o_scan_message.o_scan_product', run: \"scan product2\" },\n    { trigger: '.o_barcode_line.o_selected.o_line_completed', run: \"scan LOC-01-00-00\" },\n    {\n        trigger: '.o_scan_message.o_scan_src',\n        run: () => {\n            helper.assertLinesCount(3);\n            helper.assertLineLocations(0, \"WH/Stock/Section 1\", \".../Section 3\");\n            helper.assertLineQty(0, \"2/2\");\n            helper.assertLineLocations(1, \"WH/Stock/Section 1\", \"WH/Stock\");\n            helper.assertLineQty(1, \"1/1\");\n            helper.assertLineLocations(2, \"WH/Stock/Section 2\", \"WH/Stock\");\n            helper.assertLineQty(2, \"0/4\");\n        }\n    },\n    // Scan Section 2 as the source and then 2x product2.\n    { trigger: '.o_barcode_line', run: \"scan LOC-01-02-00\" },\n    { trigger: '.o_scan_message.o_scan_product', run: \"scan product2\" },\n    { trigger: '.o_scan_message.o_scan_product_or_dest', run: \"scan product2\" },\n    // Now scan Section 2 also as the destination.\n    { trigger: '.o_barcode_line.o_selected .qty-done:contains(\"2\")', run: \"scan LOC-01-02-00\" },\n    {\n        trigger: '.o_scan_message.o_scan_src',\n        run: () => {\n            helper.assertLinesCount(4);\n            helper.assertLineLocations(0, \"WH/Stock/Section 1\", \".../Section 3\");\n            helper.assertLineQty(0, \"2/2\");\n            helper.assertLineLocations(1, \"WH/Stock/Section 1\", \"WH/Stock\");\n            helper.assertLineQty(1, \"1/1\");\n            helper.assertLineLocations(2, \"WH/Stock/Section 2\", \".../Section 2\");\n            helper.assertLineQty(2, \"2/2\");\n            helper.assertLineLocations(3, \"WH/Stock/Section 2\", \"WH/Stock\");\n            helper.assertLineQty(3, \"0/2\");\n        }\n    },\n    // Scan 2x product2, Section 1 as the destination and close the internal transfer.\n    { trigger: '.o_scan_message.o_scan_src', run: \"scan LOC-01-02-00\" },\n    { trigger: '.o_scan_message.o_scan_product', run: \"scan product2\" },\n    { trigger: '.o_scan_message.o_scan_product_or_dest', run: \"scan product2\" },\n    { trigger: '.o_scan_message.o_scan_dest', run: \"scan LOC-01-01-00\" },\n    ...stepUtils.validateBarcodeOperation('.o_validate_page.btn-primary'),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_split_line_on_exit_for_delivery', { steps: () => [\n    // Opens the delivery and checks its lines.\n    { trigger: \".o_stock_barcode_main_menu\", run: \"scan delivery_split_line_on_exit\" },\n    {\n        trigger: \".o_barcode_client_action\",\n        run: () => {\n            helper.assertLinesCount(3);\n            helper.assertLineProduct(0, \"product1\");\n            helper.assertLineQty(0, \"0/4\");\n            helper.assertLineProduct(1, \"product2\");\n            helper.assertLineQty(1, \"0/4\");\n            helper.assertLineProduct(2, \"product3\");\n            helper.assertLineQty(2, \"0/2\");\n        }\n    },\n    // Scans 4x product1, 2x product2 and leaves the delivery without scanning product3.\n    { trigger: \".o_barcode_client_action\", run: \"scan product1\" },\n    { trigger: \".o_barcode_client_action\", run: \"scan product1\" },\n    { trigger: \".o_barcode_client_action\", run: \"scan product1\" },\n    { trigger: \".o_barcode_client_action\", run: \"scan product1\" },\n    { trigger: \".o_barcode_line.o_selected.o_line_completed\", run: \"scan product2\" },\n    { trigger: \".o_barcode_line.o_selected:not(.o_line_completed)\", run: \"scan product2\" },\n    // Leaves the delivery, the 2/4 product2 line should be split into two lines (2/2 and 0/2.)\n    { trigger: \".o_barcode_line.o_selected .qty-done:contains(2)\"},\n    { trigger: \"button.o_exit\", run: \"click\" },\n    { trigger: \".o_stock_barcode_main_menu\" },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_split_line_on_exit_for_receipt', { steps: () => [\n    // Opens the receipt and check its lines.\n    { trigger: \".o_stock_barcode_main_menu\", run: \"scan receipt_split_line_on_exit\" },\n    {\n        trigger: \".o_barcode_client_action\",\n        run: () => {\n            helper.assertLinesCount(2);\n            helper.assertLineProduct(0, \"product1\");\n            helper.assertLineQty(0, \"0/4\");\n            helper.assertLineProduct(1, \"product2\");\n            helper.assertLineQty(1, \"0/4\");\n        }\n    },\n    // Scans 1x product1 then put in pack => Should split the line.\n    { trigger: \".o_barcode_client_action\", run: \"scan product1\" },\n    { trigger: \".o_barcode_line.o_selected\", run: \"scan OBTPACK\" },\n    // Scans again 2x product1 => The line with no package just be incremented.\n    { trigger: \".o_barcode_line.o_selected .result-package\", run: \"scan product1\" },\n    { trigger: \".o_barcode_line.o_selected.o_line_not_completed\", run: \"scan product1\" },\n    // Scans 1x product2 then checks the lines' state.\n    { trigger: \".o_barcode_line.o_selected .qty-done:contains('2')\", run: \"scan product2\" },\n    {\n        trigger: \".o_barcode_line.o_selected .qty-done:contains('1')\",\n        run: () => {\n            helper.assertLinesCount(3);\n            const [line1, line2, line3] = helper.getLines();\n            helper.assertLineProduct(line1, \"product1\");\n            helper.assertLineQty(line1, \"2/3\");\n            helper.assertLineProduct(line2, \"product2\");\n            helper.assertLineQty(line2, \"1/4\");\n            helper.assertLineProduct(line3, \"product1\");\n            helper.assertLineQty(line3, \"1/1\");\n            helper.assert(line3.querySelector(\".result-package\").innerText, \"PACK0001000\")\n        }\n    },\n    // Goes back to the main menu (that's here the uncompleted lines shoud be split.)\n    {\n        trigger: \"button.o_exit\",\n        run: \"click\",\n    },\n    // Re-opens the picking and checks uncompleted lines were split.\n    { trigger: \".o_stock_barcode_main_menu\", run: \"scan receipt_split_line_on_exit\" },\n    {\n        trigger: \".o_barcode_client_action\",\n        run: () => {\n            helper.assertLinesCount(5);\n            const [line1, line2, line3, line4, line5] = helper.getLines();\n            helper.assertLineProduct(line1, \"product1\");\n            helper.assertLineQty(line1, \"0/1\");\n            helper.assertLineProduct(line2, \"product2\");\n            helper.assertLineQty(line2, \"0/3\");\n            helper.assertLineProduct(line3, \"product1\");\n            helper.assertLineQty(line3, \"2/2\");\n            helper.assertLineProduct(line4, \"product1\");\n            helper.assertLineQty(line4, \"1/1\");\n            helper.assert(line4.querySelector(\".result-package\").innerText, \"PACK0001000\")\n            helper.assertLineProduct(line5, \"product2\");\n            helper.assertLineQty(line5, \"1/1\");\n        }\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_split_line_on_scan', { steps: () => [\n    // Scan product2 twice\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product2'\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .qty-done:contains(1)',\n        run: 'scan product2'\n    },\n    // Add current balance to empty pack\n    {\n        trigger:  '.o_barcode_line.o_selected .qty-done:contains(2)',\n        run: 'scan THEPACK1'\n    },\n    // Check that line gets split properly\n    {\n        trigger: '.o_barcode_line[data-barcode=\"product2\"].o_line_completed',\n        run: function () {\n            helper.assertLinesCount(2);\n            [0, 1].map(i => helper.assertLineQty(i, [\"0/3\", \"2/2\"][i]));\n        },\n    },\n    // Assign empty move line to other empty pack\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan THEPACK2'\n    },\n    // Ensure it doesn't split prematurely\n    {\n        trigger:  '.o_barcode_line.o_selected:contains(\"THEPACK2\") .qty-done:contains(0)',\n        run: function () {\n            helper.assertLinesCount(2);\n            const lines = helper.getLines({ barcode: 'product2' });\n            helper.assert(lines[0].querySelector('.result-package').innerText, \"THEPACK2\");\n            helper.assert(lines[1].querySelector('.result-package').innerText, \"THEPACK1\");\n        },\n    },\n    // Add product2 x3 to finish the new move line\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product2'\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product2'\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan product2'\n    },\n    {\n        trigger: '.o_validate_page',\n        run: function () {\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(true);\n            helper.assertValidateEnabled(true);\n            // Check that lines' quantity didn't change.\n            helper.assertLinesCount(2);\n            [0, 1].map(i => helper.assertLineQty(i, [\"3/3\", \"2/2\"][i]));\n        },\n    },\n    {\n        trigger: '.btn.o_validate_page',\n        run: \"click\",\n    },\n    { trigger: '.o_notification_bar.bg-success'},\n]});\n\nregistry.category(\"web_tour.tours\").add('test_scan_line_splitting_preserve_destination', { steps: () => [\n    // Select the first (only) line\n    {\n        trigger: '.o_barcode_line',\n        run: 'click',\n    },\n    {\n        trigger:  '.o_barcode_line.o_selected',\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertLineQty(0, '0/5');\n            helper.assertLineDestinationLocation(0, 'WH/Stock');\n        },\n    },\n    // Reassign destination, add product2 x3, then pack it\n    {\n        trigger: '.o_barcode_line',\n        run: 'scan shelf3',\n    },\n    {\n        trigger: '.o_barcode_line .o_line_destination_location:contains(\"Section 3\")',\n        run: 'scan product2',\n    },\n    {\n        trigger: '.o_barcode_line',\n        run: 'scan product2',\n    },\n    {\n        trigger: '.o_barcode_line .qty-done:contains(\"2\")',\n        run: 'scan THEPACK1',\n    },\n    // Ensure that packing split the line and preserved the new destination\n    {\n        trigger:  '.o_barcode_line.o_selected .qty-done:contains(0)',\n        run: function () {\n            helper.assertLinesCount(2);\n            [0, 1].map(i => helper.assertLineQty(i, [\"0/3\", \"2/2\"][i]));\n            [0, 1].map(i => helper.assertLineDestinationLocation(i, '.../Section 3'));\n        },\n    },\n    // Add product2 x3, completing the remaining line, then add to a pack, then reassign destination\n    {\n        trigger: '.o_barcode_line',\n        run: 'scan product2',\n    },\n    {\n        trigger: '.o_barcode_line',\n        run: 'scan product2',\n    },\n    {\n        trigger: '.o_barcode_line',\n        run: 'scan product2',\n    },\n    {\n        trigger: '.o_barcode_line.o_selected.o_line_completed',\n        run: 'scan THEPACK2',\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .result-package:contains(\"THEPACK2\")',\n        run: 'scan shelf4',\n    },\n    {\n        trigger: '.o_barcode_line .o_line_destination_location:contains(\"Section 4\")',\n        run: function () {\n            helper.assertValidateVisible(true);\n            helper.assertValidateIsHighlighted(true);\n            helper.assertValidateEnabled(true);\n            // Check that lines' quantity didn't change.\n            helper.assertLinesCount(2);\n            const lines = helper.getLines({ barcode: 'product2' });\n            [0, 1].map(i => helper.assert(lines[i].querySelector('.result-package').innerText, [\"THEPACK2\",\"THEPACK1\"][i]));\n            [0, 1].map(i => helper.assertLineQty(lines[i], [\"3/3\", \"2/2\"][i]));\n            [0, 1].map(i => helper.assertLineDestinationLocation(lines[i], [\".../Section 4\", \".../Section 3\"][i]));\n        },\n    },\n    {\n        trigger: '.btn.o_validate_page',\n        run: \"click\",\n    },\n    { trigger: '.o_notification_bar.bg-success'},\n]});\n\nregistry.category(\"web_tour.tours\").add('test_editing_done_picking', { steps: () => [\n        { trigger: '.o_barcode_client_action', run: 'scan OBTVALI' },\n        {\n            trigger: '.o_notification_bar.bg-danger',\n            run: function () {\n                helper.assertErrorMessage(\"This picking is already done\");\n            },\n        },\n    ]\n});\n\nregistry.category(\"web_tour.tours\").add(\"test_split_uncomplete_moves_on_exit\", {\n    steps: () => [\n        {\n            trigger: \".o_barcode_line\",\n            run: \"scan product2\",\n        },\n        {\n            trigger: \".o_barcode_line[data-barcode='product2'] .qty-done:contains('1')\",\n        },\n        {\n            trigger: \".o_edit .fa-pencil\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_field_widget[name='qty_done'] input\",\n        },\n        {\n            content: \"Exit the barcode app to look at look at back end data.\",\n            trigger: \".o_field_widget[name=product_id] > a\",\n            run: \"click\",\n        },\n        {\n            trigger: \".breadcrumb-item.o_back_button\",\n        },\n        {\n            content: \"Come back to the record in the barcode App.\",\n            trigger: \".breadcrumb-item.o_back_button\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_barcode_client_action\",\n            run: () => {\n                helper.assertLinesCount(2);\n                helper.assertLineQty(0, \"0/4\");\n                helper.assertLineQty(1, \"1/1\");\n            }\n        },\n]});\n\nregistry.category(\"web_tour.tours\").add(\"test_sml_sort_order_by_product_category\", {  steps: () => [\n    {\n        trigger: \".o_barcode_client_action\",\n        run: () => {\n            helper.assertLinesCount(3);\n            // Product B should be first because it belongs to category A.\n            const line1 = document.querySelector('.o_barcode_line:first-child');\n            helper.assertLineProduct(line1, \"Product B\");\n            // Product A should comes after Product B because of its category\n            // and before Product C because of its product's name.\n            const line2 = document.querySelector('.o_barcode_line:nth-child(2)');\n            helper.assertLineProduct(line2, \"Product A\");\n            // Product C should be last.\n            const line3 = document.querySelector('.o_barcode_line:last-child');\n            helper.assertLineProduct(line3, \"Product C\");\n        }\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_barcode_picking_return', { steps: () => [\n    {\n        trigger: \".o_barcode_client_action\",\n    },\n    {\n        trigger: \"span.o_scan_message:contains('This picking is already done')\",\n    },\n    // Press return\n    {\n        trigger: \"button.o_create_return\",\n        run: \"click\",\n    },\n    {\n        trigger: \"span.o_scan_message:contains('Scan a product')\",\n    },\n    {\n        trigger: '.o_barcode_line_title > div.o_product_label:contains(\"product2\")',\n    },\n    // Scan a product not in the original picking\n    {\n        trigger: \"span.o_scan_message:contains('Scan a product')\",\n        run: \"scan product1\",\n    },\n    ...stepUtils.confirmAddingUnreservedProduct(),\n    {\n        trigger: '.o_barcode_line_title > div.o_product_label:contains(\"product1\")',\n    },\n    // Press validate before signing the picking\n    {\n        trigger: \"button.o_validate_page\",\n        run: \"click\",\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_barcode_signature_flow', { steps: () => [\n    {\n        trigger: \"div.o_kanban_record_title > span:contains(Delivery Orders)\",\n        run: \"click\",\n    },\n    {\n        trigger: \"button > span:contains(Delivery Order 1)\",\n        run: \"click\",\n    },\n    // Press validate before signing the picking\n    {\n        trigger: \"button.o_validate_page\",\n        run: \"click\",\n    },\n    // Signature modal should be opened. Choose auto signature\n    {\n        trigger: \"a.o_web_sign_auto_button\",\n        run: \"click\",\n    },\n    // Sign the picking\n    {\n        trigger: \".modal-footer button.btn-primary:enabled\",\n        run: \"click\",\n    },\n    // The picking now should be validated automatically. Wait until the picking is validated\n    { trigger: \".o_kanban_tip_filter\" },\n    {\n        trigger: \"button > span:contains(Delivery Order 2)\",\n        run: \"click\",\n    },\n    // Open picking settings menu\n    {\n        trigger: \"button.o_barcode_actions\",\n        run: \"click\",\n    },\n    // Press sign button\n    {\n        trigger: \"button.o_sign\",\n        run: \"click\",\n    },\n    // Signature modal should be opened. Choose auto signature\n    {\n        trigger: \"a.o_web_sign_auto_button\",\n        run: \"click\",\n    },\n    // Sign the picking\n    {\n        trigger: \".modal-footer button.btn-primary:enabled\",\n        run: \"click\",\n    },\n    // Validate the picking\n    {\n        trigger: \"button.o_validate_page\",\n        run: \"click\",\n    },\n    // Wait until the picking is validated\n    { trigger: \".o_kanban_tip_filter\" },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_create_backorder_after_qty_modified', { steps: () => [\n        { trigger: '.o_edit', run: 'click' },\n        { trigger: '.o_digipad_increment', run: 'click' },\n        { trigger: '.o_save', run: 'click' },\n        { trigger: '.o_validate_page', run: 'click' },\n        { trigger: '.modal-dialog button.btn-primary', run: 'click' },\n    ]\n});\n\nregistry.category(\"web_tour.tours\").add('test_open_picking_dont_override_assigned_user', { steps: () => [\n    {\n        trigger: '.o_button_operations',\n        run: 'click',\n    },\n    {\n        trigger: '.o_kanban_record_title > span:contains(Receipts)',\n        run: 'click',\n    },\n    {\n        trigger: '.o_facet_value:contains(\"To Do\") + .o_facet_remove',\n        run: 'click',\n    },\n    {\n        trigger: '.btn > span:contains(\"test_responsible_receipt\")',\n        run: 'click',\n    },\n    {\n        trigger: '.o_exit',\n        run: 'click',\n    },\n    {\n        trigger: '.o_breadcrumb > ol > li > a:contains(Operations)',\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_serial_product_packaging', { steps: () => [\n    { trigger: \".o_stock_barcode_main_menu\", run: \"scan WHIN\" },\n    { trigger: '.o_barcode_client_action', run: \"scan PCK4\" },\n    {\n        trigger: '.o_barcode_line.o_highlight',\n        run: function() {\n            helper.assertLinesCount(1);\n            helper.assertScanMessage('scan_serial');\n            helper.assertLineProduct(0, \"productserial1\");\n            helper.assertLineQty(0, \"0/4\");\n            helper.assertButtonIsVisible(0, \"toggle_sublines\", false);\n            helper.assertButtonIsVisible(0, \"edit\");\n        }\n    },\n    { trigger: \".o_barcode_client_action\", run: \"scan sn1,sn2,sn3,sn4\" },\n    {\n        trigger: '.o_barcode_line.o_selected.o_line_completed .o_line_button.o_toggle_sublines',\n        run: 'click',\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .o_sublines',\n        run: function() {\n            const line = helper.getLine();\n            helper.assertLineQty(line, \"4/4\");\n            helper.assertSublinesCount(4);\n            const sublines = helper.getSublines();\n            helper.assertLinesTrackingNumbers(sublines, [\"sn4\", \"sn3\", \"sn2\", \"sn1\"]);\n            helper.assertLineQty(sublines[0], \"1/1\");\n            helper.assertLineQty(sublines[1], \"1/1\");\n            helper.assertLineQty(sublines[2], \"1/1\");\n            helper.assertLineQty(sublines[3], \"1/1\");\n        }\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_multi_company_record_access_in_barcode', { steps: () => [\n        { trigger: '.o_stock_barcode_main_menu', run: 'scan company2_receipt' },\n        // Shouldn't have access to company1 prod while in company2 picking type\n        { trigger: '.o_barcode_client_action', run: 'scan company1_product' },\n        {\n            trigger: '.o_notification_bar.bg-danger',\n            run: () => {\n                helper.assertErrorMessage('This product doesn\\'t exist.');\n            },\n        },\n        { trigger: '.o_barcode_client_action', run: 'scan company2_product' },\n        { trigger: '.o_barcode_line' },\n        { trigger: '.btn.o_validate_page', run: 'click' },\n        { trigger: '.o_stock_barcode_main_menu' },\n    ]\n});\n\nregistry.category(\"web_tour.tours\").add('test_no_zero_demand_new_line_from_split', { steps: () => [\n        { trigger: '.o_stock_barcode_main_menu', run: 'scan TNZDNLFS picking' },\n        { trigger: '.o_edit', run: 'click' },\n        { trigger: 'button.o_digipad_increment', run: 'click' },\n        { trigger: '.o_save', run: 'click' },\n        { trigger: '.o_barcode_line' },\n        { trigger: '.o_exit', run: 'click' },\n        { trigger: '.o_stock_barcode_main_menu', run: 'scan TNZDNLFS picking' },\n        { trigger: '.o_edit', run: 'click' },\n        { trigger: 'button.o_digipad_decrement', run: 'click' },\n        { trigger: '.o_save', run: 'click' },\n        { trigger: '.o_barcode_line' },\n        { trigger: '.o_exit', run: 'click' },\n        { trigger: '.o_stock_barcode_main_menu'},\n    ]\n});\n\nregistry.category(\"web_tour.tours\").add(\"test_barcode_pack_lot_tour\", {  steps: () => [\n    // Pack two units of the same not reserved lot in different packages\n    { trigger: '.o_barcode_line', run: \"scan LOT005\" },\n    { trigger: '.o_line_button.o_toggle_sublines', run: 'click'},\n    { trigger: 'span.o_line_lot_name:contains(LOT005)'},\n    { trigger: 'button.o_put_in_pack', run: 'click'},\n    { trigger: '.o_barcode_line:nth-child(2):has(.fa-archive)'},\n    { trigger: '.o_barcode_line_summary', run: 'click'},\n    { trigger: '.o_barcode_line_summary', run: \"scan LOT005\" },\n    { trigger: '.o_barcode_line.o_line_not_completed:contains(LOT005):not(:has(.fa-archive))'},\n    { trigger: 'button.o_put_in_pack', run: 'click'},\n    // Pack two units of the same reserved lot in different packages\n    { trigger: '.o_barcode_line:nth-child(3):has(.fa-archive)'},\n    { trigger: '.o_barcode_line_summary', run: \"scan LOT004\"},\n    { trigger: '.o_barcode_line_summary span.qty-done:contains(3)'},\n    { trigger: 'button.o_put_in_pack', run: 'click'},\n    { trigger: '.o_barcode_line:nth-child(4):has(.fa-archive)'},\n    { trigger: '.o_barcode_line_summary', run: 'click'},\n    { trigger: '.o_barcode_line_summary', run: \"scan LOT004\" },\n    { trigger: '.o_barcode_line_summary span.qty-done:contains(4)'},\n    { trigger: 'button.o_put_in_pack', run: 'click'},\n    { trigger: '.o_barcode_line:nth-child(1):has(.fa-archive)'},\n    { trigger:  '.btn.o_validate_page', run: 'click'},\n    { trigger: '.o_notification_bar.bg-success'},\n]});\n\nregistry.category(\"web_tour.tours\").add('test_scan_location_destination_for_internal_transfers', {\n    steps: () => [\n        {\n            trigger: \".o_button_operations\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_barcode_picking_type:has(.o_kanban_record_title:contains('Internal Transfers'))\",\n            run: \"click\",\n        },\n        {\n            trigger: \"button.o-kanban-button-new\",\n        },\n        {\n            trigger: \"button.o-kanban-button-new\",\n            run: \"click\",\n        },\n        {\n            trigger: \"button.o_add_line:contains('Add Product')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_field_widget[name='product_id'] input.o_input\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_field_widget[name='product_id'] input.o_input\",\n            run: \"edit Lovely Product\",\n        },\n        {\n            trigger: \".dropdown-item:contains('Lovely Product')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_save\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_scan_message.o_scan_product_or_dest\",\n        },\n        {\n            trigger: \".o_barcode_line\",\n            run: \"scan WH-LOVE\",\n        },\n        {\n            trigger: \"div[name='destination_location']:contains(Lovely)\",\n        },\n        {\n            trigger: '.o_validate_page',\n            run: \"click\",\n        },\n        {\n            trigger: '.o_notification_bar.bg-success',\n        },\n]});\n", "/** @odoo-module **/\n\nimport * as helper from './tour_helper_stock_barcode';\nimport { registry } from \"@web/core/registry\";\nimport { stepUtils } from \"./tour_step_utils\";\n\n// Inventory Tests.\nregistry.category(\"web_tour.tours\").add('test_gs1_inventory_gtin_8', { steps: () => [\n    {\n        trigger: '.o_button_inventory',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 0100000082655853', // (01)00000082655853 -> GTIN-8 -> product barcode == 8265585\n    },\n    {\n        trigger: '.o_barcode_client_action:contains(\"PRO_GTIN_8\")',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ barcode: \"82655853\" });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, \"1\");\n        }\n    },\n    {\n        trigger: '.o_barcode_client_action:contains(\"PRO_GTIN_8\")',\n        run: 'scan 3777', // (37)77 -> add 77 Units to the selected product (the last one)\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"78\")',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ barcode: \"82655853\" });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, \"78\");\n        }\n    },\n    ...stepUtils.validateBarcodeOperation('.o_barcode_line .qty-done:contains(\"78\")'),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_gs1_inventory_product_units', { steps: () => [\n    {\n        trigger: '.o_button_inventory',\n        run: \"click\",\n    },\n    // The following scanned barcode should be decomposed like that:\n    //      - (01)00000082655853    > product barcode (8265585)\n    //      - (37)102               > units (102)\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 010000008265585337102',\n    },\n    {\n        trigger: '.o_barcode_client_action:contains(\"PRO_GTIN_8\")',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"102\")',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ barcode: \"82655853\" });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, \"102\");\n        }\n    },\n    ...stepUtils.validateBarcodeOperation(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_gs1_inventory_lot_serial', { steps: () => [\n    {\n        trigger: '.o_button_inventory',\n        run: \"click\",\n    },\n    // The following scanned barcode should be decomposed like that:\n    //      - (01)00111155555717    > tracked by lot product barcode (111155555717)\n    //      - (10)LOT-AAA           > lot (LOT-AAA)\n    //      - (30)5                 > quantity (5)\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 010011115555571710LOT-AAA~305',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"AAA\")',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ selected: true });\n            helper.assertLineProduct(line, \"PRO_GTIN_12_lot\");\n            helper.assertLineTrackingNumber(line, \"LOT-AAA\");\n            helper.assertLineQty(line, \"5\");\n        }\n    },\n    // Scans product + lot, and then scans the quantity after the line's creation.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 010011115555571710LOT-AAB',\n    },\n    {\n        trigger: '.o_sublines .o_barcode_line:contains(\"AAB\")',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_sublines .o_barcode_line.o_selected:contains(\"AAB\")',\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertSublinesCount(2);\n            const parentLine = helper.getLine({ selected: true });\n            const subline = helper.getSubline({ selected: true });\n            helper.assertLineProduct(parentLine, \"PRO_GTIN_12_lot\");\n            helper.assertLineQty(parentLine, \"6\");\n            helper.assertLineTrackingNumber(subline, \"LOT-AAB\");\n            helper.assertLineQty(subline, \"1\");\n        }\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 30009',\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .qty-done:contains(\"10\")',\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertSublinesCount(2);\n            const parentLine = helper.getLine({ selected: true });\n            const subline = helper.getSubline({ selected: true });\n            helper.assertLineProduct(parentLine, \"PRO_GTIN_12_lot\");\n            helper.assertLineQty(parentLine, \"15\");\n            helper.assertLineTrackingNumber(subline, \"LOT-AAB\");\n            helper.assertLineQty(subline, \"10\");\n        }\n    },\n    // Scans a second time a quantity (should increment the current line).\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 305',\n    },\n    {\n        trigger: '.o_sublines .o_barcode_line.o_selected .qty-done:contains(\"15\")',\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertSublinesCount(2);\n            const parentLine = helper.getLine({ selected: true });\n            const subline = helper.getSubline({ selected: true });\n            helper.assertLineProduct(parentLine, \"PRO_GTIN_12_lot\");\n            helper.assertLineQty(parentLine, \"20\");\n            helper.assertLineTrackingNumber(subline, \"LOT-AAB\");\n            helper.assertLineQty(subline, \"15\");\n        }\n    },\n    // Scans a lot + quantity (should get back the product from the previous\n    // line and create a new line).\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 3020##10LOT-AAC',\n    },\n    {\n        trigger: '.o_barcode_line.o_selected:contains(\"AAC\")',\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertSublinesCount(3);\n            const parentLine = helper.getLine({ selected: true });\n            const subline = helper.getSubline({ selected: true });\n            helper.assertLineProduct(parentLine, \"PRO_GTIN_12_lot\");\n            helper.assertLineQty(parentLine, \"40\");\n            helper.assertLineTrackingNumber(subline, \"LOT-AAC\");\n            helper.assertLineQty(subline, \"20\");\n        }\n    },\n    // Scans manually (with a custom separator) lot + quantity but with a lot already scanned,\n    // so it should increment the quantity on the line with this lot.\n    ...stepUtils.inputManuallyBarcode(\"305Alt02910LOT-AAA\"),\n    {\n        trigger: '.o_barcode_line.o_selected .qty-done:contains(\"10\")',\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertSublinesCount(3);\n            const parentLine = helper.getLine({ selected: true });\n            const subline = helper.getSubline({ selected: true });\n            helper.assertLineProduct(parentLine, \"PRO_GTIN_12_lot\");\n            helper.assertLineQty(parentLine, \"45\");\n            helper.assertLineTrackingNumber(subline, \"LOT-AAA\");\n            helper.assertLineQty(subline, \"10\");\n        }\n    },\n\n    // The following scanned barcode should be decomposed like that:\n    //      - (01)15222222222219    > tracked by SN product barcode (15222222222219)\n    //      - (21)Serial1           > serial number (Serial1)\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 011522222222221921Serial1',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"Serial1\")',\n        run: function () {\n            helper.assertLinesCount(2);\n            helper.assertSublinesCount(0);\n            const line = helper.getLine({ selected: true });\n            helper.assertLineProduct(line, \"PRO_GTIN_14_serial\");\n            helper.assertLineTrackingNumber(line, \"Serial1\");\n            helper.assertLineQty(line, \"1\");\n        }\n    },\n    // The following scanned barcode should be decomposed like that:\n    //      - (01)15222222222219    > tracked by SN product barcode (15222222222219)\n    //      - (21)Serial2           > serial number (Serial2)\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 011522222222221921Serial2',\n    },\n    {\n        trigger: '.o_sublines .o_barcode_line:contains(\"Serial2\")',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"Serial2\")',\n        run: function () {\n            helper.assertLinesCount(2);\n            helper.assertSublinesCount(2);\n        }\n    },\n    // Tries to scan multiple quantities for product tracked by SN, should set\n    // the inventory qty. to 1 instead, and display a notification.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 011522222222221921Serial3\\x1D3020',\n    },\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"Serial3\")',\n        run: function () {\n            helper.assertLinesCount(2);\n            helper.assertSublinesCount(3);\n            const parentLine = document.querySelector('.o_barcode_line.o_selected');\n            const subline = document.querySelector('.o_sublines .o_barcode_line.o_selected');\n            helper.assertLineQty(parentLine, \"3\");\n            helper.assertLineTrackingNumber(subline, \"Serial3\");\n            helper.assertLineQty(subline, \"1\");\n        }\n    },\n    // Tries to scan multiple quantities for product tracked by SN but without\n    // a SN, should set the inventory qty. to the scanned one (20).\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 01152222222222193020',\n    },\n    {\n        trigger: '.o_sublines .o_barcode_line:nth-child(4)',\n        run: function () {\n            helper.assertLinesCount(2);\n            helper.assertSublinesCount(4);\n            const parentLine = helper.getLine({ selected: true });\n            const subline = helper.getSubline({ selected: true });\n            helper.assertLineQty(parentLine, \"23\");\n            helper.assertLineTrackingNumber(subline, false);\n            helper.assertLineQty(subline, \"20\");\n            const barcodeIconVisible = Boolean(subline.querySelector(\".o_line_tracking_number\"));\n            helper.assert(barcodeIconVisible, false, \"The SN HTML element should not be visible\");\n        }\n    },\n    // Scans a serial number, it should not write it on the previous line (as\n    // multiple quantitieswas scanned) and create a new one instead.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 011522222222221921Serial4',\n    },\n    {\n        trigger: '.o_sublines .o_barcode_line:nth-child(5)',\n        run: function () {\n            helper.assertLinesCount(2);\n            helper.assertSublinesCount(5);\n            const parentLine = helper.getLine({ selected: true });\n            const subline = helper.getSubline({ selected: true });\n            helper.assertLineQty(parentLine, \"24\");\n            helper.assertLineTrackingNumber(subline, \"Serial4\");\n            helper.assertLineQty(subline, \"1\");\n        }\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan OBTVALI',\n    },\n    // Ask for confirmation when validate because of quantities for tracked product without SN.\n    {\n        trigger: '.modal-content:contains(\"Tracked Products in Inventory Adjustment\")',\n        run: \"click\",\n    },\n    {\n        trigger: '.modal-content .btn[name=action_confirm]',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_notification_bar.bg-success',\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_gs1_inventory_package', { steps: () => [\n    {\n        trigger: '.o_button_inventory',\n        run: \"click\",\n    },\n    // Scans the package in Section 1 => Should raise a warning.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 00987654123487568456',\n    },\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: \"click\",\n    },\n    // Changes location for Section 1 and scans again the package.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-01-00',\n    },\n    {\n        trigger: '.o_scan_product_or_src:contains(\"WH/Stock/Section 1\")',\n        run: 'scan 00987654123487568456',\n    },\n    {\n        trigger: '.o_barcode_line',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ barcode: \"product1\" });\n            const product1_package = line.querySelector('div[name=\"package\"]').innerText;\n            helper.assert(product1_package, '987654123487568456');\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, \"8/8\");\n        },\n    },\n\n    // Changes location for Section 2 and scans the second package.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-02-00',\n    },\n    {\n        trigger: '.o_scan_product_or_src:contains(\"WH/Stock/Section 2\")',\n        run: 'scan 00487325612456785124',\n    },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"product2\"]',\n        run: function () {\n            helper.assertLinesCount(2);\n            const line = helper.getLine({ barcode: \"product2\" });\n            const product2_package = line.querySelector('div[name=\"package\"]').innerText;\n            helper.assert(product2_package, '487325612456785124');\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, \"6/6\");\n        },\n    },\n    // Tries to scan the same package => Should raise a warning.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 00487325612456785124',\n    },\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: \"click\",\n    },\n\n    // Scans additionnal products and put them in a new pack by scanning a non-existing package barcode.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 0100000082655853304',\n    },\n    {\n        trigger: '.o_barcode_line:nth-child(2)',\n        run: 'scan 00122333444455555670',\n    },\n    {\n        trigger: '.o_barcode_line:contains(122333444455555670)',\n        run: function () {\n            helper.assertLinesCount(3);\n            const line1 = helper.getLine({ barcode: \"product2\" });\n            const line1_package = line1.querySelector('div[name=\"package\"]').innerText;\n            helper.assertLineIsHighlighted(line1, false);\n            helper.assert(line1_package, '487325612456785124');\n            helper.assertLineQty(line1, \"6/6\");\n            const line2 = helper.getLine({ barcode: \"82655853\" });\n            const line2_package = line2.querySelector('div[name=\"package\"]').innerText;\n            helper.assertLineIsHighlighted(line2, true);\n            helper.assert(line2_package, '122333444455555670');\n            helper.assertLineQty(line2, \"4\");\n        },\n    },\n\n    // Validates the inventory.\n    {\n        trigger: '.o_apply_page',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_notification_bar.bg-success',\n    }\n]});\n\n// Picking Tests.\n\nregistry.category(\"web_tour.tours\").add('test_gs1_package_receipt', { steps: () => [\n    { trigger: '.o_stock_barcode_main_menu', run: 'scan WHIN' },\n    // Scans PRO_GTIN_8 x4\n    { trigger: '.o_barcode_client_action', run: 'scan 0100000082655853300004' },\n    {\n        trigger: '.o_barcode_line',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line1 = helper.getLine({ barcode: \"82655853\" });\n            helper.assertLineIsHighlighted(line1, true);\n            helper.assertLineQty(line1, \"4\");\n        }\n    },\n    // Scans a package => As it doesn't exist in the DB, should put in pack the\n    // previously scanned quantities.\n    { trigger: '.o_barcode_client_action', run: 'scan 00546879213579461324' },\n    {\n        trigger: '.o_barcode_line:contains(546879213579461324)',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line1 = helper.getLine({ barcode: \"82655853\" });\n            helper.assertLineIsHighlighted(line1, true);\n            helper.assertLineQty(line1, \"4\");\n            const product1_package = line1.querySelector('[name=\"package\"]').innerText;\n            helper.assert(product1_package, '546879213579461324');\n        }\n    },\n    // Scans PRO_GTIN_12 x8.\n    { trigger: '.o_barcode_client_action', run: 'scan 300008\\x1D0100584687955629' },\n    {\n        trigger: '.o_barcode_line:nth-child(2)',\n        run: function () {\n            helper.assertLinesCount(2);\n            const line1 = helper.getLine({ barcode: \"82655853\" });\n            helper.assertLineIsHighlighted(line1, false);\n            helper.assertLineQty(line1, \"4\");\n            const product1_package = line1.querySelector('[name=\"package\"]').innerText;\n            helper.assert(product1_package, '546879213579461324');\n            const line2 = helper.getLine({ barcode: \"584687955629\" });\n            helper.assertLineIsHighlighted(line2, true);\n            helper.assertLineQty(line2, \"8\");\n        }\n    },\n    // Scans again the same package. Now it already exists but should be assigned anyway.\n    { trigger: '.o_barcode_client_action', run: 'scan 00546879213579461324' },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"584687955629\"]:contains(546879213579461324)',\n        run: function () {\n            helper.assertLinesCount(2);\n            const line1 = helper.getLine({ barcode: \"82655853\" });\n            helper.assertLineIsHighlighted(line1, false);\n            helper.assertLineQty(line1, \"4\");\n            const product1_package = line1.querySelector('[name=\"package\"]').innerText;\n            helper.assert(product1_package, '546879213579461324');\n            const line2 = helper.getLine({ barcode: \"584687955629\" });\n            helper.assertLineIsHighlighted(line2, true);\n            helper.assertLineQty(line2, \"8\");\n            const product2_package = line2.querySelector('[name=\"package\"]').innerText;\n            helper.assert(product2_package, '546879213579461324');\n        }\n    },\n    // Selects a line and scans a package type, it should be assing the package\n    // type to selected line's result package.\n    {\n        trigger: '.o_barcode_line[data-barcode=\"584687955629\"]',\n        run: \"click\",\n    },\n    { trigger: '.o_selected[data-barcode=\"584687955629\"]', run: 'scan 91WOODC' },\n    {\n        trigger: '.o_barcode_line[data-barcode=\"584687955629\"]:contains(\"(Wooden Chest)\")',\n        run: function () {\n            helper.assertLinesCount(2);\n            const line1 = helper.getLine({ barcode: \"82655853\" });\n            helper.assertLineIsHighlighted(line1, false);\n            helper.assertLineQty(line1, \"4\");\n            const product1_package = line1.querySelector('[name=\"package\"]').innerText;\n            helper.assert(product1_package, '546879213579461324 (Wooden Chest)');\n            const line2 = helper.getLine({ barcode: \"584687955629\" });\n            helper.assertLineIsHighlighted(line2, true);\n            helper.assertLineQty(line2, \"8\");\n            const product2_package = line2.querySelector('[name=\"package\"]').innerText;\n            helper.assert(product2_package, '546879213579461324 (Wooden Chest)');\n        }\n    },\n\n    // Scans PRO_GTIN_8 x6\n    { trigger: '.o_barcode_client_action', run: 'scan 0100000082655853300006' },\n    {\n        trigger: '.o_barcode_line.o_selected:contains(\"PRO_GTIN_8\")',\n        run: function () {\n            helper.assertLinesCount(3);\n            const line = helper.getLine({ selected: true });\n            helper.assertLineQty(line, \"6\");\n        }\n    },\n    // Scans a package with a type => put in pack the selected line in this package with the type.\n    { trigger: '.o_barcode_client_action', run: 'scan 00130406658041178543\\x1D91IRONC' },\n    {\n        trigger: '.o_barcode_line.o_selected:contains(\"130406658041178543\")',\n        run: function () {\n            helper.assertLinesCount(3);\n            const line = helper.getLine({ selected: true });\n            helper.assertLineQty(line, \"6\");\n            const linePackage = line.querySelector('[name=\"package\"]').innerText;\n            helper.assert(linePackage, '130406658041178543 (Iron Chest)');\n        }\n    },\n    // Scans PRO_GTIN_12 x12, then scans a package type to put in pack in a new package.\n    { trigger: '.o_barcode_client_action', run: 'scan 30000000120100584687955629' },\n    {\n        trigger: '.o_barcode_line.o_selected[data-barcode=\"584687955629\"]',\n        run: function () {\n            helper.assertLinesCount(4);\n            const line = helper.getLine({ selected: true });\n            helper.assertLineQty(line, \"12\");\n        }\n    },\n    { trigger: '.o_barcode_client_action', run: 'scan 91WOODC' },\n    {\n        trigger: '.o_barcode_line.o_selected[data-barcode=\"584687955629\"] [name=\"package\"]',\n        run: function () {\n            helper.assertLinesCount(4);\n            const line = helper.getLine({ selected: true });\n            helper.assertLineQty(line, \"12\");\n            const linePackage = line.querySelector('[name=\"package\"]').innerText;\n            helper.assert(linePackage, 'PACK0000123 (Wooden Chest)');\n        }\n    },\n    // Scan another package type => Should change the package's type.\n    { trigger: '.o_barcode_client_action', run: 'scan 91IRONC' },\n    {\n        trigger: '.o_selected[data-barcode=\"584687955629\"] [name=\"package\"]:contains(\"Iron Chest\")',\n        run: function () {\n            helper.assertLinesCount(4);\n            const line = helper.getLine({ selected: true });\n            helper.assertLineQty(line, \"12\");\n            const linePackage = line.querySelector('[name=\"package\"]').innerText;\n            helper.assert(linePackage, 'PACK0000123 (Iron Chest)');\n        }\n    },\n    ...stepUtils.validateBarcodeOperation(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_gs1_package_delivery', { steps: () => [\n    {\n        trigger: '.o_stock_barcode_main_menu',\n        run: 'scan WHOUT',\n    },\n    // Scans the package\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 00546879213579461324',\n    },\n    {\n        trigger: '.o_barcode_line:nth-child(2)',\n        run: function () {\n            helper.assertLinesCount(2);\n            const line1 = helper.getLine({ barcode: \"82655853\" });\n            helper.assertLineIsHighlighted(line1, true);\n            helper.assertLineQty(line1, \"4\");\n            helper.assert(line1.querySelector('.package').innerText, '546879213579461324');\n            helper.assert(line1.querySelector('.result-package').innerText, '546879213579461324');\n            const line2 = helper.getLine({ barcode: \"584687955629\" });\n            helper.assertLineIsHighlighted(line2, true);\n            helper.assertLineQty(line2, \"8\");\n            helper.assert(line2.querySelector('.package').innerText, '546879213579461324');\n            helper.assert(line2.querySelector('.result-package').innerText, '546879213579461324');\n        }\n    },\n    ...stepUtils.validateBarcodeOperation(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_gs1_reserved_delivery', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ barcode: \"11011019\" });\n            helper.assertLineIsHighlighted(line, false);\n            helper.assertLineQty(line, \"0/10\");\n            helper.assertValidateIsHighlighted(false);\n        }\n    },\n    // Scans 6 qty. of PRO_GTIN_8 (waiting for 10).\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 0100000011011019306',\n    },\n    {\n        trigger: '.o_barcode_line .qty-done:contains(6)',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ barcode: \"11011019\" });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, \"6/10\");\n            helper.assertValidateIsHighlighted(false);\n        }\n    },\n    // Scans 8 additional qty. of PRO_GTIN_8 (waiting for 10, so we have 4 extra).\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 0100000011011019308',\n    },\n    {\n        trigger: '.o_barcode_line  .qty-done:contains(10)',\n        run: function () {\n            helper.assertLinesCount(2);\n            const [line1, line2] = helper.getLines();\n            helper.assertLineIsHighlighted(line1, false);\n            helper.assertLineIsHighlighted(line2, true);\n            helper.assertLineQty(line1, \"10/10\");\n            helper.assertLineQty(line2, \"4\");\n            helper.assertValidateIsHighlighted(true);\n        }\n    },\n    // Validates the transfer.\n    ...stepUtils.validateBarcodeOperation(\".o_validate_page.btn-primary\"),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_gs1_receipt_conflicting_barcodes_1', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ barcode: \"11011019\"});\n            helper.assertLineIsHighlighted(line, false);\n            helper.assertLineQty(line, \"0/1\");\n            helper.assertValidateIsHighlighted(false);\n        }\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 0100000011011019', // (01)00000011011019 product barcode (11011019)\n    },\n    {\n        trigger: '.o_barcode_line.o_selected',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ barcode: \"11011019\"});\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, \"1/1\");\n            helper.assertValidateIsHighlighted(true);\n        }\n    },\n    ...stepUtils.validateBarcodeOperation(\".o_validate_page.btn-primary\"),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_gs1_delivery_ambiguous_lot_number', { steps: () => [\n    // Create a delivery.\n    { trigger: '.o_stock_barcode_main_menu', run: 'scan WHOUT' },\n    // Scan the GS1 barcode (contains product + lot)\n    {\n        trigger: '.o_scan_message.o_scan_product',\n        run: 'scan 0100000022222220152407101012345',\n    },\n    {\n        trigger: '.o_barcode_line .qty-done:contains(1)',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ barcode: \"22222220\" });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, \"1\");\n            helper.assertLineTrackingNumber(line, \"12345\");\n        }\n    },\n\n    // Cancel the transfer and do the same but with packaging barcode instead.\n    { trigger: '.o_barcode_client_action', run: 'scan OCDCANC' },\n    { trigger: '.o_stock_barcode_main_menu', run: 'scan WHIN' },\n    // Scan the GS1 barcode (contains product packaging + lot)\n    {\n        trigger: '.o_scan_message.o_scan_product',\n        run: 'scan 0110000000240489152407101012345',\n    },\n    {\n        trigger: '.o_barcode_line .qty-done:contains(1)',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ barcode: \"22222220\" });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, \"1\");\n            helper.assertLineTrackingNumber(line, \"12345\");\n        }\n    },\n    ...stepUtils.validateBarcodeOperation(\".o_validate_page.btn-primary\"),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_gs1_delivery_ambiguous_serial_number', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan LOC-01-00-00',\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 01057115440019521524071021304',\n    },\n    {\n        trigger: '.o_barcode_line .qty-done:contains(1)',\n        run: function () {\n            helper.assertLinesCount(1);\n            const $line = helper.getLine({barcode: '05711544001952'});\n            helper.assertLineIsHighlighted($line, true);\n            helper.assertLineQty($line, \"1/1\");\n        }\n    },\n    // Validates the transfer.\n    ...stepUtils.validateBarcodeOperation(\".o_validate_page.btn-primary\"),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_gs1_receipt_conflicting_barcodes_2', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ barcode: \"000011011019\" });\n            helper.assertLineIsHighlighted(line, false);\n            helper.assertLineQty(line, \"0/1\");\n            helper.assertValidateIsHighlighted(false);\n        }\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 0100000011011019', // (01)00000011011019 product barcode (000011011019)\n    },\n    {\n        trigger: '.o_barcode_line.o_selected',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ barcode: \"000011011019\" });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, \"1/1\");\n            helper.assertValidateIsHighlighted(true);\n        }\n    },\n    ...stepUtils.validateBarcodeOperation(\".o_validate_page.btn-primary\"),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_gs1_receipt_conflicting_barcodes_3', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function () {\n            helper.assertLinesCount(2);\n            const line1 = helper.getLine({ barcode: \"11011019\" });\n            helper.assertLineIsHighlighted(line1, false);\n            helper.assertLineQty(line1, \"0/1\");\n            helper.assertLineProduct(line1, \"PRO_GTIN_8\");\n            const line2 = helper.getLine({ barcode: \"000011011019\" });\n            helper.assertLineIsHighlighted(line2, false);\n            helper.assertLineQty(line2, \"0/1\");\n            helper.assertLineProduct(line2, \"PRO_GTIN_12\");\n            helper.assertValidateIsHighlighted(false);\n        }\n    },\n    // Scans '0100000011011019', should get 'PRO_GTIN_8' as this is the product put in the lazy cache.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 0100000011011019',\n    },\n    {\n        trigger: '.o_barcode_line.o_selected',\n        run: function () {\n            helper.assertLinesCount(2);\n            const line1 = helper.getLine({ barcode: \"11011019\" });\n            helper.assertLineIsHighlighted(line1, true);\n            helper.assertLineQty(line1, \"1/1\");\n            helper.assertLineProduct(line1, \"PRO_GTIN_8\");\n            const line2 = helper.getLine({ barcode: \"000011011019\" });\n            helper.assertLineIsHighlighted(line2, false);\n            helper.assertLineQty(line2, \"0/1\");\n            helper.assertLineProduct(line2, \"PRO_GTIN_12\");\n            helper.assertValidateIsHighlighted(false);\n        }\n    },\n    // Scans a second time '0100000011011019', should create a new line for PRO_GTIN_8.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 0100000011011019',\n    },\n    {\n        trigger: '.o_barcode_line:nth-child(3)',\n        run: function () {\n            helper.assertLinesCount(3);\n            const [lineGTIN12, lineGTIN8_1, lineGTIN8_2] = helper.getLines();\n            helper.assertLineIsHighlighted(lineGTIN12, false);\n            helper.assertLineQty(lineGTIN12, \"0/1\");\n            helper.assertLineProduct(lineGTIN12, 'PRO_GTIN_12');\n            helper.assertLineIsHighlighted(lineGTIN8_1, false);\n            helper.assertLineQty(lineGTIN8_1, \"1/1\");\n            helper.assertLineProduct(lineGTIN8_1, 'PRO_GTIN_8');\n            helper.assertLineIsHighlighted(lineGTIN8_2, true);\n            helper.assertLineQty(lineGTIN8_2, \"1\");\n            helper.assertLineProduct(lineGTIN8_2, 'PRO_GTIN_8');\n            helper.assertValidateIsHighlighted(false);\n        }\n    },\n    // Scans the PRO_GTIN_12 (non-GS1) barcode: '000011011019'.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 000011011019',\n    },\n    {\n        trigger: '.o_validate_page.btn-primary',\n        run: function () {\n            helper.assertLinesCount(3);\n            const [line1, line2] = helper.getLines({ barcode: \"11011019\" });\n            helper.assertLineIsHighlighted(line1, false);\n            helper.assertLineQty(line1, \"1/1\");\n            helper.assertLineProduct(line1, \"PRO_GTIN_8\");\n            helper.assertLineIsHighlighted(line2, false);\n            helper.assertLineQty(line2, \"1\");\n            helper.assertLineProduct(line2, \"PRO_GTIN_8\");\n            const line3 = helper.getLine({ barcode: \"000011011019\" });\n            helper.assertLineIsHighlighted(line3, true);\n            helper.assertLineQty(line3, \"1/1\");\n            helper.assertLineProduct(line3, \"PRO_GTIN_12\");\n            helper.assertValidateIsHighlighted(true);\n        }\n    },\n    ...stepUtils.validateBarcodeOperation(\".o_validate_page.btn-primary\"),\n]});\n\nregistry.category(\"web_tour.tours\").add(\"test_gs1_receipt_conflicting_barcodes_mistaken_as_gs1\", { steps: () => [\n    // Creates a new receipt.\n    { trigger: \".o_stock_barcode_main_menu\", run: \"scan WHIN\" },\n    // Scans 3000000015 -> Will be parsed as 15 units by the GS1 nomenclature\n    // but since we don't expect a quantity here (no line yet), it should also\n    // check if this barcode matches something else (by-passing the nomenclature.)\n    { trigger: \".o_barcode_client_action\", run: \"scan 3000000015\" },\n    {\n        trigger: \".o_barcode_line\",\n        run: function() {\n            const line = helper.getLine({ barcode: \"3000000015\" });\n            helper.assertLineQty(line, \"1\");\n        },\n    },\n    // Scans 21000000000003 -> Will be parsed as a serial number by the GS1 nomenclature but since\n    // it's irrelevant to scan a SN here (previous scanned product is not tracked), it should also\n    // check if it's something else barcode (by-passing the nomenclature.)\n    { trigger: \".o_barcode_client_action\", run: \"scan 21000000000003\" },\n    {\n        trigger: \".o_barcode_line[data-barcode='21000000000003']\",\n        run: function() {\n            const line = helper.getLine({ barcode: \"21000000000003\" });\n            helper.assertLineQty(line, \"1\");\n        },\n    },\n    // Scans again 3000000015 but since it could genuinely be a quantity, we have no way to find if\n    // it's really a GS1 or something else and use it as it was parsed.\n    { trigger: \".o_barcode_client_action\", run: \"scan 3000000015\" },\n    {\n        trigger: \".o_barcode_line.o_selected .qty-done:contains('16')\",\n        run: function() {\n            const line = helper.getLine({ barcode: \"21000000000003\" });\n            // The qty of the selected product should be incremented by 15.\n            helper.assertLineQty(line, \"16\");\n        },\n    },\n    // Scans 21-Chouette-MegaPack whom can be interpreted as a lot (AI 21, lot name:\n    // \"-Chouette-MegaPack\") but who is actually an existing package.\n    { trigger: \".o_barcode_client_action\", run: \"scan 21-Chouette-MegaPack\" },\n    {\n        trigger: \".o_barcode_line[data-barcode='21000000000003'] div[name='package']\",\n        run: function() {\n            const line = helper.getLine({ barcode: \"21000000000003\" });\n            helper.assertLineQty(line, \"16\");\n            helper.assert(line.querySelector(\".result-package\").innerText, \"21-Chouette-MegaPack\");\n        },\n    },\n    // Now, scans a product tracked by SN and ensures the barcode starting by 21 are indeed\n    // interpreted as serial number once a tracked product's waiting a SN.\n    { trigger: \".o_barcode_client_action\", run: \"scan productserial1\" },\n    { trigger: \".o_barcode_line[data-barcode='productserial1']\", run: \"scan 21-Chouette-MegaPack\" },\n    { trigger: \".o_barcode_line.o_selected .o_line_lot_name:contains('-Chouette-MegaPack')\" },\n    { trigger: \".o_barcode_client_action\", run: \"scan 21000000000003\" },\n    {\n        trigger: \".o_barcode_line.o_selected .o_line_button.o_toggle_sublines\",\n        run: \"click\",\n    },\n    {\n        trigger: \".o_sublines .o_barcode_line.o_selected\",\n        run: function() {\n            helper.assert(document.querySelector(\".o_barcode_line_summary .qty-done\").innerText, \"2\");\n            helper.assert(\n                document.querySelector(\".o_sublines .o_selected .o_line_lot_name\").innerText,\n                \"000000000003\"\n            );\n        },\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_gs1_receipt_lot_serial', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertLineIsHighlighted(0, false);\n            helper.assertLineQty(0, \"0/40\");\n        }\n    },\n    // The following scanned barcode should be decomposed like that:\n    //      - (01)00000076543210    > product barcode (76543210)\n    //      - (30)00000008          > quantity (8)\n    ...stepUtils.inputManuallyBarcode(\"((01)00000076543210(30)00000008)\"),\n\n    {\n        trigger: '.o_barcode_line.o_selected',\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertLineIsHighlighted(0, true);\n            helper.assertLineQty(0, \"8/40\");\n        }\n    },\n    // Scans the lot: as the line has already done quantity but no lot,\n    // it should apply the lot without increase the quantity.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 10b1-b001',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"b1-b001\")',\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertLineTrackingNumber(0, \"b1-b001\");\n            helper.assertLineQty(0, \"8/40\");\n        }\n    },\n    // Scan the product, lot and quantity all at once:\n    //      - (01)00000076543210    > product barcode (76543210)\n    //      - (10)b1-b002           > lot (b1-b002)\n    //      - (30)00000004          > quantity (4)\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 010000007654321010b1-b002\\x1D3000000004',\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .btn.o_toggle_sublines .fa-angle-down',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"b1-b002\")',\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertSublinesCount(2);\n            const parentLine = helper.getLine({ barcode: '76543210' });\n            const [line1, line2] = helper.getSublines();\n            helper.assertLinesTrackingNumbers([line1, line2], [\"b1-b001\", \"b1-b002\"]);\n            helper.assertLineQty(parentLine, \"12/40\");\n            helper.assertLineQty(line1, \"8/40\");\n            helper.assertLineQty(line2, \"4\");\n            helper.assertLineIsHighlighted(line1, false);\n            helper.assertLineIsHighlighted(line2, true);\n        }\n    },\n    {\n        trigger: '.o_sublines .o_barcode_line:nth-child(2).o_selected',\n        run: 'scan 010000007654321010b1-b002\\x1D3000000004',\n    },\n    {\n        trigger: '.o_sublines .o_barcode_line.o_selected .qty-done:contains(\"8\")',\n        run: function () {\n            helper.assertSublinesCount(2);\n            const [line1, line2] = helper.getSublines();\n            helper.assertLinesTrackingNumbers([line1, line2], [\"b1-b001\", \"b1-b002\"]);\n            helper.assertLineQty(line1, \"8/40\");\n            helper.assertLineQty(line2, \"8\");\n            helper.assertLineIsHighlighted(line1, false);\n            helper.assertLineIsHighlighted(line2, true);\n        }\n    },\n    // Scans a non-GS1 lot barcode to be sure it's compatible.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan b1-b003',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"b1-b003\")',\n        run: function () {\n            helper.assertSublinesCount(3);\n            const sublines = helper.getSublines();\n            helper.assertLinesTrackingNumbers(sublines, [\"b1-b001\", \"b1-b002\", \"b1-b003\"]);\n            helper.assertLineQty(sublines[0], \"8/40\");\n            helper.assertLineQty(sublines[1], \"8\");\n            helper.assertLineQty(sublines[2], \"1\");\n            helper.assertLineIsHighlighted(sublines[0], false);\n            helper.assertLineIsHighlighted(sublines[1], false);\n            helper.assertLineIsHighlighted(sublines[2], true);\n        }\n    },\n    // Scan two more time the previous barcode...\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan b1-b003',\n    },\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan b1-b003',\n    },\n    {\n        trigger: '.o_barcode_line.o_selected .qty-done:contains(\"3\")',\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertSublinesCount(3);\n            const sublines = helper.getSublines();\n            helper.assertLinesTrackingNumbers(sublines, [\"b1-b001\", \"b1-b002\", \"b1-b003\"]);\n            helper.assertLineQty(sublines[0], \"8/40\");\n            helper.assertLineQty(sublines[1], \"8\");\n            helper.assertLineQty(sublines[2], \"3\");\n            helper.assertLineIsHighlighted(sublines[0], false);\n            helper.assertLineIsHighlighted(sublines[1], false);\n            helper.assertLineIsHighlighted(sublines[2], true);\n        }\n    },\n    // ... then scan a GS1 barcode to add 5 more qty., without lot reference,\n    // to be sure the qty. will go on the last selected line.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 3000000005',\n    },\n    {\n        trigger: '.o_sublines .o_barcode_line.o_selected:contains(\"8\")',\n        run: function () {\n            helper.assertSublinesCount(3);\n            const sublines = helper.getSublines();\n            helper.assertLinesTrackingNumbers(sublines, [\"b1-b001\", \"b1-b002\", \"b1-b003\"]);\n            helper.assertLineQty(sublines[0], \"8/40\");\n            helper.assertLineQty(sublines[1], \"8\");\n            helper.assertLineQty(sublines[2], \"8\");\n            helper.assertLineIsHighlighted(sublines[0], false);\n            helper.assertLineIsHighlighted(sublines[1], false);\n            helper.assertLineIsHighlighted(sublines[2], true);\n        }\n    },\n    // Adds a line with the \"Add Product\" button, then scans the lot/qty.\n    {\n        trigger: '.o_add_line',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_field_widget[name=product_id] input',\n        run: \"edit B1\",\n    },\n    {\n        trigger: \".ui-menu-item > a:contains('Battle Droid')\",\n        run: \"click\",\n    },\n    {\n        trigger: '[name=qty_done] input',\n        run: \"edit 0\",\n    },\n    {\n        trigger: '.o_save',\n        run: \"click\",\n    },\n    {\n        trigger: '.o_barcode_line:nth-child(4)',\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertSublinesCount(4);\n            const sublines = helper.getSublines({ selected: false });\n            helper.assertLinesTrackingNumbers(sublines, [\"b1-b001\", \"b1-b002\", \"b1-b003\"]);\n            const line4 = helper.getSubline({ selected: true });\n            helper.assertLineQty(sublines[0], \"8/40\");\n            helper.assertLineQty(sublines[1], \"8\");\n            helper.assertLineQty(sublines[2], \"8\");\n            helper.assertLineQty(line4, \"0\");\n            helper.assertLineIsHighlighted(sublines[0], false);\n            helper.assertLineIsHighlighted(sublines[1], false);\n            helper.assertLineIsHighlighted(sublines[2], false);\n            helper.assertLineIsHighlighted(line4, true);\n        }\n    },\n    // The following scanned barcode should be decomposed like that:\n    //      - (30)00000004          > quantity (4)\n    //      - (10)b1-b004           > lot (b1-b004)\n    //      - (01)00000076543210    > product barcode (76543210)\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 3000000004\\x1D10b1-b004\\x1D0100000076543210',\n    },\n    {\n        trigger: '.o_sublines .o_barcode_line:contains(\"b1-b004\") .qty-done:contains(\"4\")',\n        run: function () {\n            helper.assertSublinesCount(4);\n            const sublines = helper.getSublines();\n            helper.assertLinesTrackingNumbers(sublines, [\"b1-b001\", \"b1-b002\", \"b1-b003\", \"b1-b004\"]);\n            helper.assertLineQty(sublines[0], \"8/40\");\n            helper.assertLineQty(sublines[1], \"8\");\n            helper.assertLineQty(sublines[2], \"8\");\n            helper.assertLineQty(sublines[3], \"4\");\n            helper.assertLineIsHighlighted(sublines[0], false);\n            helper.assertLineIsHighlighted(sublines[1], false);\n            helper.assertLineIsHighlighted(sublines[2], false);\n            helper.assertLineIsHighlighted(sublines[3], true);\n        }\n    },\n    // Scans only a lot => should create a new line with 1 qty.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 10b1-b005',\n    },\n    {\n        trigger: '.o_sublines .o_barcode_line:contains(\"b1-b005\") .qty-done:contains(\"1\")',\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertSublinesCount(5);\n            const sublines = helper.getSublines();\n            helper.assertLinesTrackingNumbers(sublines, [\"b1-b001\", \"b1-b002\", \"b1-b003\", \"b1-b004\", \"b1-b005\"]);\n            helper.assertLineQty(sublines[0], \"8/40\");\n            helper.assertLineQty(sublines[1], \"8\");\n            helper.assertLineQty(sublines[2], \"8\");\n            helper.assertLineQty(sublines[3], \"4\");\n            helper.assertLineQty(sublines[4], \"1\");\n            helper.assertLineIsHighlighted(sublines[0], false);\n            helper.assertLineIsHighlighted(sublines[1], false);\n            helper.assertLineIsHighlighted(sublines[2], false);\n            helper.assertLineIsHighlighted(sublines[3], false);\n            helper.assertLineIsHighlighted(sublines[4], true);\n        }\n    },\n    // Now scans the quantity.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 3000007',\n    },\n    {\n        trigger: '.o_sublines .o_barcode_line:contains(\"b1-b005\") .qty-done:contains(\"8\")',\n        run: function () {\n            helper.assertSublinesCount(5);\n            const sublines = helper.getSublines();\n            helper.assertLinesTrackingNumbers(sublines, [\"b1-b001\", \"b1-b002\", \"b1-b003\", \"b1-b004\", \"b1-b005\"]);\n            helper.assertLineQty(sublines[0], \"8/40\");\n            helper.assertLineQty(sublines[1], \"8\");\n            helper.assertLineQty(sublines[2], \"8\");\n            helper.assertLineQty(sublines[3], \"4\");\n            helper.assertLineQty(sublines[4], \"8\");\n            helper.assertLineIsHighlighted(sublines[0], false);\n            helper.assertLineIsHighlighted(sublines[1], false);\n            helper.assertLineIsHighlighted(sublines[2], false);\n            helper.assertLineIsHighlighted(sublines[3], false);\n            helper.assertLineIsHighlighted(sublines[4], true);\n        }\n    },\n    // Scans a lot already in the view with additional qty. => Should select the\n    // existing line for this lot and update its quantity done.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 10b1-b004\\x1D3000000004',\n    },\n    {\n        trigger: '.o_sublines .o_barcode_line.o_selected:contains(\"b1-b004\")',\n        run: function () {\n            helper.assertSublinesCount(5);\n            const sublines = helper.getSublines();\n            helper.assertLinesTrackingNumbers(sublines, [\"b1-b001\", \"b1-b002\", \"b1-b003\", \"b1-b004\", \"b1-b005\"]);\n            helper.assertLineQty(sublines[0], \"8/40\");\n            helper.assertLineQty(sublines[1], \"8\");\n            helper.assertLineQty(sublines[2], \"8\");\n            helper.assertLineQty(sublines[3], \"8\");\n            helper.assertLineQty(sublines[4], \"8\");\n            helper.assertLineIsHighlighted(sublines[0], false);\n            helper.assertLineIsHighlighted(sublines[1], false);\n            helper.assertLineIsHighlighted(sublines[2], false);\n            helper.assertLineIsHighlighted(sublines[3], true);\n            helper.assertLineIsHighlighted(sublines[4], false);\n        }\n    },\n    ...stepUtils.validateBarcodeOperation(\".o_validate_page\"),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_gs1_receipt_quantity_with_uom', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function () {\n            helper.assertLinesCount(0);\n        }\n    },\n    // Scans 5 kg for the \"Product by Units\" => Wrong UoM category, should display an error (instead of creating a new line)\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 01000000152643293100000005',\n    },\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: function () {\n            helper.assertLinesCount(0);\n            const errorMessageTitle = document.querySelector('.o_notification_title');\n            helper.assert(errorMessageTitle.innerText, 'Wrong Unit of Measure');\n        }\n    },\n    {\n        trigger: '.o_notification_close',\n        run: \"click\",\n    },\n    // Scans 4 units for the \"Product by Units\".\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 01000000152643293700000004',\n    },\n    {\n        trigger: '.o_barcode_line',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ barcode: \"15264329\" });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, \"4 Units\");\n        }\n    },\n    // Scans 5 kg for the \"Product by Units\" => Wrong UoM category, should display an error (instead of updating the existing line)\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 01000000152643293100000005',\n    },\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ barcode: \"15264329\" });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, \"4 Units\");\n            const errorMessageTitle = document.querySelector('.o_notification_title');\n            helper.assert(errorMessageTitle.innerText, 'Wrong Unit of Measure');\n        }\n    },\n    {\n        trigger: '.o_notification_close',\n        run: \"click\",\n    },\n\n    // Scans 5 kg for the \"Product by kg\".\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 01000000152648793100000005',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"Product by kg\")',\n        run: function () {\n            helper.assertLinesCount(2);\n            const line = helper.getLine({ barcode: \"15264879\" });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, \"5 kg\");\n        }\n    },\n    // Scans 6 units for the \"Product by kg\" => Wrong UoM category, shoud display an error.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 01000000152648793700000006',\n    },\n    {\n        trigger: '.o_notification_bar.bg-danger',\n        run: function () {\n            helper.assertLinesCount(2);\n            const line = helper.getLine({ barcode: \"15264879\" });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, \"5 kg\");\n            const errorMessageTitle = document.querySelector('.o_notification_title');\n            helper.assert(errorMessageTitle.innerText, 'Wrong Unit of Measure');\n        }\n    },\n    {\n        trigger: '.o_notification_close',\n        run: \"click\",\n    },\n\n    // Scans 1.25 kg for the \"Product by g\" => Compatible UoM but kg need to be converted to g.\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 01000000152648933102000125',\n    },\n    {\n        trigger: '.o_barcode_line:contains(\"Product by g\")',\n        run: function () {\n            helper.assertLinesCount(3);\n            const line = helper.getLine({ barcode: \"15264893\" });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, \"1250 g\");\n        }\n    },\n    // Clicks on the edit button to trigger a save.\n    {\n        trigger: '.o_barcode_line:nth-child(3) .o_edit',\n        run: \"click\",\n    },\n    {\n        trigger: '[name=qty_done] input',\n        run: function () {\n            helper.assertFormQuantity(\"1250\");\n        }\n    },\n    ...stepUtils.discardBarcodeForm(),\n]});\n\nregistry.category(\"web_tour.tours\").add(\"test_gs1_receipt_scan_not_gs1_multi_barcode\", { steps: () => [\n    { trigger: \".o_stock_barcode_main_menu\", run: \"scan WHIN\" },\n    // Scan a barcode containing 1 product tracked by SN followed by 8 serial numbers.\n    { trigger: \".o_barcode_client_action\", run: \"scan productserial1,sn1,sn2,sn3,sn4,sn5,sn6,sn7,sn8\" },\n    { trigger: \".o_barcode_line .qty-done:contains(8)\" },\n    { trigger: \"button.o_toggle_sublines\", run: \"click\" },\n    { trigger: \".o_sublines .o_barcode_line:nth-child(8)\" },\n    ...stepUtils.validateBarcodeOperation(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_gs1_receipt_packaging', { steps: () => [\n    {\n        trigger: '.o_barcode_client_action',\n        run: function () {\n            helper.assertLinesCount(0);\n        }\n    },\n    // Scans a packaging without any quantity\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 0100000000002226',\n    },\n    {\n        trigger: '.o_barcode_line',\n        run: function () {\n            helper.assertLinesCount(1);\n            const line = helper.getLine({ barcode: \"1113\" });\n            helper.assertLineIsHighlighted(line, true);\n            helper.assertLineQty(line, \"6\");\n        }\n    },\n    // Scans 4 packaging\n    {\n        trigger: '.o_barcode_client_action',\n        run: 'scan 01000000000022263700000004',\n    },\n    {\n        trigger: '.o_barcode_line [name=quantity]:contains(\"30\")',\n        run: function () {\n            helper.assertLinesCount(1);\n            helper.assertLineIsHighlighted(0, true);\n            helper.assertLineQty(0, \"30\");\n        }\n    },\n    // Clicks on the edit button to trigger a save.\n    {\n        trigger: '.o_barcode_line:first-child .o_edit',\n        run: \"click\",\n    },\n    {\n        trigger: '[name=\"qty_done\"] input',\n        run: function () {\n            helper.assertFormQuantity(\"30\");\n        }\n    },\n    ...stepUtils.discardBarcodeForm(),\n]});\n\nregistry.category(\"web_tour.tours\").add('test_gs1_receipt_packaging_with_uom', { steps: () => [\n    { trigger: \".o_stock_barcode_main_menu\", run: \"scan WHIN\"},\n    /* Scan (01)10347543011337(3103)000900(17)240701(10)100005\n        - (01) 10347543011337: packaging barcode (6 units);\n        - (3103) 000900: 0.9 kg -> Should be ignored;\n        - (17) 240701: expiration date (1st July 2024);\n        - (10) 100005: tracking number.\n    */\n    {\n        trigger: \".o_barcode_client_action\",\n        run: \"scan 011034754301133731030009001724070110100005\"\n    },\n    {\n        trigger: \".o_barcode_line\",\n        run: function() {\n            helper.assertLineQty(0, \"6 Units\");\n        }\n    },\n    /* Scan another lots with both quantity and weight.\n        (01)03287890001332(10)92404603(17)240304(3103)001500(37)5\n        - (01) 03287890001332: product barcode;\n        - (10) 92404603: tracking number;\n        - (17) 240304: expiration date (4th March 2024);\n        - (3103) 001500: 1.5 kg -> Should be ignored;\n        - (37) 5: 5 units -> Should be used as the line quantity.\n        */\n    {\n        trigger: \".o_barcode_client_action\",\n        run: \"scan 01032878900013321092404603\\x1D172403043103001500375\"\n    },\n    { trigger: \".o_toggle_sublines .fa-angle-down\", run: \"click\" },\n    {\n        trigger: \".o_toggle_sublines .fa-angle-up\",\n        run: function() {\n            helper.assertLinesCount(1);\n            helper.assertSublinesCount(2);\n            const sublines = helper.getSublines();\n            helper.assertLinesTrackingNumbers(sublines, [\"100005\", \"92404603\"]);\n            helper.assertLineQty(sublines[0], \"6 Units\");\n            helper.assertLineQty(sublines[1], \"5 Units\");\n        }\n    },\n    // Same scenario but with scanning the packaging instead of the product, the weight should\n    // still be ignored but the scanned quantity should multiply the packaging quantity.\n    {\n        trigger: \".o_barcode_client_action\",\n        run: \"scan 011034754301133710123456\\x1D172403043103001500375\"\n    },\n    {\n        trigger: \".o_barcode_line:nth-child(3)\",\n        run: function() {\n            helper.assertLinesCount(1);\n            helper.assertSublinesCount(3);\n            const sublines = helper.getSublines();\n            helper.assertLinesTrackingNumbers(sublines, [\"100005\", \"92404603\", \"123456\"]);\n            helper.assertLineQty(sublines[0], \"6 Units\");\n            helper.assertLineQty(sublines[1], \"5 Units\");\n            // Package qty: 6; scanned qty: 5 -> Line qty = 6 x 5 = 30.\n            helper.assertLineQty(sublines[2], \"30 Units\");\n        }\n    },\n]});\n\nregistry.category(\"web_tour.tours\").add('test_gs1_tracked_packaging', { steps: () => [\n    // Create a receipt and check non-existing lot is correctly scanned alongside the packaging.\n    { trigger: '.o_stock_barcode_main_menu', run: \"scan WHIN\" },\n    { trigger: '.o_barcode_client_action', run: \"scan 020000001265325610lot-001\" },\n    {\n        trigger: '.o_barcode_line',\n        run: () => {\n            helper.assertLinesCount(1);\n            helper.assertLineQty(0, \"6\", \"Scanned packaging has quantity of 6\");\n            helper.assertLineProduct(0, \"productlot1\");\n            helper.assertLineTrackingNumber(0, \"lot-001\");\n        },\n    },\n    { trigger: '.o_validate_page', run: \"click\" },\n\n    // Create a delivery and check existing lot is correctly found alongside the packaging.\n    { trigger: '.o_stock_barcode_main_menu', run: \"scan WHOUT\" },\n    { trigger: '.o_barcode_client_action', run: \"scan 020000001265325610lot-001\" },\n    {\n        trigger: '.o_barcode_line',\n        run: () => {\n            helper.assertLinesCount(1);\n            helper.assertLineQty(0, \"6\", \"Scanned packaging has quantity of 6\");\n            helper.assertLineProduct(0, \"productlot1\");\n            helper.assertLineTrackingNumber(0, \"lot-001\");\n        },\n    },\n    { trigger: '.o_validate_page', run: \"click\" },\n    { trigger: '.o_notification_bar.bg-success' },\n]});\n", "import { registry } from \"@web/core/registry\";\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_enterprise/tests/test_discuss_channel_expand.py\n */\nregistry\n    .category(\"web_tour.tours\")\n    .add(\"mail_enterprise/static/tests/tours/discuss_channel_expand_test_tour.js\", {\n        steps: () => [\n            {\n                content:\n                    \"Click on 'Open Actions Menu' in the chat window header to show expand button\",\n                trigger:\n                    '.o-mail-ChatWindow:contains(\"test-mail-channel-expand-tour\") [title=\"Open Actions Menu\"]',\n                run: \"click\",\n            },\n            {\n                content: \"Click on expand button to open channel in Discuss\",\n                trigger: '.o-dropdown-item:contains(\"Open in Discuss\")',\n                run: \"click\",\n            },\n            {\n                content:\n                    \"Check that first message of #test-mail-channel-expand-tour is shown in Discuss app\",\n                trigger:\n                    '.o-mail-Discuss-content .o-mail-Message-body:contains(\"test-message-mail-channel-expand-tour\")',\n            },\n        ],\n    });\n", "/** @odoo-module **/\n\nimport { registry } from \"@web/core/registry\";\nimport { stepUtils } from \"@web_tour/tour_service/tour_utils\";\nimport { accountTourSteps } from \"@account/js/tours/account\";\n\nregistry.category(\"web_tour.tours\").add(\"account_accountant_bank_rec_widget_rainbowman_reset\", {\n    url: \"/odoo\",\n    steps: () => [\n        stepUtils.showAppsMenuItem(),\n        ...accountTourSteps.goToAccountMenu(\"Open the accounting module\"),\n\n        // Open the widget. The first line should be selected by default.\n        {\n            trigger: \".o_breadcrumb\",\n        },\n        {\n            content: \"Open the bank reconciliation widget\",\n            trigger: \"button.btn-secondary[name='action_open_reconcile']\",\n            run: \"click\",\n        },\n        {\n            trigger: \"div[name='line_ids'] td[field='name']:contains('line1')\",\n        },\n        {\n            content: \"'line1' should be selected and form mounted\",\n            trigger: \".o_bank_rec_selected_st_line:contains('line1')\",\n        },\n        // Rainbowman gets reset\n        {\n            content: \"Mount invoice 2 for line 1\",\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table td[name='move_id']:contains('INV/2019/00002')\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table tr.o_rec_widget_list_selected_item td[name='move_id']:contains('INV/2019/00002')\",\n        },\n        {\n            content: \"Validate line1\",\n            trigger: \"button:contains('Validate')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_bank_rec_selected_st_line:contains('line2')\",\n        },\n        {\n            content: \"No records brings rainbows\",\n            trigger: \"div.o_kanban_view .o_searchview_input\",\n            run: \"fill thisShouldNotReturnAnyRecords\",\n        },\n        {\n            content: \"Select the Journal Entry search option from the dropdown\",\n            trigger: \".o_searchview_autocomplete li:contains(Journal Entry)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_reward_rainbow_man:contains('You reconciled 1 transaction in')\",\n        },\n        {\n            content: \"Remove the filter while rainbow man is on screen\",\n            trigger: \".o_kanban_view .o_searchview_facet:nth-child(3) .o_facet_remove\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_bank_rec_st_line:contains('line2')\",\n        },\n        {\n            content: \"Search for no results again\",\n            trigger: \"div.o_kanban_view .o_searchview_input\",\n            run: \"fill thisShouldNotReturnAnyRecords\",\n        },\n        {\n            content: \"Select the Journal Entry search option from the dropdown\",\n            trigger: \".o_searchview_autocomplete li:contains(Journal Entry)\",\n            run: \"click\",\n        },\n        {\n            content: \"No content helper is displayed instead of rainbowman\",\n            trigger: \".o_view_nocontent_smiling_face\",\n        },\n        // End\n        ...stepUtils.toggleHomeMenu(),\n        ...accountTourSteps.goToAccountMenu(\"Reset back to accounting module\"),\n        {\n            content: \"check that we're back on the dashboard\",\n            trigger: 'a:contains(\"Customer Invoices\")',\n        },\n    ],\n});\n", "/** @odoo-module **/\n\nimport { registry } from \"@web/core/registry\";\nimport { stepUtils } from \"@web_tour/tour_service/tour_utils\";\nimport { accountTourSteps } from \"@account/js/tours/account\";\n\nregistry.category(\"web_tour.tours\").add('account_accountant_bank_rec_widget_statements',\n    {\n        url: '/odoo',\n        steps: () => [\n        stepUtils.showAppsMenuItem(),\n        ...accountTourSteps.goToAccountMenu(\"Open the accounting module\"),\n        {\n            trigger: \".o_breadcrumb\",\n        },\n        {\n            content: \"Open the bank reconciliation widget\",\n            trigger: \"button.btn-secondary[name='action_open_reconcile']\",\n            run: \"click\",\n        },\n        {\n            content: \"Statement button\",\n            trigger:\n                \".o_bank_rec_st_line:eq(2) a.oe_kanban_action:contains('Statement'):not(:visible)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".modal-dialog:contains('Create Statement')\",\n        },\n        {\n            content: \"Save the statement with proposed values\",\n            trigger: \".o_form_button_save\",\n            run: \"click\",\n        },\n        {\n            content: \"Click the Valid Statement with $ 1,000.00 that is visible in Kanban\",\n            trigger: \"span[name='kanban-subline-clickable-amount']:contains('$ 1,000.00')\",\n            run: \"click\",\n        },\n        {\n            content: \"Modify the end balance\",\n            trigger: \"input[id='balance_end_real_0']\",\n            run: \"edit 100 && click body\",\n        },\n        {\n            trigger: \".alert-warning:contains('The running balance')\",\n        },\n        {\n            content: \"Dialog displays warning, save anyway\",\n            trigger: \".breadcrumb-item.o_back_button:nth-of-type(2)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".btn-link:contains('$ 2,100.00')\",\n        },\n        {\n            content: \"Click the red statement, after checking the balance\",\n            trigger: \"span[name='kanban-subline-clickable-amount']:contains('$ 100.00')\",\n            run: \"click\",\n        },\n        {\n            content: \"Back in the form view\",\n            trigger: \".alert-warning:contains('The running balance')\",\n        },\n        {\n            content: \"Click on Action\",\n            trigger: \".o_cp_action_menus button\",\n            run: \"click\",\n        },\n        {\n            content: \"Click on Delete\",\n            trigger: \".o-dropdown--menu span:contains('Delete')\",\n            run: \"click\",\n        },\n        {\n            content: \"Confirm Deletion\",\n            trigger: \".btn-primary:contains('Delete')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_kanban_renderer:not(:has(.kanban-statement))\",\n        },\n        {\n            content: \"balance displays $3000.00 and no statement\",\n            trigger: \".btn-link:contains('$ 3,000')\",\n        },\n        // End\n        ...stepUtils.toggleHomeMenu(),\n        ...accountTourSteps.goToAccountMenu(\"Reset back to accounting module\"),\n        {\n            content: \"check that we're back on the dashboard\",\n            trigger: 'a:contains(\"Customer Invoices\")',\n        }\n    ]\n});\n", "/** @odoo-module **/\n\nimport { registry } from \"@web/core/registry\";\nimport { stepUtils } from \"@web_tour/tour_service/tour_utils\";\nimport { patch } from \"@web/core/utils/patch\";\nimport { accountTourSteps } from \"@account/js/tours/account\";\n\npatch(accountTourSteps, {\n    bankRecUiReportSteps() {\n        return [\n            {\n                trigger: \".o_bank_rec_selected_st_line:contains('line1')\",\n            },\n            {\n                content: \"balance is 2100\",\n                trigger: \".btn-link:contains('$ 2,100.00')\",\n            },\n        ];\n    },\n});\n\nregistry.category(\"web_tour.tours\").add(\"account_accountant_bank_rec_widget_ui\", {\n    url: \"/odoo\",\n    steps: () => [\n        stepUtils.showAppsMenuItem(),\n        ...accountTourSteps.goToAccountMenu(\"Open the accounting module\"),\n\n        // Open the widget. The first line should be selected by default.\n        {\n            trigger: \".o_breadcrumb\",\n        },\n        {\n            content: \"Open the bank reconciliation widget\",\n            trigger: \"button.btn-secondary[name='action_open_reconcile']\",\n            run: \"click\",\n        },\n        {\n            trigger: \"div[name='line_ids'] td[field='name']:contains('line1')\",\n        },\n        {\n            content: \"'line1' should be selected and form mounted\",\n            trigger: \".o_bank_rec_selected_st_line:contains('line1')\",\n        },\n        // Select line2. It should remain selected when returning using the breadcrumbs.\n        {\n            trigger: \".o_bank_rec_st_line:contains('line3')\",\n        },\n        {\n            content: \"select 'line2'\",\n            trigger: \".o_bank_rec_st_line:contains('line2')\",\n            run: \"click\",\n        },\n        {\n            content: \"'line2' should be selected\",\n            trigger: \".o_bank_rec_selected_st_line:contains('line2')\",\n        },\n        {\n            content: \"View an invoice\",\n            trigger: \"button.btn-secondary[name='action_open_business_doc']:eq(1)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_breadcrumb .active:contains('INV/2019/00001')\",\n        },\n        {\n            content: \"Breadcrumb back to Bank Reconciliation from INV/2019/00001\",\n            trigger: \".breadcrumb-item:contains('Bank Reconciliation')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_bank_rec_st_line:contains('line1')\",\n        },\n        {\n            content: \"'line2' should be selected after returning\",\n            trigger: \".o_bank_rec_selected_st_line:contains('line2')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"div[name='line_ids'] td[field='name']:contains('line2')\",\n        },\n        {\n            content: \"'line2' form mounted\",\n            trigger: \".o_bank_rec_selected_st_line:contains('line2')\",\n            run: \"click\",\n        },\n        // Keep AML search, and prepared entry (line_ids) when changing tabs, using breadcrumbs, and view switcher\n        {\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table tr:nth-child(2) td[name='move_id']:contains('INV/2019/00001')\",\n        },\n        {\n            content: \"AMLs list has both invoices\",\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table tr:nth-child(1) td[name='move_id']:contains('INV/2019/00002')\",\n        },\n        {\n            trigger: \"a.active[name='amls_tab']\",\n        },\n        {\n            content: \"Search for INV/2019/00001\",\n            trigger: \"div.bank_rec_widget_form_amls_list_anchor .o_searchview_input\",\n            run: \"edit INV/2019/00001\",\n        },\n        {\n            content: \"Select the Journal Entry search option from the dropdown\",\n            trigger: \".o_searchview_autocomplete li:contains(Journal Entry)\",\n            run: \"click\",\n        },\n        {\n            content: \"AMLs list only displays one invoice\",\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table tr:nth-child(1) td[name='move_id']:contains('INV/2019/00001')\",\n        },\n        {\n            content: \"Liquidity line displays debit '$ 1,000.00'\",\n            trigger:\n                \"div[name='line_ids'] table.o_list_table tr.o_bank_rec_liquidity_line td[field='debit']:contains('$ 1,000.00')\",\n        },\n        {\n            content: \"Select the liquidity line\",\n            trigger: \"tr.o_bank_rec_liquidity_line td[field='debit']\",\n            run: \"click\",\n        },\n        {\n            content: \"Modify the liquidity line amount\",\n            trigger: \"div[name='amount_currency'] input\",\n            run: \"edit 100.00 && click body\",\n        },\n        {\n            content: \"Liquidity line displays debit '$ 100.00'\",\n            trigger:\n                \"div[name='line_ids'] table.o_list_table tr.o_bank_rec_liquidity_line td[field='debit']:contains('$ 100.00')\",\n        },\n        {\n            trigger: \"div[name='partner_id'] input\",\n        },\n        {\n            content: \"Select 'amls_tab'\",\n            trigger: \"a[name='amls_tab']\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor .o_searchview_facet:nth-child(1) .o_facet_value:contains('INV/2019/00001')\",\n        },\n        {\n            content: \"AMLs list contains the search facet, and one invoice - select it\",\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table tr:nth-child(1) td[name='move_id']:contains('INV/2019/00001')\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table tr.o_rec_widget_list_selected_item td[name='move_id']:contains('INV/2019/00001')\",\n        },\n        {\n            content: \"Check INV/2019/00001 is well marked as selected\",\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table tr.o_rec_widget_list_selected_item td[name='move_id']:contains('INV/2019/00001')\",\n        },\n        {\n            content: \"View an invoice\",\n            trigger: \"button.btn-secondary[name='action_open_business_doc']:nth-child(1)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_breadcrumb .active:contains('INV/2019/00001')\",\n        },\n        {\n            content: \"Breadcrumb back to Bank Reconciliation from INV/2019/00001\",\n            trigger: \".breadcrumb-item:contains('Bank Reconciliation')\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor .o_searchview_facet:nth-child(1) .o_facet_value:contains('INV/2019/00001')\",\n        },\n        {\n            content: \"Check INV/2019/00001 is selected and still contains the search facet\",\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table tr.o_rec_widget_list_selected_item td[name='move_id']:contains('INV/2019/00001')\",\n        },\n        // Search should remove some lines, select the first unmatched record, and persist when returning with breadcrumbs\n        {\n            trigger: \"a.active[name='amls_tab']\",\n        },\n        {\n            content: \"Search for line2\",\n            trigger: \"div.o_kanban_view .o_searchview_input\",\n            run: \"fill line2\",\n        },\n        {\n            content: \"Select the Transaction search option from the dropdown\",\n            trigger: \".o_searchview_autocomplete li:contains(Transaction)\",\n            run: \"click\",\n        },\n        {\n            trigger: \"div[name='line_ids'] td[field='name']:contains('line2')\",\n        },\n        {\n            content: \"'line2' should be selected\",\n            trigger: \".o_bank_rec_st_line:last():contains('line2')\",\n        },\n        {\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor .o_searchview_facet:nth-child(1) .o_facet_value:contains('INV/2019/00001')\",\n        },\n        {\n            content:\n                \"Nothing has changed: INV/2019/00001 is selected and still contains the search facet\",\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table tr.o_rec_widget_list_selected_item td[name='move_id']:contains('INV/2019/00001')\",\n        },\n        {\n            trigger: \".o_switch_view.o_kanban.active\",\n        },\n        {\n            content: \"Switch to list view\",\n            trigger: \".o_switch_view.o_list\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_switch_view.o_list.active\",\n        },\n        {\n            content: \"Switch back to kanban\",\n            trigger: \".o_switch_view.o_kanban\",\n            run: \"click\",\n        },\n        {\n            content: \"Remove the kanban filter for line2\",\n            trigger: \".o_kanban_view .o_searchview_facet:nth-child(3) .o_facet_remove\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor .o_searchview_facet:nth-child(1) .o_facet_value:contains('INV/2019/00001')\",\n        },\n        {\n            content:\n                \"Nothing has changed: INV/2019/00001 is still selected and contains the search facet\",\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table tr.o_rec_widget_list_selected_item td[name='move_id']:contains('INV/2019/00001')\",\n        },\n        // AML Search Facet is removed, and line_ids reset when changing line\n        {\n            trigger: \".o_bank_rec_st_line:contains('line3')\",\n        },\n        {\n            content: \"selecting 'line1' should reset the AML search filter \",\n            trigger: \".o_bank_rec_st_line:contains('line1')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"div[name='line_ids'] td[field='name']:contains('line1')\",\n        },\n        {\n            content: \"select 'line2' again\",\n            trigger: \".o_bank_rec_st_line:contains('line2')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"div[name='line_ids'] td[field='name']:contains('line2')\",\n        },\n        {\n            content: \"Bank Suspense Account is back\",\n            trigger: \"div[name='line_ids'] .o_bank_rec_auto_balance_line\",\n        },\n        {\n            content: \"AML Search Filter has been reset\",\n            trigger: \".o_list_view .o_searchview_input_container:not(:has(.o_searchview_facet))\",\n        },\n        // Test statement line selection when using the pager\n        {\n            content: \"Click Pager\",\n            trigger: \".o_pager_value:first()\",\n            run: \"click\",\n        },\n        {\n            content: \"Change pager to display lines 1-2\",\n            trigger: \"input.o_pager_value\",\n            run: \"edit 1-2 && click body\",\n        },\n        {\n            trigger: \".o_pager_value:contains('1-2')\",\n        },\n        {\n            content: \"Last St Line is line2\",\n            trigger: \".o_bank_rec_st_line:last():contains('line2')\",\n        },\n        {\n            content: \"Page Next\",\n            trigger: \".o_pager_next:first():not(:disabled)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_pager_value:contains('3-3')\",\n        },\n        {\n            content: \"Statement line3 is selected\",\n            trigger: \".o_bank_rec_selected_st_line:contains('line3')\",\n        },\n        {\n            content: \"Page to beginning\",\n            trigger: \".o_pager_next:first()\",\n            run: \"click\",\n        },\n        {\n            trigger: \"div[name='line_ids'] td[field='name']:contains('line1')\",\n        },\n        {\n            content: \"Statement line1 is selected\",\n            trigger: \".o_bank_rec_selected_st_line:contains('line1')\",\n        },\n        // HTML buttons\n        {\n            content: \"Mount an invoice\",\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table td[name='move_id']:contains('INV/2019/00003')\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \"div[name='line_ids']:has(.text-decoration-line-through:contains('$ 2,000.00'))\",\n        },\n        {\n            content: \"Select the mounted invoice line and check the strikethrough value\",\n            trigger:\n                \"div[name='line_ids'] tr.o_data_row:last() td[field='name']:contains('INV/2019/00003')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"a.active[name='manual_operations_tab']\",\n        },\n        {\n            content: \"Fully Paid button\",\n            trigger: \"button[name='action_apply_line_suggestion']:contains('fully paid')\",\n            run: \"click\",\n        },\n        {\n            content: \"Check the remainder\",\n            trigger:\n                \"div[name='line_ids'] tr.o_data_row:contains('Suspense') td[field='debit']:contains('$ 1,000.00')\",\n        },\n        {\n            content: \"Partial Payment\",\n            trigger: \"button[name='action_apply_line_suggestion']:contains('partial payment')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"button[name='action_apply_line_suggestion']:contains('fully paid')\",\n        },\n        {\n            content: \"View Invoice 0003\",\n            trigger: \"button[name='action_redirect_to_move']\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_breadcrumb .active:contains('INV/2019/00003')\",\n        },\n        {\n            content: \"Breadcrumb back to Bank Reconciliation from INV/2019/00003\",\n            trigger: \".breadcrumb-item:contains('Bank Reconciliation')\",\n            run: \"click\",\n        },\n        {\n            content: \"Select the mounted invoice line INV/2019/00003\",\n            trigger:\n                \"div[name='line_ids'] tr.o_data_row:last() td[field='name']:contains('INV/2019/00003')\",\n            run: \"click\",\n        },\n        // Match Existing entries tab is activated when line is removed\n        {\n            trigger: \"a.active[name='manual_operations_tab']\",\n        },\n        {\n            content: \"Remove the invoice\",\n            trigger: \".o_list_record_remove .fa-trash-o\",\n            run: \"click\",\n        },\n        {\n            content: \"amls_tab is activated\",\n            trigger: \"a.active[name='amls_tab']\",\n        },\n        {\n            content: \"Activate Manual Operations to add manual entries\",\n            trigger: \"a[name='manual_operations_tab']\",\n            run: \"click\",\n        },\n        {\n            content: \"add manual entry 1\",\n            trigger: \"div[name='amount_currency'] input\",\n            run: \"edit -600.0 && click body\",\n        },\n        {\n            content: \"mount the remaining opening balance line\",\n            trigger:\n                \"div[name='line_ids'] tr.o_data_row:contains('Suspense') td[field='credit']:contains('$ 400.00')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"div[name='amount_currency'] input:value('-400.00'):focus-within\",\n        },\n        {\n            content: \"Remove the manual entry\",\n            trigger: \".o_list_record_remove .fa-trash-o\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \"div[name='line_ids'] tr.o_data_row:contains('Suspense') td[field='credit']:contains('$ 1,000.00')\",\n        },\n        {\n            content: \"amls_tab is activated and auto balancing line is 1000\",\n            trigger: \"a.active[name='amls_tab']\",\n        },\n        {\n            content: \"Mount another invoice\",\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table td[name='move_id']:contains('INV/2019/00001')\",\n            run: \"click\",\n        },\n        // After validating, line1 should disappear & line2 should be selected (due to filters)\n        {\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table tr.o_rec_widget_list_selected_item td[name='move_id']:contains('INV/2019/00001')\",\n        },\n        {\n            content: \"Validate line1\",\n            trigger: \"button:contains('Validate')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"div[name='line_ids'] td[field='name']:contains('line2')\",\n        },\n        {\n            content: \"The 'line2' is the first kanban record and is selected\",\n            trigger: \".o_bank_rec_st_line:first():contains('line2')\",\n        },\n        // Test Reset, \"Matched\" badge and double-click\n        {\n            content: \"Remove the kanban filter for 'Not Matched'\",\n            trigger: \".o_kanban_view .o_searchview_facet:nth-child(2) .o_facet_remove\",\n            run: \"click\",\n        },\n        {\n            trigger: \"div[name='line_ids'] td[field='name']:contains('line2')\",\n        },\n        {\n            content: \"The 'line1' is the first kanban record with line2 selected\",\n            trigger: \".o_bank_rec_st_line:first():contains('line1')\",\n        },\n        {\n            content: \"Mount invoice 2 for line 2\",\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table td[name='move_id']:contains('INV/2019/00002')\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table tr.o_rec_widget_list_selected_item td[name='move_id']:contains('INV/2019/00002')\",\n        },\n        {\n            content: \"Validate line2 with double click\",\n            trigger: \"button:contains('Validate')\",\n            run: \"dblclick\",\n        },\n        {\n            trigger: \".o_bank_rec_st_line:contains('line2') .badge.text-bg-success\",\n        },\n        {\n            content: \"Click Pager again after line2 is matched\",\n            trigger: \".o_pager_value:first()\",\n            run: \"click\",\n        },\n        {\n            content: \"Change pager to display lines 1-3\",\n            trigger: \"input.o_pager_value\",\n            run: \"edit 1-3 && click body\",\n        },\n        {\n            trigger: \".o_bank_rec_selected_st_line:contains('line3')\",\n        },\n        {\n            content: \"manually select line2 again by clicking it's matched icon\",\n            trigger: \".badge.text-bg-success:last()\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \"div[name='line_ids']:not(:has(.fa-trash-o)) td[field='name']:contains('line2')\",\n        },\n        {\n            content: \"Reset line2\",\n            trigger: \"button:contains('Reset')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_bank_rec_selected_st_line:contains('line2'):not(:has(div.badge))\",\n        },\n        {\n            content: \"amls_tab is activated while still on line2 which doesn't contain a badge\",\n            trigger: \"div[name='line_ids']:has(.fa-trash-o)+.o_notebook a.active[name='amls_tab']\",\n        },\n        // Test view_switcher\n        {\n            trigger: \".o_switch_view.o_kanban.active\",\n        },\n        {\n            content: \"Switch to list view\",\n            trigger: \".o_switch_view.o_list\",\n            run: \"click\",\n        },\n        {\n            trigger: \".btn-secondary:contains('View')\",\n        },\n        {\n            content: \"Select the first Match Button (line2)\",\n            trigger: \".btn-secondary:contains('Match')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_bank_rec_st_line:last():contains('line2')\",\n        },\n        {\n            content: \"Last St Line is line2\",\n            trigger: \".o_bank_rec_selected_st_line:contains('line2')\",\n            run: \"click\",\n        },\n        {\n            content: \"Button To Check will reconcile since partner is saved on line2\",\n            trigger: \".btn-secondary:contains('To Check')\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o_bank_rec_selected_st_line:contains('line2'):has(div.badge[title='Matched'] i):has(span.badge:contains('To check'))\",\n        },\n        {\n            content: \"both badges are visible, trash icon is not, manual operation tab is active\",\n            trigger:\n                \"div[name='line_ids']:not(:has(.fa-trash-o))+.o_notebook a.active[name='manual_operations_tab']\",\n        },\n        {\n            trigger: \".o_switch_view.o_kanban.active\",\n        },\n        {\n            content: \"Switch to list view\",\n            trigger: \".o_switch_view.o_list\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_switch_view.o_list.active\",\n        },\n        {\n            content: \"Remove the line filter\",\n            trigger: \".o_searchview_facet:contains('0002') .o_facet_remove\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_data_row:contains('line2'):has(.btn-secondary:contains('View'))\",\n        },\n        {\n            content: \"Select the first Match Button (line3)\",\n            trigger: \".btn-secondary:contains('Match')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_bank_rec_stats_buttons\",\n        },\n        {\n            content: \"Open search bar menu\",\n            trigger: \".o_searchview_dropdown_toggler:eq(0)\",\n            run: \"click\",\n        },\n        // Test Reco Model\n        {\n            trigger: \".o-dropdown--menu.o_search_bar_menu\",\n        },\n        {\n            content: \"Choose a filter\",\n            trigger: \".o_search_bar_menu .dropdown-item:first()\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-dropdown--menu\",\n        },\n        {\n            content: \"Not Matched Filter\",\n            trigger: \".dropdown-item:contains('Not Matched')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_switch_view.o_kanban.active\",\n        },\n        {\n            content: \"reco model dropdown\",\n            trigger: \".bank_rec_reco_model_dropdown i\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-dropdown--menu\",\n        },\n        {\n            content: \"create model\",\n            trigger: \".dropdown-item:contains('Create model')\",\n            run: \"click\",\n        },\n        {\n            content: \"model name\",\n            trigger: \"input#name_0\",\n            run: \"edit Bank Fees\",\n        },\n        {\n            content: \"add an account\",\n            trigger: \"a:contains('Add a line')\",\n            run: \"click\",\n        },\n        {\n            content: \"search for bank fees account\",\n            trigger: \"[name='account_id'] input\",\n            run: \"edit Bank Fees\",\n        },\n        {\n            trigger: \".o-autocomplete--dropdown-menu\",\n        },\n        {\n            content: \"select the bank fees account\",\n            trigger: \".o-autocomplete--dropdown-item:contains('Bank Fees')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_breadcrumb .active > span:contains('New')\",\n        },\n        {\n            content: \"Breadcrumb back to Bank Reconciliation from the model\",\n            trigger: \".breadcrumb-item:contains('Bank Reconciliation')\",\n            run: \"click\",\n        },\n        {\n            content: \"Choose Bank Fees Model\",\n            trigger: \".recon_model_button:contains('Bank Fees')\",\n            run: \"click\",\n        },\n        {\n            content: \"Validate line3\",\n            trigger: \"button:contains('Validate').btn-primary\",\n            run: \"dblclick\",\n        },\n        {\n            trigger: \".o_reward_rainbow_man\",\n        },\n        {\n            content:\n                \"Remove the kanbans 'not matched' filter to reset all lines - use the rainbow man button\",\n            trigger: \"p.btn-primary:contains('All Transactions')\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \".o_kanban_view .o_searchview:first() .o_searchview_facet:last():contains('Bank')\",\n        },\n        {\n            content: \"Wait for search model change and line3 to appear\",\n            trigger: \".o_bank_rec_st_line:last():contains('line3')\",\n        },\n        {\n            trigger: \".o_bank_rec_selected_st_line:contains('line2')\",\n        },\n        {\n            content: \"'line2' should be selected, reset it\",\n            trigger: \"button:contains('Reset')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_bank_rec_st_line:contains('line2'):not(:has(div.badge))\",\n        },\n        {\n            content: \"select matched 'line3'\",\n            trigger: \".o_bank_rec_st_line:contains('line3')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_bank_rec_selected_st_line:contains('line3')\",\n        },\n        {\n            content: \"'line3' should be selected, reset it\",\n            trigger: \"button:contains('Reset')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_bank_rec_st_line:contains('line3'):not(:has(div.badge))\",\n        },\n        {\n            content: \"select matched 'line1'\",\n            trigger: \".o_bank_rec_st_line:contains('line1')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_bank_rec_selected_st_line:contains('line1')\",\n        },\n        {\n            content: \"'line1' should be selected, reset it\",\n            trigger: \"button:contains('Reset')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_bank_rec_stats_buttons\",\n        },\n        {\n            content: \"Open search bar menu\",\n            trigger: \".o_searchview_dropdown_toggler:eq(0)\",\n            run: \"click\",\n        },\n        {\n            trigger: \"button:contains('Validate')\",\n        },\n        {\n            content: \"Filter Menu\",\n            trigger: \".o_search_bar_menu .dropdown-item:first()\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o-dropdown--menu\",\n        },\n        {\n            content: \"Activate the Not Matched filter\",\n            trigger: \".dropdown-item:contains('Not Matched')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_searchview_facet:contains('Not Matched')\",\n        },\n        {\n            content: \"Close the Filter Menu\",\n            trigger: \".o_searchview_dropdown_toggler:eq(0)\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_searchview_facet:contains('Not Matched')\",\n        },\n        {\n            content: \"select 'line2'\",\n            trigger: \".o_bank_rec_st_line:contains('line2')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_bank_rec_selected_st_line:contains('line2')\",\n        },\n        {\n            content: \"Validate 'line2' again\",\n            trigger: \"button:contains('Validate')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_bank_rec_selected_st_line:contains('line3')\",\n        },\n        {\n            content: \"'line3' should be selected now\",\n            trigger: \".o_bank_rec_selected_st_line:contains('line3')\",\n        },\n        // Test the Balance when changing journal and liquidity line\n        ...stepUtils.toggleHomeMenu(),\n        ...accountTourSteps.goToAccountMenu(\"Reset back to accounting module\"),\n        {\n            trigger: \".o_breadcrumb\",\n        },\n        {\n            content: \"Open the bank reconciliation widget for Bank2\",\n            trigger: \"button.btn-secondary[name='action_open_reconcile']:last()\",\n            run: \"click\",\n        },\n        {\n            content: \"Remove the kanbans 'not matched' filter\",\n            trigger: \".o_kanban_view .o_searchview_facet:nth-child(2) .o_facet_remove\",\n            run: \"click\",\n        },\n        {\n            content: \"Remove the kanban 'journal' filter\",\n            trigger: \".o_kanban_view .o_searchview_facet:nth-child(1) .o_facet_remove\",\n            run: \"click\",\n        },\n        {\n            content: \"select 'line1' from another journal\",\n            trigger: \".o_bank_rec_st_line:contains('line1')\",\n            run: \"click\",\n        },\n        ...accountTourSteps.bankRecUiReportSteps(),\n        {\n            content: \"select 'line4' from this journal\",\n            trigger: \".o_bank_rec_st_line:contains('line4')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_bank_rec_selected_st_line:contains('line4')\",\n        },\n        {\n            content: \"balance is $222.22\",\n            trigger: \".btn-link:contains('$ 222.22')\",\n        },\n        {\n            content: \"Select the liquidity line\",\n            trigger: \"tr.o_bank_rec_liquidity_line td[field='debit']\",\n            run: \"click\",\n        },\n        {\n            trigger: \"div[name='amount_currency'] input:focus-within\",\n        },\n        {\n            content: \"Modify the liquidity line amount\",\n            trigger: \"div[name='amount_currency'] input\",\n            run: \"edit -333.33 && click body\",\n        },\n        {\n            trigger: \".btn-link:contains('$ -333.33')\",\n        },\n        {\n            content: \"balance displays $-333.33\",\n            trigger: \".btn-link:contains('$ -333.33')\",\n        },\n        {\n            content: \"Modify the label\",\n            trigger: \"div[name='name'] input\",\n            run: \"edit Spontaneous Combustion && click body\",\n        },\n        {\n            content: \"statement line displays combustion and $-333.33\",\n            trigger: \".o_bank_rec_selected_st_line:contains('Combustion'):contains('$ -333.33')\",\n        },\n        // Test that changing the balance in the list view updates the right side of the kanban view\n        // (including reapplying matching rules)\n        {\n            content: \"select matched 'line2'\",\n            trigger: \".o_bank_rec_st_line:contains('line2')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_bank_rec_selected_st_line:contains('line2')\",\n        },\n        {\n            content: \"'line2' should be selected, reset it\",\n            trigger: \"button:contains('Reset')\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_bank_rec_selected_st_line:contains('line2'):not(:has(div.badge))\",\n        },\n        {\n            content: \"Liquidity line displays debit '$ 100.00'\",\n            trigger:\n                \"div[name='line_ids'] table.o_list_table tr.o_bank_rec_liquidity_line td[field='debit']:contains('$ 100.00')\",\n        },\n        {\n            trigger: \".o_switch_view.o_kanban.active\",\n        },\n        {\n            content: \"Switch to list view\",\n            trigger: \".o_switch_view.o_list\",\n            run: \"click\",\n        },\n        {\n            content: \"Click amount field of 'line2'; Selects the row\",\n            trigger: \"table.o_list_table tr.o_data_row:contains('line2') td[name='amount']\",\n            run: \"click\",\n        },\n        {\n            content: \"Set balance of 'line2' (selected row) to 500.00\",\n            trigger: \"table.o_list_table tr.o_data_row.o_selected_row td[name='amount'] input\",\n            run: \"edit 500.00 && click body\",\n        },\n        {\n            trigger: \".o_switch_view.o_list.active\",\n        },\n        {\n            content: \"Switch back to kanban\",\n            trigger: \".o_switch_view.o_kanban\",\n            run: \"click\",\n        },\n        {\n            content: \"'line2' is still selected\",\n            trigger: \".o_bank_rec_st_line:contains('line2')\",\n        },\n        {\n            content: \"Liquidity line displays debit '$ 500.00'\",\n            trigger:\n                \"div[name='line_ids'] table.o_list_table tr.o_bank_rec_liquidity_line td[field='debit']:contains('$ 500.00')\",\n        },\n        {\n            content:\n                \"'INV/2019/00001' has been selected as matching existing entry by matching rules\",\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table tr.o_rec_widget_list_selected_item td[name='name']:contains('INV/2019/00001')\",\n        },\n        // End\n        ...stepUtils.toggleHomeMenu(),\n        ...accountTourSteps.goToAccountMenu(\"Reset back to accounting module\"),\n        {\n            content: \"check that we're back on the dashboard\",\n            trigger: 'a:contains(\"Customer Invoices\")',\n        },\n    ],\n});\n\nregistry.category(\"web_tour.tours\").add('account_accountant_bank_rec_widget_reconciliation_button',\n    {\n        url: '/odoo',\n        steps: () => [\n        stepUtils.showAppsMenuItem(),\n        ...accountTourSteps.goToAccountMenu(\"Open the accounting module\"),\n        {\n            content: \"Open the bank reconciliation widget\",\n            trigger: \"button.btn-secondary[name='action_open_reconcile']\",\n            run: \"click\",\n        },\n        {\n            content: \"Remove suggested line, if present\",\n            trigger: \".o_list_record_remove\",\n            run() {\n                const button = document.querySelector('.fa-trash-o');\n                if(button) {\n                    button.click();\n                }\n            }\n        },\n        {\n            content: \"Wait for deletion\",\n            trigger: \".o_data_row:contains('Open balance')\",\n        },\n        {\n            content: \"Select reconciliation model creating a new move\",\n            trigger: \".recon_model_button:contains('test reconcile')\",\n            run: \"click\",\n        },\n        {\n            content: \"Confirm move created through reconciliation model writeoff button\",\n            trigger: \"button[name=action_post]\",\n            run: \"click\",\n        },\n        {\n            trigger: \".o_breadcrumb\",\n        },\n        {\n            content: \"Breadcrumb back to Bank Reconciliation from created move\",\n            trigger: \".breadcrumb-item:contains('Bank Reconciliation')\",\n            run: \"click\",\n        },\n        {\n            content: \"Validate created move added as a line in reco widget\",\n            trigger: \"button:contains('Validate')\",\n            run: \"click\",\n        },\n        // End\n        ...stepUtils.toggleHomeMenu(),\n        ...accountTourSteps.goToAccountMenu(\"Reset back to accounting module\"),\n        {\n            content: \"check that we're back on the dashboard\",\n            trigger: 'a:contains(\"Customer Invoices\")',\n            run() {},\n        },\n    ],\n});\n", "/** @odoo-module **/\n\nimport { registry } from \"@web/core/registry\";\nimport { stepUtils } from \"@web_tour/tour_service/tour_utils\";\nimport { accountTourSteps } from \"@account/js/tours/account\";\n\nregistry.category(\"web_tour.tours\").add(\"account_accountant_bank_rec_widget\", {\n    url: \"/odoo\",\n    steps: () => [\n        stepUtils.showAppsMenuItem(),\n        ...accountTourSteps.goToAccountMenu(\"Open the accounting module\"),\n\n        // Open the widget. The first line should be selected by default.\n        {\n            trigger: \".o_breadcrumb\",\n        },\n        {\n            content: \"Open the bank reconciliation widget\",\n            trigger: \"button.btn-secondary[name='action_open_reconcile']\",\n            run: \"click\",\n        },\n        {\n            trigger: \"div[name='line_ids']\",\n        },\n        {\n            content: \"The 'line1' should be selected by default\",\n            trigger: \"div[name='line_ids'] td[field='name']:contains('line1')\",\n        },\n\n        // Test 1: Check the loading of lazy notebook tabs.\n        // Check 'amls_tab' (active by default).\n        {\n            trigger: \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table\",\n        },\n        {\n            content: \"The 'amls_tab' should be active and the inner list view loaded\",\n            trigger: \"a.active[name='amls_tab']\",\n        },\n        // Check 'discuss_tab'.\n        {\n            trigger: \"a.active[name='amls_tab']\",\n        },\n        {\n            content: \"Click on the 'discuss_tab'\",\n            trigger: \"a[name='discuss_tab']\",\n            run: \"click\",\n        },\n        {\n            trigger: \"a.active[name='discuss_tab']\",\n        },\n        {\n            content: \"The 'discuss_tab' should be active and the chatter loaded\",\n            trigger: \"div.bank_rec_widget_form_discuss_anchor div.o-mail-Chatter\",\n        },\n        // Check 'manual_operations_tab'.\n        {\n            trigger: \"tr.o_bank_rec_auto_balance_line\",\n        },\n        {\n            content: \"Click on the 'auto_balance' to make the 'manual_operations_tab' visible\",\n            trigger: \"tr.o_bank_rec_auto_balance_line td[field='name']\",\n            run: \"click\",\n        },\n        {\n            content: \"The 'manual_operations_tab' should be active\",\n            trigger: \"a.active[name='manual_operations_tab']\",\n        },\n        {\n            content: \"The 'name' field should be focus automatically\",\n            trigger: \"div.o_notebook div[name='name'] input:focus\",\n        },\n        {\n            trigger: \"tr.o_bank_rec_auto_balance_line\",\n        },\n        {\n            content: \"Click on the 'credit' field to change the focus from 'name' to 'amount_currency'\",\n            trigger: \"tr.o_bank_rec_auto_balance_line td[field='credit']\",\n            run: \"click\",\n        },\n        {\n            content: \"Wait to avoid non-deterministic errors on the next step\",\n            trigger: \"tr.o_bank_rec_auto_balance_line td[field='credit']\",\n        },\n        {\n            content: \"The 'balance' field should be focus now\",\n            trigger: \"div.o_notebook div[name='amount_currency'] input:focus\",\n        },\n\n        // Test 2: Test validation + auto select the next line.\n        {\n            trigger: \"a.active[name='manual_operations_tab']\",\n        },\n        {\n            content: \"Click on the 'amls_tab'\",\n            trigger: \"a[name='amls_tab']\",\n            run: \"click\",\n        },\n        {\n            trigger: \"a.active[name='amls_tab']\",\n        },\n        {\n            content: \"Mount INV/2019/00002\",\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table td[name='move_id']:contains('INV/2019/00002')\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table tr.o_rec_widget_list_selected_item td[name='move_id']:contains('INV/2019/00002')\",\n        },\n        {\n            content: \"Check INV/2019/00002 is well marked as selected\",\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table tr.o_rec_widget_list_selected_item td[name='move_id']:contains('INV/2019/00002')\",\n        },\n        {\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table tr.o_rec_widget_list_selected_item td[name='move_id']:contains('INV/2019/00002')\",\n        },\n        {\n            content: \"Remove INV/2019/00002\",\n            trigger: \"tr td.o_list_record_remove button\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table tr:not(.o_rec_widget_list_selected_item) td[name='move_id']:contains('INV/2019/00002')\",\n        },\n        {\n            content: \"Mount INV/2019/00001\",\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table td[name='move_id']:contains('INV/2019/00001')\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \"div.bank_rec_widget_form_amls_list_anchor table.o_list_table tr.o_rec_widget_list_selected_item td[name='move_id']:contains('INV/2019/00001')\",\n        },\n        {\n            content: \"Validate\",\n            trigger: \"button:contains('Validate')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"div[name='line_ids'] td[field='name']:contains('line2')\",\n        },\n        {\n            content: \"The 'line2' is the next not already reconciled line\",\n            trigger: \"div[name='line_ids'] td[field='name']:contains('line2')\",\n        },\n\n        // Test 3: Test manual operations tab.\n        {\n            content: \"Click on 'credit'\",\n            trigger: \"div[name='line_ids'] td[field='credit']:last\",\n            run: \"click\",\n        },\n        {\n            content:\n                \"The 'manual_operations_tab' should be active now and the auto_balance line mounted in edit\",\n            trigger: \"a.active[name='manual_operations_tab']\",\n        },\n        {\n            content: \"The last line should be selected\",\n            trigger: \"div[name='line_ids'] tr.o_bank_rec_selected_line\",\n        },\n        {\n            content: \"Search for 'partner_a'\",\n            trigger: \"div[name='partner_id'] input\",\n            run: \"edit partner_a\",\n        },\n        {\n            trigger: \".ui-autocomplete .o_m2o_dropdown_option a:contains('Create')\",\n        },\n        {\n            content: \"Select 'partner_a'\",\n            trigger: \".ui-autocomplete:visible li:contains('partner_a')\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \"tr:not(.o_bank_rec_auto_balance_line) td[field='partner_id']:contains('partner_a')\",\n        },\n        {\n            content: \"Select the payable account\",\n            trigger: \"button:contains('Payable')\",\n            run: \"click\",\n        },\n        {\n            trigger:\n                \"tr:not(.o_bank_rec_auto_balance_line) td[field='account_id']:contains('Payable')\",\n        },\n        {\n            content: \"Enter a tax\",\n            trigger: \"div[name='tax_ids'] input\",\n            run: \"edit 15\",\n        },\n        {\n            trigger: \".ui-autocomplete\",\n        },\n        {\n            content: \"Select 'Tax 15% (Sales)'\",\n            trigger: \".ui-autocomplete:visible li:contains('Sales')\",\n            run: \"click\",\n        },\n        {\n            content: \"Tax column appears in list of lines\",\n            trigger: \"div[name='line_ids'] td[field='tax_ids']\",\n        },\n        {\n            content: \"Wait to avoid non-deterministic errors on the next step\",\n            trigger: \"div[name='line_ids'] td:contains('Tax Received')\",\n        },\n        {\n            trigger: \"button.btn-primary:contains('Validate')\",\n        },\n        {\n            content: \"Validate\",\n            trigger: \"button:contains('Validate')\",\n            run: \"click\",\n        },\n        {\n            trigger: \"div[name='line_ids'] td[field='name']:contains('line3')\",\n        },\n        {\n            content: \"The 'line3' is the next not already reconciled line\",\n            trigger: \"div[name='line_ids'] td[field='name']:contains('line3')\",\n        },\n        ...stepUtils.toggleHomeMenu(),\n        ...accountTourSteps.goToAccountMenu(\"Reset back to accounting module\"),\n        {\n            content: \"check that we're back on the dashboard\",\n            trigger: 'a:contains(\"Customer Invoices\")',\n        },\n    ],\n});\n", "/** @odoo-module **/\n\nimport { queryAll } from \"@odoo/hoot-dom\";\nimport { rpc } from \"@web/core/network/rpc\";\nimport { registry } from \"@web/core/registry\";\nimport { stepUtils } from \"@web_tour/tour_service/tour_utils\";\n\nfunction openRoot() {\n    return [{\n        content: \"return to client root to avoid race condition\",\n        trigger: 'body',\n        run() {\n            document.querySelector(\"body\").classList.add(\"wait\");\n            window.location = '/odoo';\n        }\n    }, {\n        content: \"wait for client reload\",\n        trigger: 'body:not(.wait)',\n    }];\n}\nfunction openUserProfileAtSecurityTab() {\n    return [{\n        content: 'Open user account menu',\n        trigger: '.o_user_menu .dropdown-toggle',\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(helpers) {\n            const modal = document.querySelector(\".o_dialog\");\n            if (modal) {\n                modal.querySelector(\"button[name=preference_cancel]\").click();\n            }\n        }\n    }, {\n        trigger: 'body',\n        async run() {\n            while (document.querySelector('.o_dialog')) {\n                await Promise.resolve();\n            }\n            this.anchor.classList.add(\"dialog-closed\");\n        },\n    }, {\n        trigger: 'body.dialog-closed',\n    }];\n}\n\nregistry.category(\"web_tour.tours\").add('totp_tour_setup', {\n    url: '/odoo',\n    steps: () => [...openUserProfileAtSecurityTab(), {\n    content: \"Open totp wizard\",\n    trigger: 'button[name=action_totp_enable_wizard]',\n    run: \"click\",\n},\n{\n    trigger: \".modal div:contains(entering your password)\",\n},\n{\n    content: \"Check that we have to enter enhanced security mode and input password\",\n    trigger: '[name=password] input',\n    run: 'edit test_user',\n}, {\n    content: \"Confirm\",\n    trigger: \"button:contains(Confirm Password)\",\n    run: \"click\",\n}, {\n    content: \"Check the wizard has opened\",\n    trigger: '.modal li:contains(\"When requested to do so\")',\n}, {\n    content: \"Get secret from collapsed div\",\n    trigger: `.modal a:contains(\"Cannot scan it?\")`,\n    async run(helpers) {\n        const secret = this.anchor\n            .closest(\"div\")\n            .querySelector(\"[name=secret] span:first-child\");\n        const copyBtn = secret.querySelector(\"button\");\n        if (copyBtn) {\n            copyBtn.remove();\n        }\n        const token = await rpc('/totphook', {\n            secret: secret.textContent\n        });\n        await helpers.edit(token, '[name=code] input');\n        document.querySelector(\"body\").classList.add(\"got-token\");\n    }\n},\n{\n    trigger: \".modal button.btn-primary:contains(Activate)\",\n    run: \"click\",\n},\n{\n    trigger: \"body:not(:has(.modal))\",\n},\n{\n    content: 'wait for rpc',\n    trigger: 'body.got-token',\n},\n...openRoot(),\n...openUserProfileAtSecurityTab(),\n...closeProfileDialog({\n    content: \"Check that the button has changed\",\n    totp_state: true,\n}),\n]});\n\nregistry.category(\"web_tour.tours\").add('totp_login_enabled', {\n    url: '/',\n    steps: () => [{\n    content: \"check that we're on the login page or go to it\",\n    trigger: 'input#login, a:contains(Sign in)',\n    run: \"click\",\n}, {\n    content: \"input login\",\n    trigger: 'input#login',\n    run: \"edit test_user\",\n}, {\n    content: 'input password',\n    trigger: 'input#password',\n    run: \"edit test_user\",\n}, {\n    content: \"click da button\",\n    trigger: 'button:contains(\"Log in\")',\n    run: \"click\",\n}, {\n    content: \"expect totp screen\",\n    trigger: 'label:contains(Authentication Code)',\n    run: \"click\",\n}, {\n    content: \"input code\",\n    trigger: 'input[name=totp_token]',\n    async run(helpers) {\n        const token = await rpc('/totphook');\n        helpers.edit(token);\n    }\n},\n{\n    trigger: `button:contains(\"Log in\")`,\n    run: \"click\",\n},\n{\n    content: \"check we're logged in\",\n    trigger: \".o_user_menu .dropdown-toggle\",\n}]});\n\nregistry.category(\"web_tour.tours\").add('totp_login_device', {\n    url: '/',\n    steps: () => [{\n    content: \"check that we're on the login page or go to it\",\n    trigger: 'input#login, a:contains(Sign in)',\n    run: \"click\",\n}, {\n    content: \"input login\",\n    trigger: 'input#login',\n    run: \"edit test_user\",\n}, {\n    content: 'input password',\n    trigger: 'input#password',\n    run: \"edit test_user\",\n}, {\n    content: \"click da button\",\n    trigger: 'button:contains(\"Log in\")',\n    run: \"click\",\n}, {\n    content: \"expect totp screen\",\n    trigger: 'label:contains(Authentication Code)',\n    run: \"click\",\n}, {\n    content: \"check remember device box\",\n    trigger: 'label[for=switch-remember]',\n    run: \"click\",\n}, {\n    content: \"input code\",\n    trigger: 'input[name=totp_token]',\n    async run(helpers) {\n        const token = await rpc('/totphook')\n        helpers.edit(token);\n    }\n},\n{\n    trigger: \"button:contains(Log in)\",\n    run: \"click\",\n},\n{\n    content: \"check we're logged in\",\n    trigger: \".o_user_menu .dropdown-toggle\",\n    run: 'click',\n}, {\n    content: \"click the Log out button\",\n    trigger: '.dropdown-item[data-menu=logout]',\n    run: \"click\",\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    run: \"edit Test\",\n}, {\n    content: \"input login again\",\n    trigger: 'input#login',\n    run: \"edit test_user\",\n}, {\n    content: 'input password again',\n    trigger: 'input#password',\n    run: \"edit test_user\",\n}, {\n    content: \"click da button again\",\n    trigger: 'button:contains(\"Log in\")',\n    run: \"click\",\n},  {\n    content: \"check we're logged in without 2FA\",\n    trigger: \".o_user_menu .dropdown-toggle\",\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    run: \"click\",\n},\n{\n    trigger: \".modal div:contains(entering your password)\",\n},\n{\n    content: \"Check that we have to enter enhanced security mode and input password\",\n    trigger: '.modal [name=password] input',\n    run: \"edit test_user\",\n}, {\n    content: \"Confirm\",\n    trigger: \".modal button:contains(Confirm Password)\",\n    run: \"click\",\n},\n{\n    trigger: \"body:not(:has(.modal))\",\n},\n...openRoot(),\n...openUserProfileAtSecurityTab(),\n...closeProfileDialog({\n    content: \"Check that the button has changed\",\n    totp_state: false\n}),\n]});\n\nregistry.category(\"web_tour.tours\").add('totp_login_disabled', {\n    url: '/',\n    steps: () => [{\n    content: \"check that we're on the login page or go to it\",\n    trigger: 'input#login, a:contains(Sign in)',\n    run: \"click\",\n}, {\n    content: \"input login\",\n    trigger: 'input#login',\n    run: \"edit test_user\",\n}, {\n    content: 'input password',\n    trigger: 'input#password',\n    run: \"edit test_user\",\n}, {\n    content: \"click da button\",\n    trigger: 'button:contains(\"Log in\")',\n    run: \"click\",\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 = {};\nregistry.category(\"web_tour.tours\").add('totp_admin_disables', {\n    url: '/odoo',\n    steps: () => [stepUtils.showAppsMenuItem(), {\n    content: 'Go to settings',\n    trigger: '[data-menu-xmlid=\"base.menu_administration\"]',\n    run: \"click\",\n}, {\n    content: 'Wait for page',\n    trigger: '.o_menu_brand:contains(\"Settings\")',\n}, {\n    content: \"Open Users menu\",\n    trigger: '[data-menu-xmlid=\"base.menu_users\"]',\n    run: \"click\",\n}, {\n    content: \"Open Users view\",\n    trigger: '[data-menu-xmlid=\"base.menu_action_res_users\"]',\n    run: \"click\",\n}, {\n    content: \"Find test_user User\",\n    trigger: 'td.o_data_cell:contains(\"test_user\")',\n    run(helpers) {\n        const titles = queryAll(\"tr:first th\", { root: this.anchor.closest(\"table\") });\n        titles.forEach((el, i) => {\n            columns[el.getAttribute('data-name')] = i;\n        })\n        const row = this.anchor.closest('tr');\n        const sel = row.querySelector('.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    run: \"click\",\n}, {\n    content: \"Select totp remover\",\n    trigger: 'span.dropdown-item:contains(Disable two-factor authentication)',\n    run: \"click\",\n},\n{\n    trigger: \".modal div:contains(entering your password)\",\n},\n{ // enhanced security yo\n    content: \"Check that we have to enter enhanced security mode & input password\",\n    trigger: '.modal [name=password] input',\n    run: \"edit admin\",\n}, {\n    content: \"Confirm\",\n    trigger: \".modal button:contains(Confirm Password)\",\n    run: \"click\",\n},\n{\n    content: \"Wait the modal is closed\",\n    trigger: \"body:not(:has(.modal))\",\n},\n{\n    content: \"open the user's form\",\n    trigger: \"td.o_data_cell:contains(test_user)\",\n    run: \"click\",\n}, {\n    content: \"go to Account security Tab\",\n    trigger: \"a.nav-link:contains(Account Security)\",\n    run: \"click\",\n}, ...closeProfileDialog({\n    content: \"check that test_user user has been de-totp'd\",\n    totp_state: false,\n}),\n]})\n", "/** @odoo-module **/\n\nimport { registry } from \"@web/core/registry\";\nimport { stepUtils } from \"@web_tour/tour_service/tour_utils\";\nimport { queryFirst } from \"@odoo/hoot-dom\";\n\nfunction openAccountSettingsTab() {\n    return [{\n        content: 'Go to settings',\n        trigger: '[data-menu-xmlid=\"base.menu_administration\"]',\n        run: \"click\",\n    }, {\n        content: 'Wait for page',\n        trigger: '.o_menu_brand:contains(\"Settings\")',\n    }, {\n        content: \"Open Users menu\",\n        trigger: '[data-menu-xmlid=\"base.menu_users\"]',\n        run: \"click\",\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 breadcrumb = document.querySelector('.breadcrumb');\n            if (!breadcrumb || !breadcrumb.textContent.includes(\"Users\")) {\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\nregistry.category(\"web_tour.tours\").add('totp_admin_self_invite', {\n    url: '/odoo',\n    steps: () => [stepUtils.showAppsMenuItem(), ...openAccountSettingsTab(), {\n    content: \"open the user's form\",\n    trigger: \"td.o_data_cell:contains(admin)\",\n    run: \"click\",\n}, {\n    content: \"go to Account security Tab\",\n    trigger: \"a.nav-link:contains(Account Security)\",\n    run: \"click\",\n}, {\n    content: \"check that user cannot invite themselves to use 2FA.\",\n    trigger: \"body\",\n    run: function () {\n        const inviteBtn = queryFirst('button:contains(Invite to use 2FA)');\n        if (!inviteBtn) {\n            document.body.classList.add('CannotInviteYourself');\n        }\n    }\n}, {\n    content: \"check that user cannot invite themself.\",\n    trigger: \"body.CannotInviteYourself\",\n}]});\n\nregistry.category(\"web_tour.tours\").add('totp_admin_invite', {\n    url: '/odoo',\n    steps: () => [stepUtils.showAppsMenuItem(), ...openAccountSettingsTab(), {\n    content: \"open the user's form\",\n    trigger: \"td.o_data_cell:contains(test_user)\",\n    run: \"click\",\n}, {\n    content: \"go to Account security Tab\",\n    trigger: \"a.nav-link:contains(Account Security)\",\n    run: \"click\",\n}, {\n    content: \"check that test_user user can be invited to use 2FA.\",\n    trigger: \"button:contains(Invite to use 2FA)\",\n}]});\n", "/** @odoo-module **/\n\nimport { registry } from \"@web/core/registry\";\nimport { rpc } from \"@web/core/network/rpc\";\n\nregistry.category(\"web_tour.tours\").add('totportal_tour_setup', {\n    url: '/my/security',\n    steps: () => [{\n    content: \"Open totp wizard\",\n    trigger: 'button#auth_totp_portal_enable',\n    run: \"click\",\n}, {\n    content: \"Check that we have to enter enhanced security mode\",\n    trigger: \".modal div:contains(enter your password)\",\n}, {\n    content: \"Input password\",\n    trigger: '[name=password]',\n    run: \"edit portal\", // FIXME: better way to do this?\n}, {\n    content: \"Confirm\",\n    trigger: \"button:contains(Confirm Password)\",\n    run: \"click\",\n}, {\n    content: \"Check the wizard has opened\",\n    trigger: 'li:contains(\"scan the barcode below\")',\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\n            .closest(\"div\")\n            .querySelector('span[name=\"secret\"]').textContent;\n        const token = await rpc('/totphook', {\n            secret\n        });\n        await helpers.edit(token, 'input[name=\"code\"]');\n        await helpers.click(\"button.btn-primary:contains(Activate)\");\n    }\n}, {\n    content: \"Check that the button has changed\",\n    trigger: 'button:contains(Disable two-factor authentication)',\n}]});\n\nregistry.category(\"web_tour.tours\").add('totportal_login_enabled', {\n    url: '/',\n    steps: () => [{\n    content: \"check that we're on the login page or go to it\",\n    trigger: 'input#login, a:contains(Sign in)',\n    run: \"click\",\n}, {\n    content: \"input login\",\n    trigger: 'input#login',\n    run: \"edit portal\",\n}, {\n    content: 'input password',\n    trigger: 'input#password',\n    run: \"edit portal\",\n}, {\n    content: \"click da button\",\n    trigger: 'button:contains(\"Log in\")',\n    run: \"click\",\n}, {\n    content: \"expect totp screen\",\n    trigger: 'label:contains(Authentication Code)',\n    run: \"click\",\n}, {\n    content: \"input code\",\n    trigger: 'input[name=totp_token]',\n    run: async function (helpers) {\n        const token = await rpc('/totphook');\n        await helpers.edit(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        await helpers.click('button:contains(\"Log in\")');\n    }\n}, {\n    content: \"check we're logged in\",\n    trigger: \"h3:contains(My account)\",\n}, {\n    content: \"go back to security\",\n    trigger: \"a:contains(Security)\",\n    run: \"click\",\n},{\n    content: \"Open totp wizard\",\n    trigger: 'button#auth_totp_portal_disable',\n    run: \"click\",\n}, {\n    content: \"Check that we have to enter enhanced security mode\",\n    trigger: \".modal div:contains(enter your password)\",\n}, {\n    content: \"Input password\",\n    trigger: '[name=password]',\n    run: \"edit portal\", // FIXME: better way to do this?\n}, {\n    content: \"Confirm\",\n    trigger: \"button:contains(Confirm Password)\",\n    run: \"click\",\n}, {\n    content: \"Check that the button has changed\",\n    trigger: 'button:contains(Enable two-factor authentication)',\n}]});\n\nregistry.category(\"web_tour.tours\").add('totportal_login_disabled', {\n    url: '/',\n    steps: () => [{\n    content: \"check that we're on the login page or go to it\",\n    trigger: 'input#login, a:contains(Sign in)',\n    run: \"click\",\n}, {\n    content: \"input login\",\n    trigger: 'input#login',\n    run: \"edit portal\",\n}, {\n    content: 'input password',\n    trigger: 'input#password',\n    run: \"edit portal\",\n}, {\n    content: \"click da button\",\n    trigger: 'button:contains(\"Log in\")',\n    run: \"click\",\n}, {\n    content: \"check we're logged in\",\n    trigger: \"h3:contains(My account)\",\n}]});\n", "/** @odoo-module **/\n\nimport { registry } from \"@web/core/registry\";\nimport { stepUtils } from \"@web_tour/tour_service/tour_utils\";\n\nregistry.category(\"web_tour.tours\").add('custom_content_kanban_like_tour', {\n    steps: () => [\n        {\n            trigger: \"ul.nav a:contains(Quote Builder)\",\n            run: \"click\",\n        },\n        {\n            trigger: \"label:contains(Header)\",\n            run: \"click\",\n        },\n        {\n            trigger: \"h5:contains(custom_1) ~ div textarea\",\n            break: true,\n            run: \"edit Test\",\n        },\n        ...stepUtils.saveForm(),\n        // TODO VCR: Finish this\n    ]\n});\n"], "file": "/web/assets/3b5203b/web.assets_tests.js", "sourceRoot": "../../../"}