Remove obsolete tests and versioning files for odex30_web
- Deleted home_menu.test.js and webclient_tests.js as they are no longer needed. - Removed the test initialization file and the associated test_odex.py file. - Eliminated version.py to clean up the versioning process.
This commit is contained in:
parent
049eed2495
commit
bc4a5b32dd
|
|
@ -1,457 +0,0 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * odex30_web
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 18.0+e\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-01 22:17+0000\n"
|
||||
"PO-Revision-Date: 2026-01-01 22:17+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.js:0
|
||||
msgid "%s days"
|
||||
msgstr "%s أيام"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/settings_form_view/res_config_edition.xml:0
|
||||
msgid "(Enterprise Edition)"
|
||||
msgstr "(النسخة الشركاتية)"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.js:0
|
||||
msgid "1 month"
|
||||
msgstr "شهر واحد"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/views/list/list_renderer_desktop.xml:0
|
||||
msgid "Add Custom Field"
|
||||
msgstr "إضافة حقل مخصص"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/views/kanban/kanban_header_patch.js:0
|
||||
msgid "Automations"
|
||||
msgstr "الأتمتة"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/promote_studio_dialog/promote_studio_dialog.xml:0
|
||||
msgid "Build new apps from scratch"
|
||||
msgstr "بناء تطبيقات جديدة من الصفر"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/promote_studio_dialog/promote_studio_dialog.xml:0
|
||||
msgid "Build new reports"
|
||||
msgstr "بناء تقارير جديدة"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/share_url/burger_menu.xml:0
|
||||
msgid "Close menu"
|
||||
msgstr "إغلاق القائمة"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid ""
|
||||
"Contact your sales representative to help you to unlink your previous "
|
||||
"database"
|
||||
msgstr ""
|
||||
"تواصل مع مندوب المبيعات لمساعدتك في فصل قاعدة البيانات السابقة"
|
||||
""
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/promote_studio_dialog/promote_studio_dialog.xml:0
|
||||
msgid "Create automation rules"
|
||||
msgstr "إنشاء قواعد الأتمتة"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/promote_studio_dialog/promote_studio_dialog.xml:0
|
||||
msgid "Customize Reports"
|
||||
msgstr "تخصيص التقارير"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/promote_studio_dialog/promote_studio_dialog.xml:0
|
||||
msgid "Customize any screen"
|
||||
msgstr "تخصيص أي شاشة"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/color_scheme/color_scheme_menu_items.js:0
|
||||
msgid "Dark Mode"
|
||||
msgstr "الوضع الداكن"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/settings_form_view/res_config_edition.xml:0
|
||||
msgid "Database expiration:"
|
||||
msgstr "انتهاء صلاحية قاعدة البيانات:"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/promote_studio_dialog/promote_studio_dialog.xml:0
|
||||
msgid "Define webhooks"
|
||||
msgstr "تعريف Webhooks"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/promote_studio_dialog/promote_studio_dialog.xml:0
|
||||
msgid "Discard"
|
||||
msgstr "إلغاء"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid "Dismiss"
|
||||
msgstr "إغلاق"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid "Error reason:"
|
||||
msgstr "سبب الخطأ:"
|
||||
|
||||
#. module: odex30_web
|
||||
#: model:ir.model,name:odex30_web.model_ir_http
|
||||
msgid "HTTP Routing"
|
||||
msgstr "مسار HTTP"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/home_menu_service.js:0
|
||||
msgid "Home"
|
||||
msgstr "الرئيسية"
|
||||
|
||||
#. module: odex30_web
|
||||
#: model:ir.model.fields,field_description:odex30_web.field_res_users_settings__homemenu_config
|
||||
msgid "Home Menu Configuration"
|
||||
msgstr "إعدادات قائمة الرئيسية"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/navbar/navbar.js:0
|
||||
msgid "Home menu"
|
||||
msgstr "قائمة الرئيسية"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid "I paid, please recheck!"
|
||||
msgstr "لقد دفعت، يرجى إعادة التحقق!"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/promote_studio_dialog/promote_studio_dialog.xml:0
|
||||
msgid "Install Odoo Studio and its dependencies"
|
||||
msgstr "تثبيت Odoo Studio وتبعياته"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/promote_studio_dialog/promote_studio_dialog.xml:0
|
||||
msgid "Learn More"
|
||||
msgstr "تعرف على المزيد"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid "Log in as an administrator to correct the issue."
|
||||
msgstr "سجل الدخول كمدير لإصلاح المشكلة."
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/home_menu.xml:0
|
||||
msgid "No result"
|
||||
msgstr "لا توجد نتائج"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/settings_form_view/res_config_edition.xml:0
|
||||
msgid "Odoo"
|
||||
msgstr "أودو"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/settings_form_view/res_config_edition.xml:0
|
||||
msgid "Odoo Enterprise Edition License V1.0"
|
||||
msgstr "رخصة Odoo Enterprise Edition V1.0"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/views/list/list_renderer_desktop.js:0
|
||||
msgid "Odoo Studio - Add new fields to any view"
|
||||
msgstr "Odoo Studio - إضافة حقول جديدة إلى أي عرض"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/views/kanban/kanban_header_patch.js:0
|
||||
msgid "Odoo Studio - Customize workflows in minutes"
|
||||
msgstr "Odoo Studio - تخصيص سير العمل في دقائق"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid "Odoo Support"
|
||||
msgstr "دعم أودو"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid "Paste code here"
|
||||
msgstr "الصق الكود هنا"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/navbar/navbar.js:0
|
||||
msgid "Previous view"
|
||||
msgstr "العرض السابق"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.js:0
|
||||
msgid "Register"
|
||||
msgstr "تسجيل"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid "Register your subscription"
|
||||
msgstr "تسجيل اشتراكك"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid "Renew now"
|
||||
msgstr "تجديد الآن"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.js:0
|
||||
msgid "Retry"
|
||||
msgstr "إعادة المحاولة"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid "Send an email"
|
||||
msgstr "إرسال بريد إلكتروني"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid "Sending the instructions by email ..."
|
||||
msgstr "إرسال التعليمات بالبريد الإلكتروني..."
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/share_url/share_url.js:0
|
||||
msgid "Share"
|
||||
msgstr "مشاركة"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/share_url/burger_menu.xml:0
|
||||
msgid "Share URL"
|
||||
msgstr "مشاركة الرابط"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid ""
|
||||
"Something went wrong while registering your database. You can try again or "
|
||||
"contact"
|
||||
msgstr ""
|
||||
"حدث خطأ أثناء تسجيل قاعدة البيانات. يمكنك المحاولة مرة أخرى أو التواصل"
|
||||
" مع"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/promote_studio_dialog/promote_studio_dialog.xml:0
|
||||
msgid "Start using Odoo Studio"
|
||||
msgstr "ابدأ استخدام Odoo Studio"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid "Subscription Code:"
|
||||
msgstr "كود الاشتراك:"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/home_menu.xml:0
|
||||
msgid "TIP"
|
||||
msgstr "نصيحة"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid ""
|
||||
"Thank you, your registration was successful! Your database is valid until"
|
||||
msgstr ""
|
||||
"شكراً، تم تسجيلك بنجاح! قاعدة بياناتك صالحة حتى"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/enterprise_subscription_service.js:0
|
||||
msgid ""
|
||||
"Thank you, your registration was successful! Your database is valid until "
|
||||
"%s."
|
||||
msgstr ""
|
||||
"شكراً، تم تسجيلك بنجاح! قاعدة بياناتك صالحة حتى %s."
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid ""
|
||||
"The instructions to unlink your subscription from the previous database(s) "
|
||||
"have been sent"
|
||||
msgstr ""
|
||||
"تم إرسال التعليمات لفصل اشتراكك من قواعد البيانات السابقة"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.js:0
|
||||
msgid "This database has expired. "
|
||||
msgstr "انتهت صلاحية هذه قاعدة البيانات. "
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.js:0
|
||||
msgid "This database will expire in %s. "
|
||||
msgstr "ستنتهي صلاحية قاعدة البيانات هذه في %s. "
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.js:0
|
||||
msgid "This demo database will expire in %s. "
|
||||
msgstr "ستنتهي صلاحية قاعدة بيانات العرض التجريبي في %s. "
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid "Unable to send the instructions by email, please contact the"
|
||||
msgstr "تعذر إرسال التعليمات بالبريد الإلكتروني، يرجى التواصل مع"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/promote_studio_dialog/promote_studio_dialog.xml:0
|
||||
msgid "Unleash the power of Odoo Studio:"
|
||||
msgstr "استخدم قوة Odoo Studio:"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid "Upgrade your subscription"
|
||||
msgstr "ترقية اشتراكك"
|
||||
|
||||
#. module: odex30_web
|
||||
#: model:ir.model,name:odex30_web.model_res_users_settings
|
||||
msgid "User Settings"
|
||||
msgstr "إعدادات المستخدم"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/promote_studio_dialog/promote_studio_dialog.xml:0
|
||||
msgid "Want to tailor-make your Odoo?"
|
||||
msgstr "هل تريد تخصيص أودو حسب احتياجاتك؟"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid ""
|
||||
"You have more users or more apps installed than your subscription allows."
|
||||
msgstr ""
|
||||
"لديك مستخدمون أو تطبيقات مثبتة أكثر مما يسمح به اشتراكك."
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid ""
|
||||
"You will be able to register your database once you have installed your "
|
||||
"first app."
|
||||
msgstr ""
|
||||
"يمكنك تسجيل قاعدة البيانات بعد تثبيت أول تطبيق."
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid "Your subscription code"
|
||||
msgstr "كود اشتراكك"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.js:0
|
||||
msgid ""
|
||||
"Your subscription expired %s days ago. This database will be blocked soon. "
|
||||
msgstr ""
|
||||
"انتهى اشتراكك منذ %s أيام. سيتم حظر قاعدة البيانات قريباً. "
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.js:0
|
||||
msgid "Your subscription expires in %s days. "
|
||||
msgstr "ينتهي اشتراكك بعد %s أيام. "
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid "Your subscription is already linked to a database."
|
||||
msgstr "اشتراكك مرتبط بقاعدة بيانات بالفعل."
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid "Your subscription was updated and is valid until"
|
||||
msgstr "تم تحديث اشتراكك وهو صالح حتى"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/promote_studio_dialog/promote_studio_dialog.xml:0
|
||||
msgid "and more!"
|
||||
msgstr "وأكثر!"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid "buy a subscription"
|
||||
msgstr "شراء اشتراك"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid "buy a subscription."
|
||||
msgstr "شراء اشتراك."
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid "or"
|
||||
msgstr "أو"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/expiration_panel.xml:0
|
||||
msgid "to the subscription owner to confirm the change, enter a new code or"
|
||||
msgstr ""
|
||||
"لصاحب الاشتراك لتأكيد التغيير، أدخل كود جديد أو"
|
||||
|
||||
#. module: odex30_web
|
||||
#. odoo-javascript
|
||||
#: code:addons/odex30_web/static/src/webclient/home_menu/home_menu.xml:0
|
||||
msgid "— open me anywhere with"
|
||||
msgstr ""
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 362 KiB |
|
|
@ -1,35 +0,0 @@
|
|||
<svg width="1920" height="1080" viewBox="0 0 1920 1080" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.51001 1080H76.35L1153.55 0H3.51001V1080Z" fill="url(#o_app_switcher_gradient_01)"/>
|
||||
<path d="M76.35 1080H842.98L1920 0.18V0H1153.55L76.35 1080Z" fill="url(#o_app_switcher_gradient_02)"/>
|
||||
<path d="M1920 0.180176L842.98 1080H1063.11L1920 220.88V0.180176Z" fill="url(#o_app_switcher_gradient_03)"/>
|
||||
<path d="M1920 1080V220.88L1063.11 1080H1920Z" fill="url(#o_app_switcher_gradient_04)"/>
|
||||
<rect width="1920" height="1080" fill="url(#o_app_switcher_gradient_05)" fill-opacity="0.25"/>
|
||||
<rect width="1920" height="1080" fill="#E9E6F9" fill-opacity="0.25"/>
|
||||
<defs>
|
||||
<linearGradient id="o_app_switcher_gradient_01" x1="-222.43" y1="727.19" x2="904.26" y2="-76.67" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.1" stop-color="white"/>
|
||||
<stop offset="0.36" stop-color="#FEFEFE"/>
|
||||
<stop offset="0.68" stop-color="#EAE7F9"/>
|
||||
<stop offset="1" stop-color="#E4E9F7"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="o_app_switcher_gradient_02" x1="407.23" y1="1021.82" x2="1848.47" y2="-153.08" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.32" stop-color="#FEFEFE"/>
|
||||
<stop offset="0.66" stop-color="#EAE7F9"/>
|
||||
<stop offset="1" stop-color="#E5E2F6"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="o_app_switcher_gradient_03" x1="1142.33" y1="846.57" x2="1951.83" y2="136.16" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.15" stop-color="white"/>
|
||||
<stop offset="0.51" stop-color="#F7F0FD"/>
|
||||
<stop offset="0.85" stop-color="#F0E7F9"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="o_app_switcher_gradient_04" x1="1409.74" y1="1071" x2="2070.98" y2="526.01" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.45" stop-color="white"/>
|
||||
<stop offset="0.88" stop-color="#F7F0FD"/>
|
||||
<stop offset="1" stop-color="#ECE5F8"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="o_app_switcher_gradient_05" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(960 540) rotate(90) scale(540 960)">
|
||||
<stop stop-color="#9996A9" stop-opacity="0.53"/>
|
||||
<stop offset="1" stop-color="#7A768F"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
|
|
@ -1,9 +0,0 @@
|
|||
// = Search Panel
|
||||
// ============================================================================
|
||||
// No CSS hacks, variables overrides only
|
||||
|
||||
.o_search_panel_section {
|
||||
.o_popover > & .list-group {
|
||||
--#{$prefix}list-group-active-bg: #{$o-gray-400};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { session } from "@web/session";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { deserializeDateTime, serializeDate, formatDate } from "@web/core/l10n/dates";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { ExpirationPanel } from "./expiration_panel";
|
||||
import { cookie } from "@web/core/browser/cookie";
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
|
||||
const { DateTime } = luxon;
|
||||
import { Component, xml, useState } from "@odoo/owl";
|
||||
|
||||
function daysUntil(datetime) {
|
||||
const duration = datetime.diff(DateTime.utc(), "days");
|
||||
return Math.round(duration.values.days);
|
||||
}
|
||||
|
||||
export class SubscriptionManager {
|
||||
constructor(env, { orm, notification }) {
|
||||
this.env = env;
|
||||
this.orm = orm;
|
||||
this.notification = notification;
|
||||
if (session.expiration_date) {
|
||||
this.expirationDate = deserializeDateTime(session.expiration_date);
|
||||
} else {
|
||||
// If no date found, assume 1 month and hope for the best
|
||||
this.expirationDate = DateTime.utc().plus({ days: 30 });
|
||||
}
|
||||
this.expirationReason = session.expiration_reason;
|
||||
// Hack: we need to know if there is at least one app installed (except from App and
|
||||
// Settings). We use mail to do that, as it is a dependency of almost every addon. To
|
||||
// determine whether mail is installed or not, we check for the presence of the key
|
||||
// "storeData" in session_info, as it is added in mail.
|
||||
this.hasInstalledApps = "storeData" in session;
|
||||
// "user" or "admin"
|
||||
this.warningType = session.warning;
|
||||
this.lastRequestStatus = null;
|
||||
this.isWarningHidden = cookie.get("oe_instance_hide_panel");
|
||||
}
|
||||
|
||||
get formattedExpirationDate() {
|
||||
return formatDate(this.expirationDate, { format: "DDD" });
|
||||
}
|
||||
|
||||
get daysLeft() {
|
||||
return daysUntil(this.expirationDate);
|
||||
}
|
||||
|
||||
get unregistered() {
|
||||
return ["trial", "demo", false].includes(this.expirationReason);
|
||||
}
|
||||
|
||||
hideWarning() {
|
||||
// Hide warning for 24 hours.
|
||||
cookie.set("oe_instance_hide_panel", true, 24 * 60 * 60);
|
||||
this.isWarningHidden = true;
|
||||
}
|
||||
|
||||
async buy() {
|
||||
const limitDate = serializeDate(DateTime.utc().minus({ days: 15 }));
|
||||
const args = [
|
||||
[
|
||||
["share", "=", false],
|
||||
["login_date", ">=", limitDate],
|
||||
],
|
||||
];
|
||||
const nbUsers = await this.orm.call("res.users", "search_count", args);
|
||||
browser.location = `https://www.odoo.com/odoo-enterprise/upgrade?num_users=${nbUsers}`;
|
||||
}
|
||||
|
||||
async submitCode(OdexCode) {
|
||||
const [oldDate, ] = await Promise.all([
|
||||
this.orm.call("ir.config_parameter", "get_param", ["database.expiration_date"]),
|
||||
this.orm.call("ir.config_parameter", "set_param", [
|
||||
"database.enterprise_code",
|
||||
OdexCode,
|
||||
])
|
||||
]);
|
||||
|
||||
await this.orm.call("publisher_warranty.contract", "update_notification", [[]]);
|
||||
|
||||
const [linkedSubscriptionUrl, linkedEmail, expirationDate] = await Promise.all([
|
||||
this.orm.call("ir.config_parameter", "get_param", [
|
||||
"database.already_linked_subscription_url",
|
||||
]),
|
||||
this.orm.call("ir.config_parameter", "get_param", ["database.already_linked_email"]),
|
||||
this.orm.call("ir.config_parameter", "get_param", [
|
||||
"database.expiration_date",
|
||||
])
|
||||
]);
|
||||
|
||||
if (linkedSubscriptionUrl) {
|
||||
this.lastRequestStatus = "link";
|
||||
this.linkedSubscriptionUrl = linkedSubscriptionUrl;
|
||||
this.mailDeliveryStatus = null;
|
||||
this.linkedEmail = linkedEmail;
|
||||
} else if (expirationDate !== oldDate) {
|
||||
this.lastRequestStatus = "success";
|
||||
this.expirationDate = deserializeDateTime(expirationDate);
|
||||
if (this.daysLeft > 30) {
|
||||
this.notification.add(
|
||||
_t(
|
||||
"Thank you, your registration was successful! Your database is valid until %s.",
|
||||
this.formattedExpirationDate
|
||||
),
|
||||
{ type: "success" }
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.lastRequestStatus = "error";
|
||||
}
|
||||
}
|
||||
|
||||
async checkStatus() {
|
||||
await this.orm.call("publisher_warranty.contract", "update_notification", [[]]);
|
||||
|
||||
const expirationDateStr = await this.orm.call("ir.config_parameter", "get_param", [
|
||||
"database.expiration_date",
|
||||
]);
|
||||
this.lastRequestStatus = "update";
|
||||
this.expirationDate = deserializeDateTime(expirationDateStr);
|
||||
}
|
||||
|
||||
async sendUnlinkEmail() {
|
||||
const sendUnlinkInstructionsUrl = await this.orm.call("ir.config_parameter", "get_param", [
|
||||
"database.already_linked_send_mail_url",
|
||||
]);
|
||||
this.mailDeliveryStatus = "ongoing";
|
||||
const { result, reason } = await rpc(sendUnlinkInstructionsUrl);
|
||||
if (result) {
|
||||
this.mailDeliveryStatus = "success";
|
||||
} else {
|
||||
this.mailDeliveryStatus = "fail";
|
||||
this.mailDeliveryStatusError = reason;
|
||||
}
|
||||
}
|
||||
|
||||
async renew() {
|
||||
const OdexCode = await this.orm.call("ir.config_parameter", "get_param", [
|
||||
"database.enterprise_code",
|
||||
]);
|
||||
|
||||
const url = "https://www.odoo.com/odoo-enterprise/renew";
|
||||
const contractQueryString = OdexCode ? `?contract=${OdexCode}` : "";
|
||||
browser.location = `${url}${contractQueryString}`;
|
||||
}
|
||||
|
||||
async upsell() {
|
||||
const limitDate = serializeDate(DateTime.utc().minus({ days: 15 }));
|
||||
const [OdexCode, nbUsers] = await Promise.all([
|
||||
this.orm.call("ir.config_parameter", "get_param", ["database.enterprise_code"]),
|
||||
this.orm.call("res.users", "search_count", [
|
||||
[
|
||||
["share", "=", false],
|
||||
["login_date", ">=", limitDate],
|
||||
],
|
||||
]),
|
||||
]);
|
||||
const url = "https://www.odoo.com/odoo-enterprise/upsell";
|
||||
const contractQueryString = OdexCode ? `&contract=${OdexCode}` : "";
|
||||
browser.location = `${url}?num_users=${nbUsers}${contractQueryString}`;
|
||||
}
|
||||
}
|
||||
|
||||
class ExpiredSubscriptionBlockUI extends Component {
|
||||
static props = {};
|
||||
// TODO the "o_blockUI" div in there seems useless (it has 0 height and thus displays and does nothing)
|
||||
static template = xml`
|
||||
<t t-if="subscription.daysLeft <= 0">
|
||||
<div class="o_blockUI"/>
|
||||
<div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; z-index: 1100" class="d-flex align-items-center justify-content-center">
|
||||
<ExpirationPanel/>
|
||||
</div>
|
||||
</t>`;
|
||||
static components = { ExpirationPanel };
|
||||
setup() {
|
||||
this.subscription = useState(useService("enterprise_subscription"));
|
||||
}
|
||||
}
|
||||
|
||||
export const enterpriseSubscriptionService = {
|
||||
name: "enterprise_subscription",
|
||||
dependencies: ["orm", "notification"],
|
||||
start(env, { orm, notification }) {
|
||||
registry
|
||||
.category("main_components")
|
||||
.add("expired_subscription_block_ui", { Component: ExpiredSubscriptionBlockUI });
|
||||
return new SubscriptionManager(env, { orm, notification });
|
||||
},
|
||||
};
|
||||
|
||||
registry.category("services").add("enterprise_subscription", enterpriseSubscriptionService);
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { Transition } from "@web/core/transition";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { Component, useState, useRef } from "@odoo/owl";
|
||||
|
||||
const { DateTime } = luxon;
|
||||
|
||||
/**
|
||||
* Expiration panel
|
||||
*
|
||||
* Component representing the banner located on top of the home menu. Its purpose
|
||||
* is to display the expiration state of the current database and to help the
|
||||
* user to buy/renew its subscription.
|
||||
* @extends Component
|
||||
*/
|
||||
export class ExpirationPanel extends Component {
|
||||
static template = "DatabaseExpirationPanel";
|
||||
static props = {};
|
||||
static components = { Transition };
|
||||
|
||||
setup() {
|
||||
this.subscription = useState(useService("enterprise_subscription"));
|
||||
|
||||
this.state = useState({
|
||||
displayRegisterForm: false,
|
||||
});
|
||||
|
||||
this.inputRef = useRef("input");
|
||||
}
|
||||
|
||||
get buttonText() {
|
||||
return this.subscription.lastRequestStatus === "error" ? _t("Retry") : _t("Register");
|
||||
}
|
||||
|
||||
get alertType() {
|
||||
if (this.subscription.lastRequestStatus === "success") {
|
||||
return "success";
|
||||
}
|
||||
const { daysLeft } = this.subscription;
|
||||
if (daysLeft <= 6) {
|
||||
return "danger";
|
||||
} else if (daysLeft <= 16) {
|
||||
return "warning";
|
||||
}
|
||||
return "info";
|
||||
}
|
||||
|
||||
get expirationMessage() {
|
||||
const { daysLeft } = this.subscription;
|
||||
if (daysLeft <= 0) {
|
||||
return _t("This database has expired. ");
|
||||
}
|
||||
const delay = daysLeft === 30 ? _t("1 month") : _t("%s days", daysLeft);
|
||||
if (this.subscription.expirationReason === "demo") {
|
||||
return _t("This demo database will expire in %s. ", delay);
|
||||
}
|
||||
|
||||
const expirationDate = this.subscription.expirationDate;
|
||||
const today = DateTime.now();
|
||||
const diff = expirationDate.diff(today);
|
||||
|
||||
if (this.subscription.expirationReason !== 'renewal') {
|
||||
return _t("This database will expire in %s. ", delay);
|
||||
} else {
|
||||
if (daysLeft > 15) {
|
||||
return _t(
|
||||
"Your subscription expires in %s days. ",
|
||||
daysLeft - 15
|
||||
);
|
||||
} else {
|
||||
return _t(
|
||||
"Your subscription expired %s days ago. This database will be blocked soon. ",
|
||||
(diff.as("days") | 0)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showRegistrationForm() {
|
||||
this.state.displayRegisterForm = !this.state.displayRegisterForm;
|
||||
}
|
||||
|
||||
async onCodeSubmit() {
|
||||
const OdexCode = this.inputRef.el.value;
|
||||
if (!OdexCode) {
|
||||
return;
|
||||
}
|
||||
await this.subscription.submitCode(OdexCode);
|
||||
if (this.subscription.lastRequestStatus === "success") {
|
||||
this.state.displayRegisterForm = false;
|
||||
} else {
|
||||
this.state.buttonText = _t("Retry");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
.database_expiration_panel .oe_instance_register_form {
|
||||
max-height: 0;
|
||||
transition: max-height 0.4s;
|
||||
|
||||
&.o-vertical-slide-enter-active {
|
||||
max-height: 10rem; // fixed value is required to properly trigger transition
|
||||
}
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="DatabaseExpirationPanel">
|
||||
<div role="alert"
|
||||
class="database_expiration_panel alert o-hidden-ios text-center mt-4"
|
||||
t-attf-class="alert-{{alertType}}"
|
||||
>
|
||||
<!-- t-translation="off" should be on next a element below -->
|
||||
<a t-if="subscription.daysLeft > 0" href="#" class="oe_instance_hide_panel float-end alert-link" t-on-click.prevent="() => subscription.hideWarning()" aria-label="Dismiss">×</a>
|
||||
<span t-if="!subscription.lastRequestStatus" class="oe_instance_register">
|
||||
<t t-if="!subscription.hasInstalledApps">You will be able to register your database once you have installed your first app.</t>
|
||||
<t t-else="">
|
||||
<t t-esc="expirationMessage"/>
|
||||
<t t-if="subscription.warningType === 'admin'">
|
||||
<t t-if="subscription.unregistered">
|
||||
<a class="oe_instance_register_show alert-link text-decoration-underline" href="#" t-on-click.prevent="showRegistrationForm">Register your subscription</a>
|
||||
or
|
||||
<a class="oe_instance_buy alert-link text-decoration-underline" href="#" t-on-click.prevent="() => subscription.buy()">buy a subscription</a>.
|
||||
</t>
|
||||
<t t-if="subscription.expirationReason === 'renewal'">
|
||||
<div class="d-flex flex-wrap justify-content-center mt-2">
|
||||
<a class="oe_instance_renew btn btn-link" href="#" t-on-click.prevent="() => subscription.renew()">Renew now </a>
|
||||
<a class="check_enterprise_status btn btn-link" href="#" t-on-click.prevent="() => subscription.checkStatus()">I paid, please recheck!</a>
|
||||
</div>
|
||||
</t>
|
||||
<t t-elif="subscription.expirationReason === 'upsell'">You have more users or more apps installed than your subscription allows.<br/>
|
||||
<div class="d-flex flex-wrap justify-content-center mt-2">
|
||||
<a class="oe_instance_upsell btn btn-link" href="#" t-on-click.prevent="() => subscription.upsell()">Upgrade your subscription </a>
|
||||
<a class="check_enterprise_status btn btn-link" href="#" t-on-click.prevent="() => subscription.checkStatus()">I paid, please recheck!</a>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
<t t-elif="subscription.warningType === 'user'">Log in as an administrator to correct the issue.</t>
|
||||
</t>
|
||||
</span>
|
||||
<span t-if="subscription.lastRequestStatus === 'success'" class="oe_instance_register oe_instance_success">Thank you, your registration was successful! Your database is valid until <span><t t-esc="subscription.formattedExpirationDate"/></span>.</span>
|
||||
<span t-elif="subscription.lastRequestStatus === 'update'" class="oe_instance_register oe_subscription_updated">Your subscription was updated and is valid until <span><t t-esc="subscription.formattedExpirationDate"/></span>.</span>
|
||||
<span t-elif="subscription.lastRequestStatus === 'error'" class="oe_instance_register oe_instance_error">Something went wrong while registering your database. You can try again or contact <a class="alert-link text-decoration-underline" href="https://www.odoo.com/help" target="_blank">Odoo Support</a>.</span>
|
||||
<span t-elif="subscription.lastRequestStatus === 'link'" class="oe_instance_register oe_database_already_linked">
|
||||
Your subscription is already linked to a database.<br/>
|
||||
<span t-if="subscription.linkedEmail" class="oe_contract_email_block">
|
||||
<a href="#" class="oe_contract_send_mail alert-link text-decoration-underline" t-on-click.prevent="() => subscription.sendUnlinkEmail()">Send an email</a> to the subscription owner to confirm the change, enter a new code or <a class="oe_instance_buy" href="#" t-on-click.prevent="() => subscription.buy()">buy a subscription.</a>
|
||||
<p t-if="subscription.mailDeliveryStatus === 'ongoing'">Sending the instructions by email ...</p>
|
||||
<p t-elif="subscription.mailDeliveryStatus === 'success'">The instructions to unlink your subscription from the previous database(s) have been sent</p>
|
||||
<p t-elif="subscription.mailDeliveryStatus === 'fail'">Unable to send the instructions by email, please contact the <a href="https://www.odoo.com/help" target="_blank">Odoo Support</a><br/>
|
||||
Error reason: <t t-esc="subscription.mailDeliveryStatusError"/>
|
||||
</p>
|
||||
</span>
|
||||
<span t-else="">Contact your sales representative to help you to unlink your previous database</span>
|
||||
</span>
|
||||
<Transition visible="state.displayRegisterForm and subscription.lastRequestStatus !== 'success'" t-slot-scope="transition" leaveDuration="400" name="'o-vertical-slide'">
|
||||
<form class="oe_instance_register oe_instance_register_form d-flex flex-wrap align-items-center overflow-hidden justify-content-center mt-4" t-att-class="transition.className">
|
||||
<label for="enterprise_code">Subscription Code: </label>
|
||||
<input type="text" class="o_input w-auto mx-2 mb-2 mb-sm-0" t-ref="input"
|
||||
placeholder="Paste code here"
|
||||
title="Your subscription code"
|
||||
/>
|
||||
<button class="btn btn-primary" t-on-click.prevent="onCodeSubmit"><t t-esc="buttonText"/></button>
|
||||
</form>
|
||||
</Transition>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -4,5 +4,5 @@
|
|||
|
||||
.o_home_menu_background {
|
||||
--homeMenu-bg-color: #000511;
|
||||
--homeMenu-bg-image: url("/odex30_web/static/img/background-dark.jpg");
|
||||
--homeMenu-bg-image: url("/odex30_web/static/img/background-dark.png");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@
|
|||
size: cover;
|
||||
attachment: fixed;
|
||||
color: var(--homeMenu-bg-color, #{$o-gray-200});
|
||||
image: var(--homeMenu-bg-image, url("/odex30_web/static/img/background-light.svg"));
|
||||
image: var(--homeMenu-bg-image, url("/odex30_web/static/img/background-light.png"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { createWebClient } from "@web/../tests/webclient/helpers";
|
||||
import { WebClientOdex } from "@odex30_web/webclient/webclient";
|
||||
|
||||
export function createOdexWebClient(params) {
|
||||
params.WebClientClass = WebClientOdex;
|
||||
return createWebClient(params);
|
||||
}
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
import { beforeEach, describe, expect, test } from "@odoo/hoot";
|
||||
import { click, queryAll } from "@odoo/hoot-dom";
|
||||
import { animationFrame, runAllTimers } from "@odoo/hoot-mock";
|
||||
import { defineActions, defineMenus, mountWithCleanup } from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { Component, onMounted, xml } from "@odoo/owl";
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { WebClientOdex } from "@odex30_web/webclient/webclient";
|
||||
|
||||
const actionRegistry = registry.category("actions");
|
||||
|
||||
const queryAllRoot = (selector) => queryAll(selector, { root: document.body });
|
||||
|
||||
class TestClientAction extends Component {
|
||||
static template = xml`
|
||||
<div class="test_client_action">
|
||||
ClientAction_<t t-esc="props.action.params?.description"/>
|
||||
</div>`;
|
||||
static props = ["*"];
|
||||
setup() {
|
||||
onMounted(() => this.env.config.setDisplayName(`Client action ${this.props.action.id}`));
|
||||
}
|
||||
}
|
||||
|
||||
describe.current.tags("mobile");
|
||||
|
||||
beforeEach(() => {
|
||||
defineMenus([
|
||||
{
|
||||
id: 1,
|
||||
name: "App1",
|
||||
appID: 1,
|
||||
actionID: 1001,
|
||||
xmlid: "menu_1",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("Burger Menu on home menu", async () => {
|
||||
expect.assertions(5);
|
||||
|
||||
await mountWithCleanup(WebClientOdex);
|
||||
await animationFrame();
|
||||
expect(queryAllRoot(".o_burger_menu")).toHaveCount(0);
|
||||
expect(queryAllRoot(".o_home_menu")).toBeVisible();
|
||||
|
||||
await click(queryAllRoot(".o_mobile_menu_toggle"));
|
||||
await runAllTimers();
|
||||
await animationFrame();
|
||||
expect(queryAllRoot(".o_burger_menu")).toHaveCount(1);
|
||||
expect(queryAllRoot(".o_user_menu_mobile")).toHaveCount(1);
|
||||
await click(queryAllRoot(".o_sidebar_close"));
|
||||
await animationFrame();
|
||||
expect(".o_burger_menu").toHaveCount(0);
|
||||
});
|
||||
|
||||
test("Burger Menu on home menu over an App", async () => {
|
||||
expect.assertions(5);
|
||||
|
||||
actionRegistry.add("__test__client__action__", TestClientAction);
|
||||
|
||||
defineMenus([
|
||||
{
|
||||
id: 1,
|
||||
children: [
|
||||
{
|
||||
id: 99,
|
||||
name: "SubMenu",
|
||||
appID: 1,
|
||||
actionID: 1002,
|
||||
webIconData: undefined,
|
||||
webIcon: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
defineActions([
|
||||
{
|
||||
id: 1001,
|
||||
tag: "__test__client__action__",
|
||||
target: "main",
|
||||
type: "ir.actions.client",
|
||||
params: { description: "Id 1" },
|
||||
},
|
||||
]);
|
||||
|
||||
await mountWithCleanup(WebClientOdex);
|
||||
await animationFrame();
|
||||
|
||||
await click(queryAllRoot(".o_draggable:first-of-type .o_app"));
|
||||
await animationFrame();
|
||||
await click(queryAllRoot(".o_menu_toggle"));
|
||||
await animationFrame();
|
||||
await click(queryAllRoot(".o_sidebar_topbar a.btn-primary"));
|
||||
await animationFrame();
|
||||
|
||||
expect(queryAllRoot(".o_burger_menu")).toHaveCount(0);
|
||||
expect(queryAllRoot(".o_home_menu")).toBeVisible();
|
||||
|
||||
await click(queryAllRoot(".o_mobile_menu_toggle"));
|
||||
await animationFrame();
|
||||
expect(queryAllRoot(".o_burger_menu")).toHaveCount(1);
|
||||
expect(queryAllRoot(".o_burger_menu nav.o_burger_menu_content li")).toHaveCount(0);
|
||||
expect(queryAllRoot(".o_burger_menu_content")).not.toHaveClass("o_burger_menu_dark");
|
||||
});
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { click } from "@odoo/hoot-dom";
|
||||
import { animationFrame } from "@odoo/hoot-mock";
|
||||
import { defineModels, fields, models, mountView } from "@web/../tests/web_test_helpers";
|
||||
|
||||
class Partner extends models.Model {
|
||||
foo = fields.Integer({ aggregator: "sum" });
|
||||
|
||||
_records = [
|
||||
{
|
||||
id: 1,
|
||||
foo: 12,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
foo: 1,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
foo: 17,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
foo: 2,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
defineModels([Partner]);
|
||||
|
||||
describe.current.tags("mobile");
|
||||
|
||||
test("simple pivot rendering", async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
await mountView({
|
||||
type: "pivot",
|
||||
resModel: "partner",
|
||||
arch: /* xml */ `
|
||||
<pivot string="Partners">
|
||||
<field name="foo" type="measure"/>
|
||||
</pivot>
|
||||
`,
|
||||
});
|
||||
|
||||
expect(".o_pivot_view").toHaveClass("o_view_controller");
|
||||
expect("td.o_pivot_cell_value:contains(32)").toHaveCount(1, {
|
||||
message: "should contain a pivot cell with the sum of all records",
|
||||
});
|
||||
});
|
||||
|
||||
test("unselecting all measures should not crash pivot rendering", async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
await mountView({
|
||||
type: "pivot",
|
||||
resModel: "partner",
|
||||
arch: /* xml */ `
|
||||
<pivot string="Partners">
|
||||
<field name="foo" type="measure"/>
|
||||
</pivot>
|
||||
`,
|
||||
});
|
||||
|
||||
await click(".dropdown-toggle.btn.btn-primary:eq(1)");
|
||||
await animationFrame();
|
||||
await click(".dropdown-item.o_menu_item.selected:eq(0)");
|
||||
await animationFrame();
|
||||
expect("div.o_nocontent_help").toHaveCount(1, {
|
||||
message: "Instead of error action helper will appear",
|
||||
});
|
||||
});
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { click, queryFirst } from "@odoo/hoot-dom";
|
||||
import { animationFrame, mockMatchMedia } from "@odoo/hoot-mock";
|
||||
import {
|
||||
defineActions,
|
||||
defineModels,
|
||||
fields,
|
||||
getService,
|
||||
models,
|
||||
mountWithCleanup,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { UserMenu } from "@web/webclient/user_menu/user_menu";
|
||||
import { WebClientOdex } from "@odex30_web/webclient/webclient";
|
||||
|
||||
class Partner extends models.Model {
|
||||
name = fields.Char();
|
||||
|
||||
_records = [
|
||||
{ id: 1, name: "First record" },
|
||||
{ id: 2, name: "Second record" },
|
||||
];
|
||||
_views = {
|
||||
form: `
|
||||
<form>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
</group>
|
||||
</form>
|
||||
`,
|
||||
kanban: `
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="card">
|
||||
<field name="name"/>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
`,
|
||||
list: `<list><field name="name"/></list>`,
|
||||
};
|
||||
}
|
||||
|
||||
defineModels([Partner]);
|
||||
|
||||
defineActions([
|
||||
{
|
||||
id: 1,
|
||||
xml_id: "action_1",
|
||||
name: "Partners Action 1",
|
||||
res_model: "partner",
|
||||
views: [[false, "kanban"]],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
xml_id: "action_3",
|
||||
name: "Partners",
|
||||
res_model: "partner",
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "kanban"],
|
||||
[false, "form"],
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
describe.current.tags("mobile");
|
||||
|
||||
test("scroll position is kept", async () => {
|
||||
// This test relies on the fact that the scrollable element in mobile
|
||||
// is view's root node.
|
||||
const firstRecord = Partner._records[0];
|
||||
delete firstRecord.id;
|
||||
Partner._records = [...Array(80)].map((_, i) => ({
|
||||
...firstRecord,
|
||||
name: `Record ${i + 1}`,
|
||||
}));
|
||||
|
||||
await mountWithCleanup(WebClientOdex);
|
||||
await animationFrame();
|
||||
|
||||
// partners in list/kanban
|
||||
await getService("action").doAction(3);
|
||||
expect(".o_kanban_view").toHaveCount(1);
|
||||
|
||||
queryFirst(".o_kanban_view").scrollTo(0, 123);
|
||||
await click(".o_kanban_record:eq(20)");
|
||||
await animationFrame();
|
||||
expect(".o_form_view").toHaveCount(1);
|
||||
expect(".o_kanban_view").toHaveCount(0);
|
||||
|
||||
await click(".o_breadcrumb .o_back_button");
|
||||
await animationFrame();
|
||||
expect(".o_form_view").toHaveCount(0);
|
||||
expect(".o_kanban_view").toHaveCount(1);
|
||||
});
|
||||
|
||||
test("Share URL item is not present in the user menu when screen is small", async () => {
|
||||
mockMatchMedia({ ["display-mode"]: "standalone" });
|
||||
|
||||
await mountWithCleanup(UserMenu);
|
||||
|
||||
expect(".o_user_menu").toHaveCount(1);
|
||||
queryFirst(".o_user_menu").classList.remove("d-none");
|
||||
|
||||
await click(".o_user_menu button");
|
||||
await animationFrame();
|
||||
|
||||
expect(".o_user_menu .dropdown-item").toHaveCount(0, {
|
||||
message: "share button is not visible",
|
||||
});
|
||||
});
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
/** @odoo-module */
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("odex30_web.test_studio_list_upsell", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: ".o_list_view",
|
||||
},
|
||||
{
|
||||
trigger: ".o_optional_columns_dropdown > button",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: " .o-dropdown--menu .dropdown-item-studio",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -1,348 +0,0 @@
|
|||
import { beforeEach, describe, expect, test } from "@odoo/hoot";
|
||||
import { click, queryAll } from "@odoo/hoot-dom";
|
||||
import { animationFrame } from "@odoo/hoot-mock";
|
||||
import {
|
||||
contains,
|
||||
defineModels,
|
||||
fields,
|
||||
getDropdownMenu,
|
||||
getService,
|
||||
models,
|
||||
mountView,
|
||||
mountWithCleanup,
|
||||
onRpc,
|
||||
patchWithCleanup,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { user } from "@web/core/user";
|
||||
import { WebClientOdex } from "@odex30_web/webclient/webclient";
|
||||
|
||||
class Foo extends models.Model {
|
||||
foo = fields.Char();
|
||||
bar = fields.Boolean();
|
||||
|
||||
_records = [
|
||||
{ id: 1, bar: true, foo: "yop" },
|
||||
{ id: 2, bar: true, foo: "blip" },
|
||||
{ id: 3, bar: true, foo: "gnap" },
|
||||
{ id: 4, bar: false, foo: "blip" },
|
||||
];
|
||||
}
|
||||
|
||||
defineModels([Foo]);
|
||||
|
||||
const getDefaultConfig = () => ({
|
||||
actionId: 1,
|
||||
actionType: "ir.actions.act_window",
|
||||
});
|
||||
|
||||
describe.current.tags("desktop");
|
||||
|
||||
beforeEach(() => {
|
||||
onRpc("has_group", () => true);
|
||||
});
|
||||
|
||||
test("add custom field button with other optional columns - studio not installed", async () => {
|
||||
expect.assertions(8);
|
||||
|
||||
onRpc("search_read", ({ model }) => {
|
||||
if (model === "ir.module.module") {
|
||||
expect.step("studio_module_id");
|
||||
return [{ id: 42 }];
|
||||
}
|
||||
});
|
||||
onRpc("button_immediate_install", ({ model, args }) => {
|
||||
if (model === "ir.module.module") {
|
||||
expect(args[0]).toEqual([42], {
|
||||
message: "Should be the id of studio module returned by the search read",
|
||||
});
|
||||
expect.step("studio_module_install");
|
||||
return true;
|
||||
}
|
||||
});
|
||||
await mountView({
|
||||
type: "list",
|
||||
resModel: "foo",
|
||||
arch: /* xml */ `
|
||||
<list>
|
||||
<field name="foo"/>
|
||||
<field name="bar" optional="hide"/>
|
||||
</list>
|
||||
`,
|
||||
config: getDefaultConfig(),
|
||||
});
|
||||
|
||||
patchWithCleanup(browser.location, {
|
||||
reload: function () {
|
||||
expect.step("window_reload");
|
||||
},
|
||||
});
|
||||
|
||||
expect(".o_data_row").toHaveCount(4);
|
||||
expect(".o_optional_columns_dropdown_toggle").toHaveCount(1);
|
||||
|
||||
await click(".o_optional_columns_dropdown_toggle");
|
||||
await animationFrame();
|
||||
const dropdown = getDropdownMenu(".o_optional_columns_dropdown");
|
||||
|
||||
expect(queryAll(".dropdown-item", { root: dropdown })).toHaveCount(2);
|
||||
expect(queryAll(".dropdown-item-studio", { root: dropdown })).toHaveCount(1);
|
||||
|
||||
await click(".dropdown-item-studio");
|
||||
await animationFrame();
|
||||
expect(".modal-studio").toHaveCount(1);
|
||||
|
||||
await click(".modal .o_install_studio");
|
||||
await animationFrame();
|
||||
expect(browser.localStorage.getItem("openStudioOnReload")).toBe("main");
|
||||
expect.verifySteps(["studio_module_id", "studio_module_install", "window_reload"]);
|
||||
});
|
||||
|
||||
test("add custom field button without other optional columns - studio not installed", async () => {
|
||||
expect.assertions(8);
|
||||
|
||||
onRpc("search_read", ({ model }) => {
|
||||
if (model === "ir.module.module") {
|
||||
expect.step("studio_module_id");
|
||||
return [{ id: 42 }];
|
||||
}
|
||||
});
|
||||
onRpc("button_immediate_install", ({ model, args }) => {
|
||||
if (model === "ir.module.module") {
|
||||
expect(args[0]).toEqual([42], {
|
||||
message: "Should be the id of studio module returned by the search read",
|
||||
});
|
||||
expect.step("studio_module_install");
|
||||
return true;
|
||||
}
|
||||
});
|
||||
await mountView({
|
||||
type: "list",
|
||||
resModel: "foo",
|
||||
config: getDefaultConfig(),
|
||||
arch: /* xml */ `
|
||||
<list>
|
||||
<field name="foo"/>
|
||||
<field name="bar"/>
|
||||
</list>
|
||||
`,
|
||||
});
|
||||
|
||||
patchWithCleanup(browser.location, {
|
||||
reload: function () {
|
||||
expect.step("window_reload");
|
||||
},
|
||||
});
|
||||
|
||||
expect(".o_data_row").toHaveCount(4);
|
||||
expect(".o_optional_columns_dropdown_toggle").toHaveCount(1);
|
||||
|
||||
await click(".o_optional_columns_dropdown_toggle");
|
||||
await animationFrame();
|
||||
const dropdown = getDropdownMenu(".o_optional_columns_dropdown");
|
||||
|
||||
expect(queryAll(".dropdown-item", { root: dropdown })).toHaveCount(1);
|
||||
expect(queryAll(".dropdown-item-studio", { root: dropdown })).toHaveCount(1);
|
||||
|
||||
await click(".dropdown-item-studio");
|
||||
await animationFrame();
|
||||
expect(".modal-studio").toHaveCount(1);
|
||||
|
||||
await click(".modal .o_install_studio");
|
||||
await animationFrame();
|
||||
expect(browser.localStorage.getItem("openStudioOnReload")).toBe("main");
|
||||
expect.verifySteps(["studio_module_id", "studio_module_install", "window_reload"]);
|
||||
});
|
||||
|
||||
test("add custom field button not shown to non-system users (with opt. col.)", async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
patchWithCleanup(user, { isSystem: false });
|
||||
|
||||
await mountView({
|
||||
type: "list",
|
||||
resModel: "foo",
|
||||
config: getDefaultConfig(),
|
||||
arch: /* xml */ `
|
||||
<list>
|
||||
<field name="foo"/>
|
||||
<field name="bar" optional="hide"/>
|
||||
</list>
|
||||
`,
|
||||
});
|
||||
|
||||
expect(".o_optional_columns_dropdown_toggle").toHaveCount(1);
|
||||
|
||||
await click(".o_optional_columns_dropdown_toggle");
|
||||
await animationFrame();
|
||||
const dropdown = getDropdownMenu(".o_optional_columns_dropdown");
|
||||
expect(queryAll(".dropdown-item", { root: dropdown })).toHaveCount(1);
|
||||
expect(queryAll(".dropdown-item-studio", { root: dropdown })).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("add custom field button not shown to non-system users (wo opt. col.)", async () => {
|
||||
patchWithCleanup(user, { isSystem: false });
|
||||
|
||||
await mountView({
|
||||
type: "list",
|
||||
resModel: "foo",
|
||||
config: getDefaultConfig(),
|
||||
arch: /* xml */ `
|
||||
<list>
|
||||
<field name="foo"/>
|
||||
<field name="bar"/>
|
||||
</list>
|
||||
`,
|
||||
});
|
||||
|
||||
expect(".o_optional_columns_dropdown_toggle").toHaveCount(0);
|
||||
});
|
||||
|
||||
test("add custom field button not shown with invalid action", async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
patchWithCleanup(user, { isSystem: false });
|
||||
|
||||
await mountView({
|
||||
type: "list",
|
||||
resModel: "foo",
|
||||
config: { ...getDefaultConfig(), actionId: null },
|
||||
arch: /* xml */ `
|
||||
<list>
|
||||
<field name="foo"/>
|
||||
<field name="bar"/>
|
||||
</list>
|
||||
`,
|
||||
});
|
||||
|
||||
expect(".o_optional_columns_dropdown_toggle").toHaveCount(0);
|
||||
});
|
||||
|
||||
test("add custom field button not shown with bank statement line model", async () => {
|
||||
class AccountBankStatementLine extends models.Model {
|
||||
name = fields.Char();
|
||||
_views = {
|
||||
kanban: `
|
||||
<kanban>
|
||||
<template>
|
||||
<t t-name="card">
|
||||
<field name="display_name"/>
|
||||
</t>
|
||||
</template>
|
||||
</kanban>
|
||||
`,
|
||||
list: `<list><field name="display_name" /> <field name="name" optional="1" /></list>`,
|
||||
};
|
||||
}
|
||||
defineModels([AccountBankStatementLine]);
|
||||
|
||||
expect.assertions(3);
|
||||
|
||||
await mountWithCleanup(WebClientOdex);
|
||||
await animationFrame();
|
||||
|
||||
await getService("action").doAction({
|
||||
xml_id: "test",
|
||||
id: 1312,
|
||||
name: "test",
|
||||
res_id: 1,
|
||||
res_model: "account.bank.statement.line",
|
||||
type: "ir.actions.act_window",
|
||||
views: [[false, "kanban"], [false, "list"]],
|
||||
});
|
||||
|
||||
expect("button.o_switch_view.o_list[data-tooltip='List']").toHaveCount(1);
|
||||
await contains("button.o_switch_view.o_list[data-tooltip='List']").click();
|
||||
expect(".o_list_renderer .o_list_controller button.dropdown-toggle").toHaveCount(1);
|
||||
await contains(".o_list_renderer .o_list_controller button.dropdown-toggle").click();
|
||||
expect(".dropdown-item-studio").toHaveCount(0);
|
||||
});
|
||||
|
||||
test("x2many should not be editable", async () => {
|
||||
class Bar extends models.Model {}
|
||||
defineModels([Bar]);
|
||||
Foo._fields.o2m = fields.One2many({ relation: "bar" });
|
||||
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "foo",
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<notebook>
|
||||
<page>
|
||||
<field name="o2m">
|
||||
<list>
|
||||
<field name="display_name"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page><div class="test_empty_page" /></page>
|
||||
</notebook>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
expect(".o_optional_columns_dropdown_toggle").toHaveCount(0);
|
||||
await click(".nav-link:eq(1)");
|
||||
await animationFrame();
|
||||
await click(".nav-link:eq(0)");
|
||||
await animationFrame();
|
||||
expect(".o_field_widget").toHaveCount(1);
|
||||
expect(".o_optional_columns_dropdown_toggle").toHaveCount(0);
|
||||
});
|
||||
|
||||
test("upsell studio feature is not polluted by another view", async () => {
|
||||
class Partner extends models.Model {
|
||||
name = fields.Char();
|
||||
|
||||
_views = {
|
||||
list: `<list><field name="display_name" /> <field name="name" optional="1" /></list>`,
|
||||
};
|
||||
}
|
||||
|
||||
defineModels([Partner]);
|
||||
|
||||
await mountWithCleanup(WebClientOdex);
|
||||
await animationFrame();
|
||||
|
||||
await getService("action").doAction({
|
||||
xml_id: "editable",
|
||||
id: 999,
|
||||
type: "ir.actions.act_window",
|
||||
views: [[false, "list"]],
|
||||
res_model: "partner",
|
||||
});
|
||||
|
||||
await click(".o_optional_columns_dropdown_toggle");
|
||||
await animationFrame();
|
||||
expect(".dropdown-item").toHaveCount(2);
|
||||
expect(".dropdown-item-studio").toHaveCount(1);
|
||||
|
||||
await getService("action").doAction({
|
||||
id: 99,
|
||||
xml_id: "in_dialog",
|
||||
type: "ir.actions.act_window",
|
||||
views: [[false, "list"]],
|
||||
res_model: "partner",
|
||||
target: "new",
|
||||
});
|
||||
|
||||
await click(".modal .o_optional_columns_dropdown_toggle");
|
||||
await animationFrame();
|
||||
let dropdown = getDropdownMenu(".modal .o_optional_columns_dropdown");
|
||||
expect(queryAll(".dropdown-item", { root: dropdown })).toHaveCount(1);
|
||||
expect(queryAll(".dropdown-item-studio", { root: dropdown })).toHaveCount(0);
|
||||
await click(".modal-header .btn-close");
|
||||
await animationFrame();
|
||||
expect(".modal").toHaveCount(0);
|
||||
|
||||
await click(".o_optional_columns_dropdown_toggle");
|
||||
await animationFrame();
|
||||
expect(".o-dropdown--menu").toHaveCount(0);
|
||||
await click(".o_optional_columns_dropdown_toggle");
|
||||
await animationFrame();
|
||||
|
||||
dropdown = getDropdownMenu(".o_optional_columns_dropdown");
|
||||
expect(queryAll(".dropdown-item", { root: dropdown })).toHaveCount(2);
|
||||
expect(queryAll(".dropdown-item-studio", { root: dropdown })).toHaveCount(1);
|
||||
});
|
||||
|
|
@ -1,164 +0,0 @@
|
|||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { click } from "@odoo/hoot-dom";
|
||||
import { animationFrame } from "@odoo/hoot-mock";
|
||||
import {
|
||||
defineActions,
|
||||
defineModels,
|
||||
getService,
|
||||
fields,
|
||||
models,
|
||||
mountWithCleanup,
|
||||
onRpc,
|
||||
stepAllNetworkCalls,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { redirect } from "@web/core/utils/urls";
|
||||
import { WebClientOdex } from "@odex30_web/webclient/webclient";
|
||||
|
||||
class Partner extends models.Model {
|
||||
name = fields.Char();
|
||||
|
||||
_records = [
|
||||
{ id: 1, name: "First record" },
|
||||
{ id: 2, name: "Second record" },
|
||||
];
|
||||
_views = {
|
||||
form: `
|
||||
<form>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
</group>
|
||||
</form>
|
||||
`,
|
||||
kanban: `
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="card">
|
||||
<field name="name"/>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
`,
|
||||
list: `<list><field name="name"/></list>`,
|
||||
};
|
||||
}
|
||||
|
||||
defineModels([Partner]);
|
||||
|
||||
defineActions([
|
||||
{
|
||||
id: 1,
|
||||
name: "Partners Action 1",
|
||||
res_model: "partner",
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "kanban"],
|
||||
[false, "form"],
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Partners Action 2",
|
||||
res_model: "partner",
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "form"],
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
describe.current.tags("mobile");
|
||||
|
||||
test("uses a mobile-friendly view by default (if possible)", async () => {
|
||||
onRpc("has_group", () => true);
|
||||
|
||||
await mountWithCleanup(WebClientOdex);
|
||||
await animationFrame();
|
||||
// should default on a mobile-friendly view (kanban) for action 1
|
||||
await getService("action").doAction(1);
|
||||
|
||||
expect(".o_list_view").toHaveCount(0);
|
||||
expect(".o_kanban_view").toHaveCount(1);
|
||||
|
||||
// there is no mobile-friendly view for action 2, should use the first one (list)
|
||||
await getService("action").doAction(2);
|
||||
|
||||
expect(".o_list_view").toHaveCount(1);
|
||||
expect(".o_kanban_view").toHaveCount(0);
|
||||
});
|
||||
|
||||
test("lazy load mobile-friendly view", async () => {
|
||||
stepAllNetworkCalls();
|
||||
|
||||
redirect("/odoo/action-1/new");
|
||||
await mountWithCleanup(WebClientOdex);
|
||||
await animationFrame();
|
||||
|
||||
expect(".o_list_view").toHaveCount(0);
|
||||
expect(".o_kanban_view").toHaveCount(0);
|
||||
expect(".o_form_view").toHaveCount(1);
|
||||
|
||||
// go back to lazy loaded view
|
||||
await click(".o_breadcrumb .o_back_button");
|
||||
await animationFrame();
|
||||
expect(".o_list_view").toHaveCount(0);
|
||||
expect(".o_form_view").toHaveCount(0);
|
||||
expect(".o_kanban_view").toHaveCount(1);
|
||||
|
||||
expect.verifySteps([
|
||||
"/web/webclient/translations",
|
||||
"/web/webclient/load_menus",
|
||||
"/web/action/load",
|
||||
"get_views",
|
||||
"onchange", // default_get/onchange to open form view
|
||||
"web_search_read", // web search read when coming back to Kanban
|
||||
]);
|
||||
});
|
||||
|
||||
test("lazy load mobile-friendly view; legacy url", async () => {
|
||||
stepAllNetworkCalls();
|
||||
|
||||
redirect("/web#action=1&view_type=form");
|
||||
await mountWithCleanup(WebClientOdex);
|
||||
await animationFrame();
|
||||
|
||||
expect(".o_list_view").toHaveCount(0);
|
||||
expect(".o_kanban_view").toHaveCount(0);
|
||||
expect(".o_form_view").toHaveCount(1);
|
||||
|
||||
// go back to lazy loaded view
|
||||
await click(".o_breadcrumb .o_back_button");
|
||||
await animationFrame();
|
||||
expect(".o_list_view").toHaveCount(0);
|
||||
expect(".o_form_view").toHaveCount(0);
|
||||
expect(".o_kanban_view").toHaveCount(1);
|
||||
|
||||
expect.verifySteps([
|
||||
"/web/webclient/translations",
|
||||
"/web/webclient/load_menus",
|
||||
"/web/action/load",
|
||||
"get_views",
|
||||
"onchange", // default_get/onchange to open form view
|
||||
"web_search_read", // web search read when coming back to Kanban
|
||||
]);
|
||||
});
|
||||
|
||||
test("view switcher button should be displayed in dropdown on mobile screens", async () => {
|
||||
// This test will spawn a kanban view (mobile friendly).
|
||||
// so, the "legacy" code won't be tested here.
|
||||
await mountWithCleanup(WebClientOdex);
|
||||
await animationFrame();
|
||||
|
||||
await getService("action").doAction(1);
|
||||
|
||||
expect(".o_control_panel .o_cp_switch_buttons > button").toHaveCount(1);
|
||||
expect(".o_control_panel .o_cp_switch_buttons .o_switch_view.o_kanban").toHaveCount(0);
|
||||
expect(".o_control_panel .o_cp_switch_buttons button.o_switch_view").toHaveCount(0);
|
||||
|
||||
expect(".o_control_panel .o_cp_switch_buttons > button > i").toHaveClass("oi-view-kanban");
|
||||
await click(".o_control_panel .o_cp_switch_buttons > button");
|
||||
await animationFrame();
|
||||
|
||||
expect(".dropdown-item:has(.oi-view-kanban)").toHaveClass("selected");
|
||||
expect(".dropdown-item:has(.oi-view-list)").not.toHaveClass("selected");
|
||||
});
|
||||
|
|
@ -1,176 +0,0 @@
|
|||
import { beforeEach, describe, expect, test } from "@odoo/hoot";
|
||||
import { Deferred, mockDate } from "@odoo/hoot-mock";
|
||||
import {
|
||||
defineActions,
|
||||
defineMenus,
|
||||
defineModels,
|
||||
fields,
|
||||
models,
|
||||
mountWithCleanup,
|
||||
onRpc,
|
||||
patchWithCleanup,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { WebClient } from "@web/webclient/webclient";
|
||||
import { SUCCESS_SIGNAL } from "@web/webclient/clickbot/clickbot";
|
||||
|
||||
class Foo extends models.Model {
|
||||
foo = fields.Char();
|
||||
bar = fields.Boolean();
|
||||
date = fields.Date();
|
||||
|
||||
_records = [
|
||||
{ id: 1, bar: true, foo: "yop", date: "2017-01-25" },
|
||||
{ id: 2, bar: true, foo: "blip" },
|
||||
{ id: 3, bar: true, foo: "gnap" },
|
||||
{ id: 4, bar: false, foo: "blip" },
|
||||
];
|
||||
|
||||
_views = {
|
||||
search: /* xml */ `
|
||||
<search>
|
||||
<filter string="Not Bar" name="not bar" domain="[['bar','=',False]]"/>
|
||||
<filter string="Date" name="date" date="date"/>
|
||||
</search>
|
||||
`,
|
||||
list: /* xml */ `
|
||||
<list>
|
||||
<field name="foo" />
|
||||
</list>
|
||||
`,
|
||||
kanban: /* xml */ `
|
||||
<kanban class="o_kanban_test">
|
||||
<templates><t t-name="card">
|
||||
<field name="foo"/>
|
||||
</t></templates>
|
||||
</kanban>
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
describe.current.tags("desktop");
|
||||
|
||||
defineModels([Foo]);
|
||||
|
||||
beforeEach(() => {
|
||||
defineActions([
|
||||
{
|
||||
id: 1001,
|
||||
name: "App1",
|
||||
res_model: "foo",
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "kanban"],
|
||||
],
|
||||
xml_id: "app1",
|
||||
},
|
||||
{
|
||||
id: 1002,
|
||||
name: "App2 Menu 1",
|
||||
res_model: "foo",
|
||||
views: [[false, "kanban"]],
|
||||
xml_id: "app2_menu1",
|
||||
},
|
||||
{
|
||||
id: 1022,
|
||||
name: "App2 Menu 2",
|
||||
res_model: "foo",
|
||||
views: [[false, "list"]],
|
||||
xml_id: "app2_menu2",
|
||||
},
|
||||
]);
|
||||
defineMenus([
|
||||
{ id: 1, name: "App1", appID: 1, actionID: 1001, xmlid: "app1" },
|
||||
{
|
||||
id: 2,
|
||||
children: [
|
||||
{
|
||||
id: 3,
|
||||
name: "menu 1",
|
||||
appID: 2,
|
||||
actionID: 1002,
|
||||
xmlid: "app2_menu1",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "menu 2",
|
||||
appID: 2,
|
||||
actionID: 1022,
|
||||
xmlid: "app2_menu2",
|
||||
},
|
||||
],
|
||||
name: "App2",
|
||||
appID: 2,
|
||||
actionID: 1002,
|
||||
xmlid: "app2",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("clickbot clickeverywhere test", async () => {
|
||||
onRpc("has_group", () => true);
|
||||
mockDate("2017-10-08T15:35:11.000");
|
||||
const clickEverywhereDef = new Deferred();
|
||||
patchWithCleanup(browser, {
|
||||
console: {
|
||||
log: (msg) => {
|
||||
expect.step(msg);
|
||||
if (msg === SUCCESS_SIGNAL) {
|
||||
clickEverywhereDef.resolve();
|
||||
}
|
||||
},
|
||||
error: (msg) => {
|
||||
expect.step(msg);
|
||||
clickEverywhereDef.resolve();
|
||||
},
|
||||
},
|
||||
});
|
||||
const webClient = await mountWithCleanup(WebClient);
|
||||
patchWithCleanup(odoo, {
|
||||
__WOWL_DEBUG__: { root: webClient },
|
||||
});
|
||||
window.clickEverywhere();
|
||||
await clickEverywhereDef;
|
||||
expect.verifySteps([
|
||||
"Clicking on: apps menu toggle button",
|
||||
"Testing app menu: app1",
|
||||
"Testing menu App1 app1",
|
||||
'Clicking on: menu item "App1"',
|
||||
"Testing 2 filters",
|
||||
'Clicking on: filter "Not Bar"',
|
||||
'Clicking on: filter "Date"',
|
||||
'Clicking on: filter option "October"',
|
||||
"Testing view switch: kanban",
|
||||
"Clicking on: kanban view switcher",
|
||||
"Testing 2 filters",
|
||||
'Clicking on: filter "Not Bar"',
|
||||
'Clicking on: filter "Date"',
|
||||
'Clicking on: filter option "October"',
|
||||
"Clicking on: apps menu toggle button",
|
||||
"Testing app menu: app2",
|
||||
"Testing menu App2 app2",
|
||||
'Clicking on: menu item "App2"',
|
||||
"Testing 2 filters",
|
||||
'Clicking on: filter "Not Bar"',
|
||||
'Clicking on: filter "Date"',
|
||||
'Clicking on: filter option "October"',
|
||||
"Testing menu menu 1 app2_menu1",
|
||||
'Clicking on: menu item "menu 1"',
|
||||
"Testing 2 filters",
|
||||
'Clicking on: filter "Not Bar"',
|
||||
'Clicking on: filter "Date"',
|
||||
'Clicking on: filter option "October"',
|
||||
"Testing menu menu 2 app2_menu2",
|
||||
'Clicking on: menu item "menu 2"',
|
||||
"Testing 2 filters",
|
||||
'Clicking on: filter "Not Bar"',
|
||||
'Clicking on: filter "Date"',
|
||||
'Clicking on: filter option "October"',
|
||||
"Successfully tested 2 apps",
|
||||
"Successfully tested 2 menus",
|
||||
"Successfully tested 0 modals",
|
||||
"Successfully tested 10 filters",
|
||||
SUCCESS_SIGNAL,
|
||||
]);
|
||||
});
|
||||
|
|
@ -1,615 +0,0 @@
|
|||
import { expect, test } from "@odoo/hoot";
|
||||
import { click, queryFirst, edit } from "@odoo/hoot-dom";
|
||||
import { animationFrame, mockDate, runAllTimers } from "@odoo/hoot-mock";
|
||||
import {
|
||||
getService,
|
||||
mockService,
|
||||
mountWithCleanup,
|
||||
onRpc,
|
||||
patchWithCleanup,
|
||||
serverState,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { session } from "@web/session";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { WebClientOdex } from "@odex30_web/webclient/webclient";
|
||||
|
||||
test("Expiration Panel one app installed", async () => {
|
||||
mockDate("2019-10-10T12:00:00");
|
||||
|
||||
patchWithCleanup(session, {
|
||||
expiration_date: "2019-11-09 12:00:00",
|
||||
expiration_reason: "",
|
||||
storeData: true,
|
||||
warning: "admin",
|
||||
});
|
||||
await mountWithCleanup(WebClientOdex);
|
||||
await animationFrame();
|
||||
await getService("action").doAction("menu");
|
||||
|
||||
expect(".oe_instance_register").toHaveText("This database will expire in 1 month.");
|
||||
|
||||
expect(".database_expiration_panel").toHaveClass("alert-info");
|
||||
|
||||
// Close the expiration panel
|
||||
await click(".oe_instance_hide_panel");
|
||||
await animationFrame();
|
||||
|
||||
expect(".database_expiration_panel").toHaveCount(0);
|
||||
});
|
||||
|
||||
test("Expiration Panel one app installed, buy subscription", async () => {
|
||||
expect.assertions(6);
|
||||
|
||||
mockDate("2019-10-10T12:00:00");
|
||||
patchWithCleanup(session, {
|
||||
expiration_date: "2019-10-24 12:00:00",
|
||||
expiration_reason: "demo",
|
||||
storeData: true, // used by subscription service to know whether mail is installed
|
||||
warning: "admin",
|
||||
});
|
||||
onRpc("res.users", "search_count", () => 7);
|
||||
await mountWithCleanup(WebClientOdex);
|
||||
await animationFrame();
|
||||
await runAllTimers();
|
||||
|
||||
expect(".oe_instance_register").toHaveText(
|
||||
"This demo database will expire in 14 days. Register your subscription or buy a subscription."
|
||||
);
|
||||
|
||||
expect(".database_expiration_panel").toHaveClass("alert-warning", {
|
||||
message: "Color should be orange",
|
||||
});
|
||||
|
||||
expect(".oe_instance_register_show").toHaveCount(1, {
|
||||
message: "Part 'Register your subscription'",
|
||||
});
|
||||
expect(".oe_instance_buy").toHaveCount(1, { message: "Part 'buy a subscription'" });
|
||||
expect(".oe_instance_register_form").toHaveCount(0, {
|
||||
message: "There should be no registration form",
|
||||
});
|
||||
|
||||
// Click on 'buy subscription'
|
||||
await click(".oe_instance_buy");
|
||||
await animationFrame();
|
||||
|
||||
expect(browser.location.href).toBe("https://www.odoo.com/odoo-enterprise/upgrade?num_users=7");
|
||||
});
|
||||
|
||||
test("Expiration Panel one app installed, try several times to register subscription", async () => {
|
||||
expect.assertions(33);
|
||||
|
||||
mockDate("2019-10-10T12:00:00");
|
||||
|
||||
let callToGetParamCount = 0;
|
||||
|
||||
patchWithCleanup(session, {
|
||||
expiration_date: "2019-10-15 12:00:00",
|
||||
expiration_reason: "trial",
|
||||
storeData: true, // used by subscription service to know whether mail is installed
|
||||
warning: "admin",
|
||||
});
|
||||
|
||||
mockService("notification", {
|
||||
add: (message, options) => {
|
||||
expect.step(JSON.stringify({ message, options }));
|
||||
},
|
||||
});
|
||||
onRpc("get_param", ({ args }) => {
|
||||
expect.step("get_param");
|
||||
if (args[0] === "database.already_linked_subscription_url") {
|
||||
return false;
|
||||
}
|
||||
if (args[0] === "database.already_linked_email") {
|
||||
return "super_company_admin@gmail.com";
|
||||
}
|
||||
expect(args[0]).toBe("database.expiration_date");
|
||||
callToGetParamCount++;
|
||||
if (callToGetParamCount <= 3) {
|
||||
return "2019-10-15 12:00:00";
|
||||
} else {
|
||||
return "2019-11-15 12:00:00";
|
||||
}
|
||||
});
|
||||
onRpc("set_param", ({ args }) => {
|
||||
expect.step("set_param");
|
||||
expect(args[0]).toBe("database.enterprise_code");
|
||||
if (callToGetParamCount === 1) {
|
||||
expect(args[1]).toBe("ABCDEF");
|
||||
} else {
|
||||
expect(args[1]).toBe("ABC");
|
||||
}
|
||||
return true;
|
||||
});
|
||||
onRpc("update_notification", ({ args }) => {
|
||||
expect.step("update_notification");
|
||||
expect(args[0]).toBeInstanceOf(Array);
|
||||
expect(args[0]).toHaveLength(0);
|
||||
return true;
|
||||
});
|
||||
onRpc("res.users", "search_count", () => 7);
|
||||
await mountWithCleanup(WebClientOdex);
|
||||
await animationFrame();
|
||||
|
||||
expect(".oe_instance_register").toHaveText(
|
||||
"This database will expire in 5 days. Register your subscription or buy a subscription."
|
||||
);
|
||||
|
||||
expect(".database_expiration_panel").toHaveClass("alert-danger", {
|
||||
message: "Color should be red",
|
||||
});
|
||||
|
||||
expect(".oe_instance_register_show").toHaveCount(1, {
|
||||
message: "Part 'Register your subscription'",
|
||||
});
|
||||
expect(".oe_instance_buy").toHaveCount(1, { message: "Part 'buy a subscription'" });
|
||||
expect(".oe_instance_register_form").toHaveCount(0, {
|
||||
message: "There should be no registration form",
|
||||
});
|
||||
|
||||
// Click on 'buy subscription'
|
||||
await click(".oe_instance_register_show");
|
||||
await animationFrame();
|
||||
|
||||
expect(".oe_instance_register_form").toHaveCount(1, {
|
||||
message: "there should be a registration form",
|
||||
});
|
||||
expect('.oe_instance_register_form input[placeholder="Paste code here"]').toHaveCount(1, {
|
||||
message: "with an input with place holder 'Paste code here'",
|
||||
});
|
||||
expect(".oe_instance_register_form button").toHaveCount(1, {
|
||||
message: "and a button 'Register'",
|
||||
});
|
||||
expect(".oe_instance_register_form button").toHaveText("Register");
|
||||
|
||||
await click(".oe_instance_register_form button");
|
||||
await animationFrame();
|
||||
|
||||
expect(".oe_instance_register_form").toHaveCount(1, {
|
||||
message: "there should be a registration form",
|
||||
});
|
||||
expect('.oe_instance_register_form input[placeholder="Paste code here"]').toHaveCount(1, {
|
||||
message: "with an input with place holder 'Paste code here'",
|
||||
});
|
||||
expect(".oe_instance_register_form button").toHaveCount(1, {
|
||||
message: "and a button 'Register'",
|
||||
});
|
||||
|
||||
await click(".oe_instance_register_form input");
|
||||
await edit("ABCDEF");
|
||||
await animationFrame();
|
||||
await click(".oe_instance_register_form button");
|
||||
await animationFrame();
|
||||
|
||||
expect(queryFirst(".oe_instance_register")).toHaveText(
|
||||
"Something went wrong while registering your database. You can try again or contact Odoo Support."
|
||||
);
|
||||
expect(".database_expiration_panel").toHaveClass("alert-danger", {
|
||||
message: "Color should be red",
|
||||
});
|
||||
expect("span.oe_instance_error").toHaveCount(1);
|
||||
expect(".oe_instance_register_form").toHaveCount(1, {
|
||||
message: "there should be a registration form",
|
||||
});
|
||||
expect('.oe_instance_register_form input[placeholder="Paste code here"]').toHaveCount(1, {
|
||||
message: "with an input with place holder 'Paste code here'",
|
||||
});
|
||||
expect(".oe_instance_register_form button").toHaveCount(1, {
|
||||
message: "and a button 'Register'",
|
||||
});
|
||||
expect(".oe_instance_register_form button").toHaveText("Retry");
|
||||
|
||||
await click(".oe_instance_register_form input");
|
||||
await edit("ABC");
|
||||
await animationFrame();
|
||||
await click(".oe_instance_register_form button");
|
||||
await animationFrame();
|
||||
|
||||
expect(".database_expiration_panel").toHaveCount(0, {
|
||||
message: "expiration panel should be gone",
|
||||
});
|
||||
|
||||
expect.verifySteps([
|
||||
// second try to submit
|
||||
"get_param",
|
||||
"set_param",
|
||||
"update_notification",
|
||||
"get_param",
|
||||
"get_param",
|
||||
"get_param",
|
||||
// third try
|
||||
"get_param",
|
||||
"set_param",
|
||||
"update_notification",
|
||||
"get_param",
|
||||
"get_param",
|
||||
"get_param",
|
||||
`{"message":"Thank you, your registration was successful! Your database is valid until November 15, 2019.","options":{"type":"success"}}`,
|
||||
]);
|
||||
});
|
||||
|
||||
test("Expiration Panel one app installed, subscription already linked", async () => {
|
||||
expect.assertions(5);
|
||||
|
||||
mockDate("2019-10-10T12:00:00");
|
||||
|
||||
let getExpirationDateCount = 0;
|
||||
|
||||
patchWithCleanup(session, {
|
||||
expiration_date: "2019-10-15 12:00:00",
|
||||
expiration_reason: "trial",
|
||||
storeData: true, // used by subscription service to know whether mail is installed
|
||||
warning: "admin",
|
||||
});
|
||||
onRpc("/already/linked/send/mail/url", () => ({
|
||||
result: false,
|
||||
reason: "By design",
|
||||
}));
|
||||
onRpc("get_param", ({ args }) => {
|
||||
expect.step("get_param");
|
||||
if (args[0] === "database.expiration_date") {
|
||||
getExpirationDateCount++;
|
||||
if (getExpirationDateCount === 1) {
|
||||
return "2019-10-15 12:00:00";
|
||||
} else {
|
||||
return "2019-11-17 12:00:00";
|
||||
}
|
||||
}
|
||||
if (args[0] === "database.already_linked_subscription_url") {
|
||||
return "www.super_company.com";
|
||||
}
|
||||
if (args[0] === "database.already_linked_send_mail_url") {
|
||||
return "/already/linked/send/mail/url";
|
||||
}
|
||||
if (args[0] === "database.already_linked_email") {
|
||||
return "super_company_admin@gmail.com";
|
||||
}
|
||||
});
|
||||
onRpc("set_param", () => {
|
||||
expect.step("set_param");
|
||||
return true;
|
||||
});
|
||||
onRpc("update_notification", () => {
|
||||
expect.step("update_notification");
|
||||
return true;
|
||||
});
|
||||
await mountWithCleanup(WebClientOdex);
|
||||
await animationFrame();
|
||||
|
||||
expect(".oe_instance_register").toHaveText(
|
||||
"This database will expire in 5 days. Register your subscription or buy a subscription."
|
||||
);
|
||||
// Click on 'register your subscription'
|
||||
await click(".oe_instance_register_show");
|
||||
await animationFrame();
|
||||
await click(".oe_instance_register_form input");
|
||||
await edit("ABC");
|
||||
await click(".oe_instance_register_form button");
|
||||
await animationFrame();
|
||||
|
||||
expect(".oe_instance_register.oe_database_already_linked").toHaveText(
|
||||
`Your subscription is already linked to a database.\nSend an email to the subscription owner to confirm the change, enter a new code or buy a subscription.`
|
||||
);
|
||||
|
||||
await click("a.oe_contract_send_mail");
|
||||
await animationFrame();
|
||||
expect(".database_expiration_panel").toHaveClass("alert-danger", {
|
||||
message: "Color should be red",
|
||||
});
|
||||
|
||||
expect(".oe_instance_register.oe_database_already_linked").toHaveText(
|
||||
`Your subscription is already linked to a database.\nSend an email to the subscription owner to confirm the change, enter a new code or buy a subscription.\n\nUnable to send the instructions by email, please contact the Odoo Support\nError reason: By design`
|
||||
);
|
||||
|
||||
expect.verifySteps([
|
||||
"get_param",
|
||||
"set_param",
|
||||
"update_notification",
|
||||
"get_param",
|
||||
"get_param",
|
||||
"get_param",
|
||||
"get_param",
|
||||
]);
|
||||
});
|
||||
|
||||
test("One app installed, database expired", async () => {
|
||||
expect.assertions(8);
|
||||
|
||||
mockDate("2019-10-10T12:00:00");
|
||||
|
||||
let callToGetParamCount = 0;
|
||||
|
||||
patchWithCleanup(session, {
|
||||
expiration_date: "2019-10-08 12:00:00",
|
||||
expiration_reason: "trial",
|
||||
storeData: true, // used by subscription service to know whether mail is installed
|
||||
warning: "admin",
|
||||
});
|
||||
onRpc("/already/linked/send/mail/url", () => ({
|
||||
result: false,
|
||||
reason: "By design",
|
||||
}));
|
||||
onRpc("get_param", ({ args }) => {
|
||||
if (args[0] === "database.already_linked_subscription_url") {
|
||||
return false;
|
||||
}
|
||||
callToGetParamCount++;
|
||||
if (callToGetParamCount === 1) {
|
||||
return "2019-10-09 12:00:00";
|
||||
} else {
|
||||
return "2019-11-09 12:00:00";
|
||||
}
|
||||
});
|
||||
onRpc("set_param", () => true);
|
||||
onRpc("update_notification", () => true);
|
||||
await mountWithCleanup(WebClientOdex);
|
||||
await animationFrame();
|
||||
|
||||
expect(".oe_instance_register").toHaveText(
|
||||
"This database has expired. Register your subscription or buy a subscription."
|
||||
);
|
||||
expect(".o_blockUI").toHaveCount(1, { message: "UI should be blocked" });
|
||||
|
||||
expect(".database_expiration_panel").toHaveClass("alert-danger", {
|
||||
message: "Color should be red",
|
||||
});
|
||||
expect(".oe_instance_register_show").toHaveCount(1, {
|
||||
message: "Part 'Register your subscription'",
|
||||
});
|
||||
expect(".oe_instance_buy").toHaveCount(1, { message: "Part 'buy a subscription'" });
|
||||
|
||||
expect(".oe_instance_register_form").toHaveCount(0);
|
||||
|
||||
// Click on 'Register your subscription'
|
||||
await click(".oe_instance_register_show");
|
||||
await animationFrame();
|
||||
await click(".oe_instance_register_form input");
|
||||
await edit("ABC");
|
||||
await click(".oe_instance_register_form button");
|
||||
await animationFrame();
|
||||
|
||||
expect(".oe_instance_register").toHaveText(
|
||||
"Thank you, your registration was successful! Your database is valid until November 9, 2019."
|
||||
);
|
||||
expect(".o_blockUI").toHaveCount(0, { message: "UI should no longer be blocked" });
|
||||
});
|
||||
|
||||
test("One app installed, renew", async () => {
|
||||
expect.assertions(7);
|
||||
|
||||
mockDate("2019-10-10T12:00:00");
|
||||
|
||||
patchWithCleanup(session, {
|
||||
expiration_date: "2019-10-20 12:00:00",
|
||||
expiration_reason: "renewal",
|
||||
storeData: true, // used by subscription service to know whether mail is installed
|
||||
warning: "admin",
|
||||
});
|
||||
onRpc("get_param", ({ args }) => {
|
||||
expect.step("get_param");
|
||||
expect(args[0]).toBe("database.enterprise_code");
|
||||
return "ABC";
|
||||
});
|
||||
await mountWithCleanup(WebClientOdex);
|
||||
await animationFrame();
|
||||
|
||||
expect(".oe_instance_register").toHaveText(
|
||||
"Your subscription expired 9 days ago. This database will be blocked soon.\n" +
|
||||
"Renew now\n" +
|
||||
"I paid, please recheck!"
|
||||
);
|
||||
|
||||
expect(".database_expiration_panel").toHaveClass("alert-warning", {
|
||||
message: "Color should be orange",
|
||||
});
|
||||
expect(".oe_instance_renew").toHaveCount(1, { message: "Part 'Register your subscription'" });
|
||||
expect("a.check_enterprise_status").toHaveCount(1, {
|
||||
message: "there should be a button for status checking",
|
||||
});
|
||||
|
||||
expect(".oe_instance_register_form").toHaveCount(0);
|
||||
|
||||
// Click on 'Renew your subscription'
|
||||
await click(".oe_instance_renew");
|
||||
await animationFrame();
|
||||
|
||||
expect.verifySteps(["get_param"]);
|
||||
});
|
||||
|
||||
test("One app installed, check status and get success", async () => {
|
||||
expect.assertions(4);
|
||||
|
||||
mockDate("2019-10-10T12:00:00");
|
||||
|
||||
patchWithCleanup(session, {
|
||||
expiration_date: "2019-10-20 12:00:00",
|
||||
expiration_reason: "renewal",
|
||||
storeData: true, // used by subscription service to know whether mail is installed
|
||||
warning: "admin",
|
||||
});
|
||||
onRpc("get_param", ({ args }) => {
|
||||
expect.step("get_param");
|
||||
expect(args[0]).toBe("database.expiration_date");
|
||||
return "2019-10-24 12:00:00";
|
||||
});
|
||||
onRpc("update_notification", () => {
|
||||
expect.step("update_notification");
|
||||
return true;
|
||||
});
|
||||
await mountWithCleanup(WebClientOdex);
|
||||
await animationFrame();
|
||||
|
||||
// click on "I paid, please recheck!"
|
||||
expect("a.check_enterprise_status").toHaveText("I paid, please recheck!");
|
||||
await click("a.check_enterprise_status");
|
||||
await animationFrame();
|
||||
|
||||
expect(".oe_instance_register.oe_subscription_updated").toHaveText(
|
||||
"Your subscription was updated and is valid until October 24, 2019."
|
||||
);
|
||||
|
||||
expect.verifySteps(["update_notification", "get_param"]);
|
||||
});
|
||||
|
||||
// Why would we want to reload the page when we check the status and it hasn't changed?
|
||||
test.skip("One app installed, check status and get page reload", async () => {
|
||||
expect.assertions(4);
|
||||
|
||||
mockDate("2019-10-10T12:00:00");
|
||||
|
||||
patchWithCleanup(session, {
|
||||
expiration_date: "2019-10-20 12:00:00",
|
||||
expiration_reason: "renewal",
|
||||
storeData: true, // used by subscription service to know whether mail is installed
|
||||
warning: "admin",
|
||||
});
|
||||
onRpc("get_param", () => {
|
||||
expect.step("get_param");
|
||||
return "2019-10-20 12:00:00";
|
||||
});
|
||||
onRpc("update_notification", () => {
|
||||
expect.step("update_notification");
|
||||
return true;
|
||||
});
|
||||
await mountWithCleanup(WebClientOdex);
|
||||
await animationFrame();
|
||||
|
||||
// click on "I paid, please recheck!"
|
||||
await click("a.check_enterprise_status");
|
||||
await animationFrame();
|
||||
|
||||
expect.verifySteps(["update_notification", "get_param", "reloadPage"]);
|
||||
});
|
||||
|
||||
test("One app installed, upgrade database", async () => {
|
||||
expect.assertions(4);
|
||||
|
||||
mockDate("2019-10-10T12:00:00");
|
||||
|
||||
patchWithCleanup(session, {
|
||||
expiration_date: "2019-10-20 12:00:00",
|
||||
expiration_reason: "upsell",
|
||||
storeData: true, // used by subscription service to know whether mail is installed
|
||||
warning: "admin",
|
||||
});
|
||||
onRpc("get_param", ({ args }) => {
|
||||
expect.step("get_param");
|
||||
expect(args[0]).toBe("database.enterprise_code");
|
||||
return "ABC";
|
||||
});
|
||||
onRpc("search_count", () => {
|
||||
expect.step("search_count");
|
||||
return 13;
|
||||
});
|
||||
onRpc("update_notification", () => true);
|
||||
await mountWithCleanup(WebClientOdex);
|
||||
await animationFrame();
|
||||
await runAllTimers();
|
||||
|
||||
expect(".oe_instance_register").toHaveText(
|
||||
"This database will expire in 10 days. You have more users or more apps installed than your subscription allows.\n\n" +
|
||||
"Upgrade your subscription\n" +
|
||||
"I paid, please recheck!"
|
||||
);
|
||||
|
||||
await click("a.oe_instance_upsell");
|
||||
await animationFrame();
|
||||
|
||||
expect.verifySteps(["get_param", "search_count"]);
|
||||
expect(browser.location.href).toBe(
|
||||
"https://www.odoo.com/odoo-enterprise/upsell?num_users=13&contract=ABC"
|
||||
);
|
||||
});
|
||||
|
||||
test("One app installed, message for non admin user", async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
mockDate("2019-10-10T12:00:00");
|
||||
|
||||
patchWithCleanup(session, {
|
||||
expiration_date: "2019-11-08 12:00:00",
|
||||
expiration_reason: "",
|
||||
storeData: true, // used by subscription service to know whether mail is installed
|
||||
warning: "user",
|
||||
});
|
||||
await mountWithCleanup(WebClientOdex);
|
||||
await animationFrame();
|
||||
|
||||
expect(".oe_instance_register").toHaveText(
|
||||
"This database will expire in 29 days. Log in as an administrator to correct the issue."
|
||||
);
|
||||
|
||||
expect(".database_expiration_panel").toHaveClass("alert-info", {
|
||||
message: "Color should be grey",
|
||||
});
|
||||
});
|
||||
|
||||
test("One app installed, navigation to renewal page", async () => {
|
||||
expect.assertions(8);
|
||||
|
||||
mockDate("2019-11-10T00:00:00");
|
||||
|
||||
patchWithCleanup(session, {
|
||||
expiration_date: "2019-10-20 12:00:00",
|
||||
expiration_reason: "renewal",
|
||||
storeData: true, // used by subscription service to know whether mail is installed
|
||||
warning: "admin",
|
||||
});
|
||||
onRpc("get_param", ({ args }) => {
|
||||
expect.step("get_param");
|
||||
expect(args[0]).toBe("database.enterprise_code");
|
||||
return "ABC";
|
||||
});
|
||||
onRpc("update_notification", () => {
|
||||
expect.step("update_notification");
|
||||
return true;
|
||||
});
|
||||
await mountWithCleanup(WebClientOdex);
|
||||
await animationFrame();
|
||||
await runAllTimers();
|
||||
|
||||
expect(".oe_instance_register").toHaveText(
|
||||
"This database has expired.\nRenew now\nI paid, please recheck!"
|
||||
);
|
||||
|
||||
expect(".database_expiration_panel").toHaveClass("alert-danger");
|
||||
expect(".oe_instance_renew").toHaveCount(1, { message: "Part 'Register your subscription'" });
|
||||
expect("a.check_enterprise_status").toHaveCount(1, {
|
||||
message: "there should be a button for status checking",
|
||||
});
|
||||
|
||||
expect(".oe_instance_register_form").toHaveCount(0);
|
||||
|
||||
// Click on 'Renew your subscription'
|
||||
await click(".oe_instance_renew");
|
||||
await animationFrame();
|
||||
|
||||
expect(browser.location.href).toBe("https://www.odoo.com/odoo-enterprise/renew?contract=ABC");
|
||||
|
||||
expect.verifySteps(["get_param"]);
|
||||
});
|
||||
|
||||
test("One app installed, different locale (arabic)", async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
mockDate("2019-25-09T12:00:00");
|
||||
|
||||
patchWithCleanup(session, {
|
||||
expiration_date: "2019-10-20 12:00:00",
|
||||
expiration_reason: "renewal",
|
||||
storeData: true, // used by subscription service to know whether mail is installed
|
||||
warning: "admin",
|
||||
});
|
||||
serverState.lang = "ar-001";
|
||||
onRpc("get_param", () => "2019-11-09 12:00:00");
|
||||
onRpc("update_notification", () => true);
|
||||
await mountWithCleanup(WebClientOdex);
|
||||
await animationFrame();
|
||||
|
||||
await click("a.check_enterprise_status");
|
||||
await animationFrame();
|
||||
|
||||
expect(".oe_instance_register").toHaveText(
|
||||
"Your subscription was updated and is valid until ٩ نوفمبر ٢٠١٩."
|
||||
);
|
||||
});
|
||||
|
|
@ -1,353 +0,0 @@
|
|||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { click, drag, keyDown, pointerDown, queryFirst } from "@odoo/hoot-dom";
|
||||
import { advanceTime, animationFrame, mockDate, mockTouch } from "@odoo/hoot-mock";
|
||||
import {
|
||||
getService,
|
||||
mountWithCleanup,
|
||||
onRpc,
|
||||
patchWithCleanup,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { reactive } from "@odoo/owl";
|
||||
import { session } from "@web/session";
|
||||
import { HomeMenu } from "@odex30_web/webclient/home_menu/home_menu";
|
||||
import { reorderApps } from "@web/webclient/menus/menu_helpers";
|
||||
|
||||
async function walkOn(path) {
|
||||
for (const step of path) {
|
||||
await keyDown(`${step.shiftKey ? "shift+" : ""}${step.key}`);
|
||||
await animationFrame();
|
||||
expect(`.o_menuitem:eq(${step.index})`).toHaveClass("o_focused", {
|
||||
message: `step ${step.number}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const getDefaultHomeMenuProps = () => {
|
||||
const apps = [
|
||||
{
|
||||
actionID: 121,
|
||||
href: "/odoo/action-121",
|
||||
appID: 1,
|
||||
id: 1,
|
||||
label: "Discuss",
|
||||
parents: "",
|
||||
webIcon: false,
|
||||
xmlid: "app.1",
|
||||
},
|
||||
{
|
||||
actionID: 122,
|
||||
href: "/odoo/action-122",
|
||||
appID: 2,
|
||||
id: 2,
|
||||
label: "Calendar",
|
||||
parents: "",
|
||||
webIcon: false,
|
||||
xmlid: "app.2",
|
||||
},
|
||||
{
|
||||
actionID: 123,
|
||||
href: "/odoo/contacts",
|
||||
appID: 3,
|
||||
id: 3,
|
||||
label: "Contacts",
|
||||
parents: "",
|
||||
webIcon: false,
|
||||
xmlid: "app.3",
|
||||
},
|
||||
];
|
||||
return { apps, reorderApps: (order) => reorderApps(apps, order) };
|
||||
};
|
||||
|
||||
describe.current.tags("desktop");
|
||||
|
||||
test("ESC Support", async () => {
|
||||
await mountWithCleanup(HomeMenu, {
|
||||
props: getDefaultHomeMenuProps(),
|
||||
});
|
||||
patchWithCleanup(getService("home_menu"), {
|
||||
async toggle(show) {
|
||||
expect.step(`toggle ${show}`);
|
||||
},
|
||||
});
|
||||
await keyDown("escape");
|
||||
expect.verifySteps(["toggle false"]);
|
||||
});
|
||||
|
||||
test("Click on an app", async () => {
|
||||
await mountWithCleanup(HomeMenu, {
|
||||
props: getDefaultHomeMenuProps(),
|
||||
});
|
||||
patchWithCleanup(getService("menu"), {
|
||||
async selectMenu(menu) {
|
||||
expect.step(`selectMenu ${menu.id}`);
|
||||
},
|
||||
});
|
||||
await click(".o_menuitem:eq(0)");
|
||||
await animationFrame();
|
||||
expect.verifySteps(["selectMenu 1"]);
|
||||
});
|
||||
|
||||
test("Display Expiration Panel (no module installed)", async () => {
|
||||
mockDate("2019-10-09T00:00:00");
|
||||
|
||||
patchWithCleanup(session, {
|
||||
expiration_date: "2019-11-01 12:00:00",
|
||||
expiration_reason: "",
|
||||
isMailInstalled: false,
|
||||
warning: "admin",
|
||||
});
|
||||
|
||||
await mountWithCleanup(HomeMenu, {
|
||||
props: getDefaultHomeMenuProps(),
|
||||
});
|
||||
|
||||
expect(".database_expiration_panel").toHaveCount(1);
|
||||
expect(".database_expiration_panel .oe_instance_register").toHaveText(
|
||||
"You will be able to register your database once you have installed your first app.",
|
||||
{ message: "There should be an expiration panel displayed" }
|
||||
);
|
||||
|
||||
// Close the expiration panel
|
||||
await click(".database_expiration_panel .oe_instance_hide_panel");
|
||||
await animationFrame();
|
||||
expect(".database_expiration_panel").toHaveCount(0);
|
||||
});
|
||||
|
||||
test("Navigation (only apps, only one line)", async () => {
|
||||
expect.assertions(8);
|
||||
|
||||
const homeMenuProps = {
|
||||
apps: new Array(3).fill().map((x, i) => {
|
||||
return {
|
||||
actionID: 120 + i,
|
||||
href: "/odoo/act" + (120 + i),
|
||||
appID: i + 1,
|
||||
id: i + 1,
|
||||
label: `0${i}`,
|
||||
parents: "",
|
||||
webIcon: false,
|
||||
xmlid: `app.${i}`,
|
||||
};
|
||||
}),
|
||||
reorderApps: (order) => reorderApps(homeMenuProps.apps, order),
|
||||
};
|
||||
await mountWithCleanup(HomeMenu, {
|
||||
props: homeMenuProps,
|
||||
});
|
||||
|
||||
const path = [
|
||||
{ number: 0, key: "ArrowDown", index: 0 },
|
||||
{ number: 1, key: "ArrowRight", index: 1 },
|
||||
{ number: 2, key: "Tab", index: 2 },
|
||||
{ number: 3, key: "ArrowRight", index: 0 },
|
||||
{ number: 4, key: "Tab", shiftKey: true, index: 2 },
|
||||
{ number: 5, key: "ArrowLeft", index: 1 },
|
||||
{ number: 6, key: "ArrowDown", index: 1 },
|
||||
{ number: 7, key: "ArrowUp", index: 1 },
|
||||
];
|
||||
|
||||
await walkOn(path);
|
||||
});
|
||||
|
||||
test("Navigation (only apps, two lines, one incomplete)", async () => {
|
||||
expect.assertions(19);
|
||||
|
||||
const homeMenuProps = {
|
||||
apps: new Array(8).fill().map((x, i) => {
|
||||
return {
|
||||
actionID: 121,
|
||||
href: "/odoo/action-121",
|
||||
appID: i + 1,
|
||||
id: i + 1,
|
||||
label: `0${i}`,
|
||||
parents: "",
|
||||
webIcon: false,
|
||||
xmlid: `app.${i}`,
|
||||
};
|
||||
}),
|
||||
reorderApps: (order) => reorderApps(homeMenuProps.apps, order),
|
||||
};
|
||||
await mountWithCleanup(HomeMenu, {
|
||||
props: homeMenuProps,
|
||||
});
|
||||
|
||||
const path = [
|
||||
{ number: 1, key: "ArrowRight", index: 0 },
|
||||
{ number: 2, key: "ArrowUp", index: 6 },
|
||||
{ number: 3, key: "ArrowUp", index: 0 },
|
||||
{ number: 4, key: "ArrowDown", index: 6 },
|
||||
{ number: 5, key: "ArrowDown", index: 0 },
|
||||
{ number: 6, key: "ArrowRight", index: 1 },
|
||||
{ number: 7, key: "ArrowRight", index: 2 },
|
||||
{ number: 8, key: "ArrowUp", index: 7 },
|
||||
{ number: 9, key: "ArrowUp", index: 1 },
|
||||
{ number: 10, key: "ArrowRight", index: 2 },
|
||||
{ number: 11, key: "ArrowDown", index: 7 },
|
||||
{ number: 12, key: "ArrowDown", index: 1 },
|
||||
{ number: 13, key: "ArrowUp", index: 7 },
|
||||
{ number: 14, key: "ArrowRight", index: 6 },
|
||||
{ number: 15, key: "ArrowLeft", index: 7 },
|
||||
{ number: 16, key: "ArrowUp", index: 1 },
|
||||
{ number: 17, key: "ArrowLeft", index: 0 },
|
||||
{ number: 18, key: "ArrowLeft", index: 5 },
|
||||
{ number: 19, key: "ArrowRight", index: 0 },
|
||||
];
|
||||
|
||||
await walkOn(path);
|
||||
});
|
||||
|
||||
test("Navigation and open an app in the home menu", async () => {
|
||||
expect.assertions(6);
|
||||
|
||||
await mountWithCleanup(HomeMenu, {
|
||||
props: getDefaultHomeMenuProps(),
|
||||
});
|
||||
patchWithCleanup(getService("menu"), {
|
||||
async selectMenu(menu) {
|
||||
expect.step(`selectMenu ${menu.id}`);
|
||||
},
|
||||
});
|
||||
// No app selected so nothing to open
|
||||
await keyDown("enter");
|
||||
expect.verifySteps([]);
|
||||
|
||||
const path = [
|
||||
{ number: 0, key: "ArrowDown", index: 0 },
|
||||
{ number: 1, key: "ArrowRight", index: 1 },
|
||||
{ number: 2, key: "Tab", index: 2 },
|
||||
{ number: 3, key: "shift+Tab", index: 1 },
|
||||
];
|
||||
|
||||
await walkOn(path);
|
||||
|
||||
// open first app (Calendar)
|
||||
await keyDown("enter");
|
||||
|
||||
expect.verifySteps(["selectMenu 2"]);
|
||||
});
|
||||
|
||||
test("Reorder apps in home menu using drag and drop", async () => {
|
||||
const homeMenuProps = {
|
||||
apps: reactive(
|
||||
new Array(8).fill().map((x, i) => {
|
||||
return {
|
||||
actionID: 121,
|
||||
href: "/odoo/action-121",
|
||||
appID: i + 1,
|
||||
id: i + 1,
|
||||
label: `0${i}`,
|
||||
parents: "",
|
||||
webIcon: false,
|
||||
xmlid: `app.${i}`,
|
||||
};
|
||||
})
|
||||
),
|
||||
reorderApps: (order) => reorderApps(homeMenuProps.apps, order),
|
||||
};
|
||||
onRpc("set_res_users_settings", () => {
|
||||
expect.step(`set_res_users_settings`);
|
||||
return {
|
||||
id: 1,
|
||||
homemenu_config: '["app.1","app.2","app.3","app.0","app.4","app.5","app.6","app.7"]',
|
||||
};
|
||||
});
|
||||
await mountWithCleanup(HomeMenu, {
|
||||
props: homeMenuProps,
|
||||
});
|
||||
|
||||
const { moveTo, drop } = await drag(".o_draggable:first-child");
|
||||
await advanceTime(250);
|
||||
expect(".o_draggable:first-child a").not.toHaveClass("o_dragged_app");
|
||||
await advanceTime(250);
|
||||
expect(".o_draggable:first-child a").toHaveClass("o_dragged_app");
|
||||
await moveTo(".o_draggable:first-child", {
|
||||
position: {
|
||||
x: 70,
|
||||
y: 35,
|
||||
},
|
||||
relative: true,
|
||||
});
|
||||
await drop(".o_draggable:not(.o_dragged):eq(3)");
|
||||
await animationFrame();
|
||||
expect.verifySteps(["set_res_users_settings"]);
|
||||
expect(".o_app:eq(0)").toHaveAttribute("data-menu-xmlid", "app.1", {
|
||||
message: "first displayed app has app.1 xmlid",
|
||||
});
|
||||
expect(".o_app:eq(3)").toHaveAttribute("data-menu-xmlid", "app.0", {
|
||||
message: "app 0 is now at 4th position",
|
||||
});
|
||||
});
|
||||
|
||||
test("The HomeMenu input takes the focus when you press a key only if no other element is the activeElement", async () => {
|
||||
await mountWithCleanup(HomeMenu, {
|
||||
props: getDefaultHomeMenuProps(),
|
||||
});
|
||||
expect(".o_search_hidden").toBeFocused();
|
||||
|
||||
const activeElement = document.createElement("div");
|
||||
getService("ui").activateElement(activeElement);
|
||||
// remove the focus from the input
|
||||
const otherInput = document.createElement("input");
|
||||
queryFirst(".o_home_menu").appendChild(otherInput);
|
||||
await pointerDown(otherInput);
|
||||
await pointerDown(document.body);
|
||||
expect(".o_search_hidden").not.toBeFocused();
|
||||
|
||||
await keyDown("a");
|
||||
await animationFrame();
|
||||
expect(".o_search_hidden").not.toBeFocused();
|
||||
|
||||
getService("ui").deactivateElement(activeElement);
|
||||
await keyDown("a");
|
||||
await animationFrame();
|
||||
expect(".o_search_hidden").toBeFocused();
|
||||
});
|
||||
|
||||
test("The HomeMenu input does not take the focus if it is already on another input", async () => {
|
||||
await mountWithCleanup(HomeMenu, {
|
||||
props: getDefaultHomeMenuProps(),
|
||||
});
|
||||
expect(".o_search_hidden").toBeFocused();
|
||||
|
||||
const otherInput = document.createElement("input");
|
||||
queryFirst(".o_home_menu").appendChild(otherInput);
|
||||
await pointerDown(otherInput);
|
||||
await keyDown("a");
|
||||
await animationFrame();
|
||||
expect(".o_search_hidden").not.toBeFocused();
|
||||
|
||||
otherInput.remove();
|
||||
await keyDown("a");
|
||||
await animationFrame();
|
||||
expect(".o_search_hidden").toBeFocused();
|
||||
});
|
||||
|
||||
test("The HomeMenu input does not take the focus if it is already on a textarea", async () => {
|
||||
await mountWithCleanup(HomeMenu, {
|
||||
props: getDefaultHomeMenuProps(),
|
||||
});
|
||||
expect(".o_search_hidden").toBeFocused();
|
||||
|
||||
const textarea = document.createElement("textarea");
|
||||
queryFirst(".o_home_menu").appendChild(textarea);
|
||||
await pointerDown(textarea);
|
||||
await keyDown("a");
|
||||
await animationFrame();
|
||||
expect(".o_search_hidden").not.toBeFocused();
|
||||
|
||||
textarea.remove();
|
||||
await keyDown("a");
|
||||
await animationFrame();
|
||||
expect(".o_search_hidden").toBeFocused();
|
||||
});
|
||||
|
||||
test("home search input shouldn't be focused on touch devices", async () => {
|
||||
mockTouch(true);
|
||||
await mountWithCleanup(HomeMenu, {
|
||||
props: getDefaultHomeMenuProps(),
|
||||
});
|
||||
expect(".o_search_hidden").not.toBeFocused({
|
||||
message: "home menu search input shouldn't have the focus",
|
||||
});
|
||||
});
|
||||
|
|
@ -1,798 +0,0 @@
|
|||
import { beforeEach, describe, expect, test } from "@odoo/hoot";
|
||||
import { click, keyDown, queryAll, queryFirst } from "@odoo/hoot-dom";
|
||||
import { animationFrame, Deferred, mockMatchMedia } from "@odoo/hoot-mock";
|
||||
import { Component, onMounted, xml } from "@odoo/owl";
|
||||
import {
|
||||
clearRegistry,
|
||||
contains,
|
||||
defineActions,
|
||||
defineMenus,
|
||||
defineModels,
|
||||
fields,
|
||||
getMockEnv,
|
||||
getService,
|
||||
models,
|
||||
mountWithCleanup,
|
||||
onRpc,
|
||||
patchWithCleanup,
|
||||
serverState,
|
||||
stepAllNetworkCalls,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { router } from "@web/core/browser/router";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { config as transitionConfig } from "@web/core/transition";
|
||||
import { user } from "@web/core/user";
|
||||
import { redirect } from "@web/core/utils/urls";
|
||||
import { UserMenu } from "@web/webclient/user_menu/user_menu";
|
||||
import { shareUrlMenuItem } from "@odex30_web/webclient/share_url/share_url";
|
||||
import { WebClientOdex } from "@odex30_web/webclient/webclient";
|
||||
|
||||
const actionRegistry = registry.category("actions");
|
||||
|
||||
/**
|
||||
* @param {{ env: import("@web/env").OdooEnv }} [options]
|
||||
*/
|
||||
async function mountWebClientOdex(options) {
|
||||
await mountWithCleanup(WebClientOdex, options);
|
||||
// Wait for visual changes caused by a potential loadState
|
||||
await animationFrame();
|
||||
// wait for BlankComponent
|
||||
await animationFrame();
|
||||
// wait for the regular rendering
|
||||
await animationFrame();
|
||||
}
|
||||
|
||||
async function goToHomeMenu() {
|
||||
await click(".o_menu_toggle");
|
||||
await animationFrame();
|
||||
|
||||
if (getMockEnv().isSmall) {
|
||||
await click(queryFirst(".o_sidebar_topbar a.btn-primary", { root: document.body }));
|
||||
await animationFrame();
|
||||
}
|
||||
}
|
||||
|
||||
defineActions([
|
||||
{
|
||||
id: 1,
|
||||
xml_id: "action_1",
|
||||
name: "Partners Action 1",
|
||||
res_model: "partner",
|
||||
views: [[false, "kanban"]],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
xml_id: "action_2",
|
||||
type: "ir.actions.server",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
xml_id: "action_3",
|
||||
name: "Partners",
|
||||
res_model: "partner",
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "kanban"],
|
||||
[false, "form"],
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
xml_id: "action_4",
|
||||
name: "Partners Action 4",
|
||||
res_model: "partner",
|
||||
views: [
|
||||
[false, "kanban"],
|
||||
[false, "list"],
|
||||
[false, "form"],
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
xml_id: "action_5",
|
||||
name: "Create a Partner",
|
||||
res_model: "partner",
|
||||
target: "new",
|
||||
views: [[false, "form"]],
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
xml_id: "action_6",
|
||||
name: "Partner",
|
||||
res_id: 2,
|
||||
res_model: "partner",
|
||||
target: "inline",
|
||||
views: [[false, "form"]],
|
||||
},
|
||||
{
|
||||
id: 1001,
|
||||
tag: "__test__client__action__",
|
||||
target: "main",
|
||||
type: "ir.actions.client",
|
||||
params: { description: "Id 1" },
|
||||
},
|
||||
{
|
||||
id: 1002,
|
||||
tag: "__test__client__action__",
|
||||
target: "main",
|
||||
type: "ir.actions.client",
|
||||
params: { description: "Id 2" },
|
||||
},
|
||||
]);
|
||||
|
||||
defineMenus([
|
||||
{ id: 0 }, // prevents auto-loading the first action
|
||||
{ id: 1, name: "App1", appID: 1, actionID: 1001, xmlid: "menu_1" },
|
||||
{ id: 2, name: "App2", appID: 2, actionID: 1002, xmlid: "menu_2" },
|
||||
]);
|
||||
class Partner extends models.Model {
|
||||
name = fields.Char();
|
||||
foo = fields.Char();
|
||||
parent_id = fields.Many2one({ relation: "partner" });
|
||||
child_ids = fields.One2many({ relation: "partner", relation_field: "parent_id" });
|
||||
|
||||
_records = [
|
||||
{ id: 1, name: "First record", foo: "yop", parent_id: 3 },
|
||||
{ id: 2, name: "Second record", foo: "blip", parent_id: 3 },
|
||||
{ id: 3, name: "Third record", foo: "gnap", parent_id: 1 },
|
||||
{ id: 4, name: "Fourth record", foo: "plop", parent_id: 1 },
|
||||
{ id: 5, name: "Fifth record", foo: "zoup", parent_id: 1 },
|
||||
];
|
||||
_views = {
|
||||
kanban: `
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="card">
|
||||
<field name="foo"/>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
`,
|
||||
list: `<list><field name="foo"/></list>`,
|
||||
form: `
|
||||
<form>
|
||||
<header>
|
||||
<button name="object" string="Call method" type="object"/>
|
||||
<button name="4" string="Execute action" type="action"/>
|
||||
</header>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="foo"/>
|
||||
</group>
|
||||
</form>
|
||||
`,
|
||||
search: `<search><field name="foo" string="Foo"/></search>`,
|
||||
};
|
||||
}
|
||||
defineModels([Partner]);
|
||||
class TestClientAction extends Component {
|
||||
static template = xml`
|
||||
<div class="test_client_action">
|
||||
ClientAction_<t t-esc="props.action.params?.description"/>
|
||||
</div>
|
||||
`;
|
||||
static props = ["*"];
|
||||
|
||||
setup() {
|
||||
onMounted(() => {
|
||||
this.env.config.setDisplayName(`Client action ${this.props.action.id}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onRpc("has_group", () => true);
|
||||
|
||||
beforeEach(() => {
|
||||
actionRegistry.add("__test__client__action__", TestClientAction);
|
||||
patchWithCleanup(transitionConfig, { disabled: true });
|
||||
});
|
||||
|
||||
describe("basic flow with home menu", () => {
|
||||
stepAllNetworkCalls();
|
||||
onRpc("partner", "get_formview_action", () => ({
|
||||
type: "ir.actions.act_window",
|
||||
res_model: "partner",
|
||||
view_type: "form",
|
||||
view_mode: "form",
|
||||
views: [[false, "form"]],
|
||||
target: "current",
|
||||
res_id: 2,
|
||||
}));
|
||||
defineMenus(
|
||||
[
|
||||
{
|
||||
id: 1,
|
||||
name: "App1",
|
||||
appID: 1,
|
||||
actionID: 4,
|
||||
xmlid: "menu_1",
|
||||
},
|
||||
],
|
||||
{ mode: "replace" }
|
||||
);
|
||||
test("1 -- start up", async () => {
|
||||
await mountWebClientOdex();
|
||||
expect.verifySteps(["/web/webclient/translations", "/web/webclient/load_menus"]);
|
||||
expect(document.body).toHaveClass("o_home_menu_background");
|
||||
expect(".o_home_menu").toHaveCount(1);
|
||||
expect(".o_menu_toggle").not.toBeVisible();
|
||||
expect(".o_app.o_menuitem").toHaveCount(1);
|
||||
});
|
||||
|
||||
test("2 -- navbar updates on displaying an action", async () => {
|
||||
await mountWebClientOdex();
|
||||
expect.verifySteps(["/web/webclient/translations", "/web/webclient/load_menus"]);
|
||||
await contains(".o_app.o_menuitem").click();
|
||||
await animationFrame();
|
||||
expect.verifySteps(["/web/action/load", "get_views", "web_search_read"]);
|
||||
expect(document.body).not.toHaveClass("o_home_menu_background");
|
||||
expect(".o_home_menu").toHaveCount(0);
|
||||
expect(".o_kanban_view").toHaveCount(1);
|
||||
expect(".o_menu_toggle").toBeVisible();
|
||||
expect(".o_menu_toggle").not.toHaveClass("o_menu_toggle_back");
|
||||
});
|
||||
|
||||
test("3 -- push another action in the breadcrumb", async () => {
|
||||
await mountWebClientOdex();
|
||||
expect.verifySteps(["/web/webclient/translations", "/web/webclient/load_menus"]);
|
||||
await contains(".o_app.o_menuitem").click();
|
||||
await animationFrame();
|
||||
expect.verifySteps(["/web/action/load", "get_views", "web_search_read"]);
|
||||
expect(".o_kanban_view").toHaveCount(1);
|
||||
await contains(".o_kanban_record").click();
|
||||
await animationFrame(); // there is another tick to update navbar and destroy HomeMenu
|
||||
expect.verifySteps(["web_read"]);
|
||||
expect(".o_menu_toggle").toBeVisible();
|
||||
expect(".o_form_view").toHaveCount(1);
|
||||
expect(".o_breadcrumb .active").toHaveText("First record");
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("4 -- push a third action in the breadcrumb", async () => {
|
||||
Partner._views["form"] = `
|
||||
<form>
|
||||
<field name="display_name"/>
|
||||
<field name="parent_id" open_target="current"/>
|
||||
</form>
|
||||
`;
|
||||
await mountWebClientOdex();
|
||||
expect.verifySteps(["/web/webclient/translations", "/web/webclient/load_menus"]);
|
||||
await contains(".o_app.o_menuitem").click();
|
||||
await animationFrame();
|
||||
expect.verifySteps(["/web/action/load", "get_views", "web_search_read"]);
|
||||
expect(".o_kanban_view").toHaveCount(1);
|
||||
await contains(".o_kanban_record").click();
|
||||
expect.verifySteps(["web_read"]);
|
||||
await contains('.o_field_widget[name="parent_id"] .o_external_button', {
|
||||
visible: false,
|
||||
}).click();
|
||||
expect.verifySteps(["get_formview_action", "get_views", "web_read"]);
|
||||
expect(".o_form_view").toHaveCount(1);
|
||||
expect(".o_breadcrumb .active").toHaveText("Second record");
|
||||
// The third one is the active one
|
||||
expect(".breadcrumb-item").toHaveCount(2);
|
||||
});
|
||||
|
||||
test("5 -- switch to HomeMenu from an action with 2 breadcrumbs", async () => {
|
||||
Partner._views["form"] = `
|
||||
<form>
|
||||
<field name="display_name"/>
|
||||
<field name="parent_id" open_target="current"/>
|
||||
</form>
|
||||
`;
|
||||
await mountWebClientOdex();
|
||||
expect.verifySteps(["/web/webclient/translations", "/web/webclient/load_menus"]);
|
||||
await contains(".o_app.o_menuitem").click();
|
||||
await animationFrame();
|
||||
expect.verifySteps(["/web/action/load", "get_views", "web_search_read"]);
|
||||
expect(".o_kanban_view").toHaveCount(1);
|
||||
await contains(".o_kanban_record").click();
|
||||
expect.verifySteps(["web_read"]);
|
||||
await contains('.o_field_widget[name="parent_id"] .o_external_button', {
|
||||
visible: false,
|
||||
}).click();
|
||||
expect.verifySteps(["get_formview_action", "get_views", "web_read"]);
|
||||
await goToHomeMenu();
|
||||
expect.verifySteps([]);
|
||||
expect(".o_menu_toggle").toHaveClass("o_menu_toggle_back");
|
||||
expect(".o_home_menu").toHaveCount(1);
|
||||
expect(".o_form_view").not.toHaveCount();
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("6 -- back to underlying action with many breadcrumbs", async () => {
|
||||
Partner._views["form"] = `
|
||||
<form>
|
||||
<field name="display_name"/>
|
||||
<field name="parent_id" open_target="current"/>
|
||||
</form>
|
||||
`;
|
||||
await mountWebClientOdex();
|
||||
expect.verifySteps(["/web/webclient/translations", "/web/webclient/load_menus"]);
|
||||
await contains(".o_app.o_menuitem").click();
|
||||
await animationFrame();
|
||||
expect.verifySteps(["/web/action/load", "get_views", "web_search_read"]);
|
||||
expect(".o_kanban_view").toHaveCount(1);
|
||||
await contains(".o_kanban_record").click();
|
||||
expect.verifySteps(["web_read"]);
|
||||
await contains('.o_field_widget[name="parent_id"] .o_external_button', {
|
||||
visible: false,
|
||||
}).click();
|
||||
expect.verifySteps(["get_formview_action", "get_views", "web_read"]);
|
||||
await contains(".o_menu_toggle").click();
|
||||
|
||||
// can't click again too soon because of the mutex in home_menu
|
||||
// service (waiting for the url to be updated)
|
||||
await animationFrame();
|
||||
|
||||
await contains(".o_menu_toggle").click();
|
||||
|
||||
expect.verifySteps(["web_read"]);
|
||||
expect(".o_home_menu").toHaveCount(0);
|
||||
expect(".o_form_view").toHaveCount(1);
|
||||
expect(".o_menu_toggle").not.toHaveClass("o_menu_toggle_back");
|
||||
expect(".o_breadcrumb .active").toHaveText("Second record");
|
||||
// Third breadcrumb is the active one
|
||||
expect(".breadcrumb-item").toHaveCount(2);
|
||||
});
|
||||
});
|
||||
|
||||
test("restore the newly created record in form view", async () => {
|
||||
defineActions(
|
||||
[
|
||||
{
|
||||
id: 6,
|
||||
xml_id: "action_6",
|
||||
name: "Partner",
|
||||
res_model: "partner",
|
||||
views: [[false, "form"]],
|
||||
},
|
||||
],
|
||||
{ mode: "replace" }
|
||||
);
|
||||
await mountWebClientOdex();
|
||||
|
||||
await getService("action").doAction(6);
|
||||
expect(".o_form_view").toHaveCount(1);
|
||||
expect(".o_form_view .o_form_editable").toHaveCount(1);
|
||||
await contains(".o_field_widget[name=name] input").edit("red right hand");
|
||||
await contains(".o_form_button_save").click();
|
||||
expect(".o_breadcrumb .active").toHaveText("red right hand");
|
||||
await goToHomeMenu();
|
||||
expect(".o_form_view").not.toHaveCount();
|
||||
|
||||
// can't click again too soon because of the mutex in home_menu
|
||||
// service (waiting for the url to be updated)
|
||||
await animationFrame();
|
||||
|
||||
await contains(".o_menu_toggle").click();
|
||||
expect(".o_form_view").toHaveCount(1);
|
||||
expect(".o_form_view .o_form_saved").toHaveCount(1);
|
||||
expect(".o_breadcrumb .active").toHaveText("red right hand");
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("fast clicking on restore (implementation detail)", async () => {
|
||||
expect.assertions(8);
|
||||
|
||||
let doVeryFastClick = false;
|
||||
|
||||
class DelayedClientAction extends Component {
|
||||
static template = xml`<div class='delayed_client_action'>
|
||||
<button t-on-click="resolve">RESOLVE</button>
|
||||
</div>`;
|
||||
static props = ["*"];
|
||||
setup() {
|
||||
onMounted(() => {
|
||||
if (doVeryFastClick) {
|
||||
doVeryFastClick = false;
|
||||
click(".o_menu_toggle"); // go to home menu
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("actions").add("DelayedClientAction", DelayedClientAction);
|
||||
await mountWebClientOdex();
|
||||
await getService("action").doAction("DelayedClientAction");
|
||||
await animationFrame();
|
||||
await contains(".o_menu_toggle").click(); // go to home menu
|
||||
expect(".o_home_menu").toBeVisible();
|
||||
expect(".delayed_client_action").not.toHaveCount();
|
||||
|
||||
doVeryFastClick = true;
|
||||
await contains(".o_menu_toggle").click(); // back
|
||||
expect(".o_home_menu").toHaveCount(0);
|
||||
expect(".delayed_client_action").toHaveCount(1);
|
||||
await animationFrame(); // waiting for DelayedClientAction
|
||||
expect(".o_home_menu").toBeVisible();
|
||||
expect(".delayed_client_action").not.toHaveCount();
|
||||
|
||||
await contains(".o_menu_toggle").click(); // back
|
||||
await animationFrame();
|
||||
expect(".o_home_menu").toHaveCount(0);
|
||||
expect(".delayed_client_action").toHaveCount(1);
|
||||
});
|
||||
|
||||
test("clear unCommittedChanges when toggling home menu", async () => {
|
||||
expect.assertions(6);
|
||||
// Edit a form view, don't save, toggle home menu
|
||||
// the autosave feature of the Form view is activated
|
||||
// and relied upon by this test
|
||||
|
||||
onRpc("web_save", ({ args, model }) => {
|
||||
expect(model).toBe("partner");
|
||||
expect(args[1]).toEqual({
|
||||
name: "red right hand",
|
||||
foo: false,
|
||||
});
|
||||
});
|
||||
|
||||
await mountWebClientOdex();
|
||||
await getService("action").doAction(3, { viewType: "form" });
|
||||
expect(".o_form_view .o_form_editable").toHaveCount(1);
|
||||
await contains(".o_field_widget[name=name] input").edit("red right hand");
|
||||
|
||||
await goToHomeMenu();
|
||||
expect(".o_form_view").toHaveCount(0);
|
||||
expect(".modal").toHaveCount(0);
|
||||
expect(".o_home_menu").toHaveCount(1);
|
||||
});
|
||||
|
||||
test("can have HomeMenu and dialog action", async () => {
|
||||
await mountWebClientOdex();
|
||||
expect(".o_home_menu").toHaveCount(1);
|
||||
expect(".modal .o_form_view").toHaveCount(0);
|
||||
await getService("action").doAction(5);
|
||||
expect(".modal .o_form_view").toHaveCount(1);
|
||||
expect(".modal .o_form_view").toBeVisible();
|
||||
expect(".o_home_menu").toHaveCount(1);
|
||||
});
|
||||
|
||||
test("supports attachments of apps deleted", async () => {
|
||||
// When doing a pg_restore without the filestore
|
||||
// LPE fixme: may not be necessary anymore since menus are not HomeMenu props anymore
|
||||
defineMenus([
|
||||
{
|
||||
id: 1,
|
||||
appID: 1,
|
||||
actionID: 1,
|
||||
xmlid: "",
|
||||
name: "Partners",
|
||||
webIconData: "",
|
||||
webIcon: "bloop,bloop",
|
||||
},
|
||||
]);
|
||||
serverState.debug = "1";
|
||||
await mountWebClientOdex();
|
||||
expect(".o_home_menu").toHaveCount(1);
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("debug manager resets to global items when home menu is displayed", async () => {
|
||||
const debugRegistry = registry.category("debug");
|
||||
debugRegistry.category("default").add("item_1", () => ({
|
||||
type: "item",
|
||||
description: "globalItem",
|
||||
callback: () => {},
|
||||
sequence: 10,
|
||||
}));
|
||||
onRpc("has_access", () => true);
|
||||
serverState.debug = "1";
|
||||
await mountWebClientOdex();
|
||||
await contains(".o_debug_manager .dropdown-toggle").click();
|
||||
expect(".dropdown-item:contains('globalItem')").toHaveCount(1);
|
||||
expect(".dropdown-item:contains('View: Kanban')").toHaveCount(0);
|
||||
|
||||
await contains(".o_debug_manager .dropdown-toggle").click();
|
||||
await getService("action").doAction(1);
|
||||
await contains(".o_debug_manager .dropdown-toggle").click();
|
||||
expect(".dropdown-item:contains('globalItem')").toHaveCount(1);
|
||||
expect(".dropdown-item:contains('View: Kanban')").toHaveCount(1);
|
||||
|
||||
await contains(".o_menu_toggle").click();
|
||||
await contains(".o_debug_manager .dropdown-toggle").click();
|
||||
expect(".dropdown-item:contains('globalItem')").toHaveCount(1);
|
||||
expect(".dropdown-item:contains('View: Kanban')").toHaveCount(0);
|
||||
|
||||
await contains(".o_debug_manager .dropdown-toggle").click();
|
||||
await getService("action").doAction(3);
|
||||
await contains(".o_debug_manager .dropdown-toggle").click();
|
||||
expect(".dropdown-item:contains('globalItem')").toHaveCount(1);
|
||||
expect(".dropdown-item:contains('View: List')").toHaveCount(1);
|
||||
expect(".dropdown-item:contains('View: Kanban')").toHaveCount(0);
|
||||
});
|
||||
|
||||
test("url state is well handled when going in and out of the HomeMenu", async () => {
|
||||
patchWithCleanup(browser.location, {
|
||||
origin: "http://example.com",
|
||||
});
|
||||
redirect("/odoo");
|
||||
await mountWebClientOdex();
|
||||
expect(router.current).toEqual({
|
||||
action: "menu",
|
||||
actionStack: [
|
||||
{
|
||||
action: "menu",
|
||||
displayName: "Home",
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(browser.history.length).toBe(1);
|
||||
|
||||
await contains(".o_apps > .o_draggable:eq(1) > .o_app").click();
|
||||
await animationFrame();
|
||||
expect(router.current).toEqual({
|
||||
action: 1002,
|
||||
actionStack: [
|
||||
{
|
||||
action: 1002,
|
||||
displayName: "Client action 1002",
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(browser.history.length).toBe(2);
|
||||
expect(browser.location.href).toBe("http://example.com/odoo/action-1002");
|
||||
|
||||
await goToHomeMenu();
|
||||
await animationFrame();
|
||||
expect(router.current).toEqual(
|
||||
{
|
||||
action: "menu",
|
||||
actionStack: [
|
||||
{
|
||||
action: 1002,
|
||||
displayName: "Client action 1002",
|
||||
},
|
||||
{
|
||||
action: "menu",
|
||||
displayName: "Home",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
message:
|
||||
"the actionStack is required to be able to restore the menu toggle back button and the underlying breadcrumbs",
|
||||
}
|
||||
);
|
||||
expect(browser.history.length).toBe(3);
|
||||
expect(browser.location.href).toBe("http://example.com/odoo", {
|
||||
message:
|
||||
"despite the actionStack being in the router state, the url shouldn't have any path",
|
||||
});
|
||||
|
||||
await contains(".o_apps > .o_draggable:eq(0) > .o_app").click();
|
||||
await animationFrame();
|
||||
expect(router.current).toEqual(
|
||||
{
|
||||
action: 1001,
|
||||
actionStack: [
|
||||
{
|
||||
action: 1001,
|
||||
displayName: "Client action 1001",
|
||||
},
|
||||
],
|
||||
},
|
||||
{ message: "clicking another app creates a new action stack (ie empties the breadcrumb)" }
|
||||
);
|
||||
expect(browser.history.length).toBe(4);
|
||||
expect(browser.location.href).toBe("http://example.com/odoo/action-1001");
|
||||
|
||||
browser.history.back();
|
||||
await animationFrame();
|
||||
expect(router.current).toEqual(
|
||||
{
|
||||
action: "menu",
|
||||
actionStack: [
|
||||
{
|
||||
action: 1002,
|
||||
displayName: "Client action 1002",
|
||||
},
|
||||
{
|
||||
action: "menu",
|
||||
displayName: "Home",
|
||||
},
|
||||
],
|
||||
globalState: {},
|
||||
},
|
||||
{ message: "actionStack was restored" }
|
||||
);
|
||||
expect(browser.history.length).toBe(4, {
|
||||
message: "the previous history entry still exists (available with forward button)",
|
||||
});
|
||||
expect(browser.location.href).toBe("http://example.com/odoo");
|
||||
|
||||
await contains(".o_menu_toggle").click();
|
||||
await animationFrame();
|
||||
expect(router.current).toEqual({
|
||||
action: 1002,
|
||||
actionStack: [
|
||||
{
|
||||
action: 1002,
|
||||
displayName: "Client action 1002",
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(browser.history.length).toBe(4);
|
||||
expect(browser.location.href).toBe("http://example.com/odoo/action-1002");
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("underlying action's menu items are invisible when HomeMenu is displayed", async () => {
|
||||
defineMenus([
|
||||
{
|
||||
id: 1,
|
||||
children: [
|
||||
{
|
||||
id: 99,
|
||||
name: "SubMenu",
|
||||
appID: 1,
|
||||
actionID: 1002,
|
||||
xmlid: "",
|
||||
webIconData: undefined,
|
||||
webIcon: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
await mountWebClientOdex();
|
||||
expect("nav .o_menu_sections").toHaveCount(0);
|
||||
expect("nav .o_menu_brand").toHaveCount(0);
|
||||
await contains(".o_app.o_menuitem:nth-child(1)").click();
|
||||
await animationFrame();
|
||||
expect("nav .o_menu_sections").toHaveCount(1);
|
||||
expect("nav .o_menu_brand").toHaveCount(1);
|
||||
expect(".o_menu_sections").toBeVisible();
|
||||
expect(".o_menu_brand").toBeVisible();
|
||||
await contains(".o_menu_toggle").click();
|
||||
expect("nav .o_menu_sections").toHaveCount(1);
|
||||
expect("nav .o_menu_brand").toHaveCount(1);
|
||||
expect(".o_menu_sections").not.toBeVisible();
|
||||
expect(".o_menu_brand").not.toBeVisible();
|
||||
});
|
||||
|
||||
test("go back to home menu using browser back button", async () => {
|
||||
await mountWebClientOdex();
|
||||
expect(".o_home_menu").toHaveCount(1);
|
||||
expect(".o_main_navbar .o_menu_toggle").not.toBeVisible();
|
||||
|
||||
await contains(".o_apps > .o_draggable:nth-child(2) > .o_app").click();
|
||||
expect(".test_client_action").toHaveCount(0);
|
||||
await animationFrame();
|
||||
expect(".test_client_action").toHaveCount(1);
|
||||
expect(".o_home_menu").toHaveCount(0);
|
||||
|
||||
browser.history.back();
|
||||
await animationFrame();
|
||||
await animationFrame();
|
||||
expect(".test_client_action").toHaveCount(0);
|
||||
expect(".o_home_menu").toHaveCount(1);
|
||||
expect(".o_main_navbar .o_menu_toggle").not.toBeVisible();
|
||||
});
|
||||
|
||||
test("initial action crashes", async () => {
|
||||
expect.errors(1);
|
||||
redirect("/odoo/action-__test__client__action__?menu_id=1");
|
||||
const ClientAction = registry.category("actions").get("__test__client__action__");
|
||||
class Override extends ClientAction {
|
||||
setup() {
|
||||
super.setup();
|
||||
expect.step("clientAction setup");
|
||||
throw new Error("my error");
|
||||
}
|
||||
}
|
||||
registry.category("actions").add("__test__client__action__", Override, { force: true });
|
||||
|
||||
await mountWebClientOdex();
|
||||
expect.verifySteps(["clientAction setup"]);
|
||||
expect("nav .o_menu_toggle").toHaveCount(1);
|
||||
expect("nav .o_menu_toggle").toBeVisible();
|
||||
expect(".o_action_manager").toHaveInnerHTML("");
|
||||
expect(router.current).toEqual({
|
||||
action: "__test__client__action__",
|
||||
menu_id: 1,
|
||||
actionStack: [
|
||||
{
|
||||
action: "__test__client__action__",
|
||||
},
|
||||
],
|
||||
});
|
||||
await animationFrame();
|
||||
expect.verifyErrors(["my error"]);
|
||||
});
|
||||
|
||||
test("Apps are reordered at startup based on session's user settings", async () => {
|
||||
// Config is written with apps xmlids order (default is menu_1, menu_2)
|
||||
patchWithCleanup(user, {
|
||||
get settings() {
|
||||
return { id: 1, homemenu_config: '["menu_2","menu_1"]' };
|
||||
},
|
||||
});
|
||||
await mountWebClientOdex();
|
||||
|
||||
const apps = queryAll(".o_app");
|
||||
expect(apps[0]).toHaveAttribute("data-menu-xmlid", "menu_2", {
|
||||
message: "first displayed app has menu_2 xmlid",
|
||||
});
|
||||
expect(apps[1]).toHaveAttribute("data-menu-xmlid", "menu_1", {
|
||||
message: "second displayed app has menu_1 xmlid",
|
||||
});
|
||||
expect(apps[0]).toHaveText("App2", { message: "first displayed app is App2" });
|
||||
expect(apps[1]).toHaveText("App1", { message: "second displayed app is App1" });
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("Share URL item is present in the user menu when running as PWA", async () => {
|
||||
mockMatchMedia({ ["display-mode"]: "standalone" });
|
||||
clearRegistry(registry.category("user_menuitems"));
|
||||
// This service adds a "Dark Mode" item to the user menu items on start
|
||||
registry.category("services").remove("color_scheme");
|
||||
registry.category("user_menuitems").add("share_url", shareUrlMenuItem);
|
||||
|
||||
await mountWithCleanup(UserMenu);
|
||||
await contains(".o_user_menu button").click();
|
||||
|
||||
expect(".o-dropdown--menu .dropdown-item").toHaveCount(1);
|
||||
expect(".o-dropdown--menu .dropdown-item").toHaveText("Share");
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("Share URL item is not present in the user menu when not running as PWA", async () => {
|
||||
mockMatchMedia({ ["display-mode"]: "browser" });
|
||||
clearRegistry(registry.category("user_menuitems"));
|
||||
// This service adds a "Dark Mode" item to the user menu items on start
|
||||
registry.category("services").remove("color_scheme");
|
||||
registry.category("user_menuitems").add("share_url", shareUrlMenuItem);
|
||||
|
||||
await mountWithCleanup(UserMenu);
|
||||
await contains(".o_user_menu button").click();
|
||||
|
||||
expect(".o-dropdown--menu .dropdown-item").not.toHaveCount();
|
||||
});
|
||||
|
||||
test("Navigate to an application from the HomeMenu should generate only one pushState", async () => {
|
||||
patchWithCleanup(history, {
|
||||
pushState(state, title, url) {
|
||||
super.pushState(...arguments);
|
||||
const parsedUrl = new URL(url);
|
||||
expect.step(parsedUrl.pathname + parsedUrl.search);
|
||||
},
|
||||
});
|
||||
await mountWebClientOdex();
|
||||
|
||||
await contains(".o_apps > .o_draggable:nth-child(2) > .o_app").click();
|
||||
await animationFrame();
|
||||
expect(".test_client_action").toHaveCount(1);
|
||||
expect(".test_client_action").toHaveText("ClientAction_Id 2");
|
||||
|
||||
await goToHomeMenu();
|
||||
expect(".o_home_menu").toHaveCount(1);
|
||||
|
||||
await contains(".o_apps > .o_draggable:nth-child(1) > .o_app").click();
|
||||
await animationFrame();
|
||||
expect(".test_client_action").toHaveCount(1);
|
||||
expect(".test_client_action").toHaveText("ClientAction_Id 1");
|
||||
|
||||
await goToHomeMenu();
|
||||
await animationFrame();
|
||||
expect(".o_home_menu").toHaveCount(1);
|
||||
expect.verifySteps(["/odoo", "/odoo/action-1002", "/odoo", "/odoo/action-1001", "/odoo"]);
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("Should not crash when opening an app via palette and immediately entering input in the palette search", async () => {
|
||||
await mountWebClientOdex();
|
||||
|
||||
const def = new Deferred();
|
||||
onRpc("web_search_read", () => def);
|
||||
await keyDown("a");
|
||||
await animationFrame();
|
||||
await keyDown("Enter");
|
||||
await keyDown("a");
|
||||
await animationFrame();
|
||||
def.resolve();
|
||||
await animationFrame();
|
||||
expect(".test_client_action").toHaveCount(1);
|
||||
expect(".test_client_action").toHaveText("ClientAction_Id 1");
|
||||
});
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
|
||||
from . import test_odex
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
import base64
|
||||
|
||||
from odoo.tests.common import HttpCase, tagged
|
||||
|
||||
|
||||
class LoadMenusTests(HttpCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.menu = self.env["ir.ui.menu"].create({
|
||||
"name": "test_menu",
|
||||
"parent_id": False,
|
||||
})
|
||||
|
||||
def search(*args, **kwargs):
|
||||
return self.menu
|
||||
|
||||
self.patch(type(self.env["ir.ui.menu"]), "search", search)
|
||||
self.authenticate("admin", "admin")
|
||||
|
||||
def test_web_icon(self):
|
||||
self.menu.web_icon = False
|
||||
self.menu.web_icon_data = b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+BCQAHBQICJmhD1AAAAABJRU5ErkJggg=="
|
||||
|
||||
menu_loaded = self.url_open("/web/webclient/load_menus/1234")
|
||||
|
||||
expected = {
|
||||
str(self.menu.id): {
|
||||
"actionID": False,
|
||||
"actionModel": False,
|
||||
"actionPath": False,
|
||||
"appID": self.menu.id,
|
||||
"children": [],
|
||||
"id": self.menu.id,
|
||||
"name": "test_menu",
|
||||
"webIcon": False,
|
||||
"webIconData": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+BCQAHBQICJmhD1AAAAABJRU5ErkJggg==",
|
||||
"webIconDataMimetype": "image/png",
|
||||
"xmlid": ""
|
||||
},
|
||||
"root": {
|
||||
"actionID": False,
|
||||
"actionModel": False,
|
||||
"actionPath": False,
|
||||
"appID": False,
|
||||
"children": [
|
||||
self.menu.id
|
||||
],
|
||||
"id": "root",
|
||||
"name": "root",
|
||||
"webIcon": None,
|
||||
"webIconData": None,
|
||||
"webIconDataMimetype": None,
|
||||
"xmlid": "",
|
||||
"backgroundImage": None,
|
||||
}
|
||||
}
|
||||
|
||||
self.assertDictEqual(menu_loaded.json(), expected)
|
||||
|
||||
|
||||
@tagged("-at_install", "post_install")
|
||||
class TestWeb(HttpCase):
|
||||
def test_studio_list_upsell(self):
|
||||
invoice_action = self.env.ref("account.action_move_out_invoice_type", raise_if_not_found=False)
|
||||
if not invoice_action:
|
||||
return
|
||||
self.start_tour("/odoo/action-account.action_move_out_invoice_type", "odex30_web.test_studio_list_upsell", login="admin")
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
import odoo
|
||||
|
||||
odoo.release.version_info = odoo.release.version_info[:5] + ('e',)
|
||||
if '+e' not in odoo.release.version:
|
||||
odoo.release.version = '{0}+e{1}{2}'.format(*odoo.release.version.partition('-'))
|
||||
|
||||
odoo.service.common.RPC_VERSION_1.update(
|
||||
server_version=odoo.release.version,
|
||||
server_version_info=odoo.release.version_info)
|
||||
Loading…
Reference in New Issue