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 {
|
.o_home_menu_background {
|
||||||
--homeMenu-bg-color: #000511;
|
--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;
|
size: cover;
|
||||||
attachment: fixed;
|
attachment: fixed;
|
||||||
color: var(--homeMenu-bg-color, #{$o-gray-200});
|
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