mirror of
https://github.com/bringout/oca-ocb-test.git
synced 2026-04-19 21:42:07 +02:00
19.0 vanilla
This commit is contained in:
parent
38c6088dcc
commit
d9452d2060
243 changed files with 30797 additions and 10815 deletions
|
|
@ -12,37 +12,14 @@ pip install odoo-bringout-oca-ocb-test_base_automation
|
|||
|
||||
## Dependencies
|
||||
|
||||
This addon depends on:
|
||||
- base_automation
|
||||
|
||||
## Manifest Information
|
||||
|
||||
- **Name**: Test - Base Automation
|
||||
- **Version**: 1.0
|
||||
- **Category**: Hidden
|
||||
- **License**: LGPL-3
|
||||
- **Installable**: True
|
||||
|
||||
## Source
|
||||
|
||||
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `test_base_automation`.
|
||||
- Repository: https://github.com/OCA/OCB
|
||||
- Branch: 19.0
|
||||
- Path: addons/test_base_automation
|
||||
|
||||
## License
|
||||
|
||||
This package maintains the original LGPL-3 license from the upstream Odoo project.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Overview: doc/OVERVIEW.md
|
||||
- Architecture: doc/ARCHITECTURE.md
|
||||
- Models: doc/MODELS.md
|
||||
- Controllers: doc/CONTROLLERS.md
|
||||
- Wizards: doc/WIZARDS.md
|
||||
- Reports: doc/REPORTS.md
|
||||
- Security: doc/SECURITY.md
|
||||
- Install: doc/INSTALL.md
|
||||
- Usage: doc/USAGE.md
|
||||
- Configuration: doc/CONFIGURATION.md
|
||||
- Dependencies: doc/DEPENDENCIES.md
|
||||
- Troubleshooting: doc/TROUBLESHOOTING.md
|
||||
- FAQ: doc/FAQ.md
|
||||
This package preserves the original LGPL-3 license.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
[project]
|
||||
name = "odoo-bringout-oca-ocb-test_base_automation"
|
||||
version = "16.0.0"
|
||||
description = "Test - Base Automation - Base Automation Tests: Ensure Flow Robustness"
|
||||
description = "Test - Base Automation -
|
||||
Base Automation Tests: Ensure Flow Robustness
|
||||
"
|
||||
authors = [
|
||||
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
|
||||
]
|
||||
dependencies = [
|
||||
"odoo-bringout-oca-ocb-base_automation>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-base_automation>=19.0.0",
|
||||
"requests>=2.25.1"
|
||||
]
|
||||
readme = "README.md"
|
||||
|
|
@ -16,7 +18,7 @@ classifiers = [
|
|||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Office/Business",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -14,6 +14,12 @@ tests independently to functional aspects of other models.""",
|
|||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_tests': [
|
||||
'test_base_automation/static/tests/**/*',
|
||||
],
|
||||
},
|
||||
'installable': True,
|
||||
'author': 'Odoo S.A.',
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ from dateutil import relativedelta
|
|||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class LeadTest(models.Model):
|
||||
_name = "base.automation.lead.test"
|
||||
class BaseAutomationLeadTest(models.Model):
|
||||
_name = 'base.automation.lead.test'
|
||||
_description = "Automated Rule Test"
|
||||
|
||||
name = fields.Char(string='Subject', required=True)
|
||||
|
|
@ -15,8 +15,9 @@ class LeadTest(models.Model):
|
|||
('pending', 'Pending'), ('done', 'Closed')],
|
||||
string="Status", readonly=True, default='draft')
|
||||
active = fields.Boolean(default=True)
|
||||
tag_ids = fields.Many2many('test_base_automation.tag')
|
||||
partner_id = fields.Many2one('res.partner', string='Partner')
|
||||
date_action_last = fields.Datetime(string='Last Action', readonly=True)
|
||||
date_automation_last = fields.Datetime(string='Last Automation', readonly=True)
|
||||
employee = fields.Boolean(compute='_compute_employee_deadline', store=True)
|
||||
line_ids = fields.One2many('base.automation.line.test', 'lead_id')
|
||||
|
||||
|
|
@ -24,12 +25,26 @@ class LeadTest(models.Model):
|
|||
deadline = fields.Boolean(compute='_compute_employee_deadline', store=True)
|
||||
is_assigned_to_admin = fields.Boolean(string='Assigned to admin user')
|
||||
|
||||
stage_id = fields.Many2one(
|
||||
'test_base_automation.stage', string='Stage',
|
||||
compute='_compute_stage_id', readonly=False, store=True)
|
||||
|
||||
@api.depends('state')
|
||||
def _compute_stage_id(self):
|
||||
Test_Base_AutomationStage = self.env['test_base_automation.stage']
|
||||
for task in self:
|
||||
if not task.stage_id and task.state == 'draft':
|
||||
task.stage_id = (
|
||||
Test_Base_AutomationStage.search([('name', 'ilike', 'new')], limit=1)
|
||||
or Test_Base_AutomationStage.create({'name': 'New'})
|
||||
)
|
||||
|
||||
@api.depends('partner_id.employee', 'priority')
|
||||
def _compute_employee_deadline(self):
|
||||
# this method computes two fields on purpose; don't split it
|
||||
for record in self:
|
||||
record.employee = record.partner_id.employee
|
||||
if not record.priority:
|
||||
if not record.priority or not record.create_date:
|
||||
record.deadline = False
|
||||
else:
|
||||
record.deadline = record.create_date + relativedelta.relativedelta(days=3)
|
||||
|
|
@ -42,8 +57,15 @@ class LeadTest(models.Model):
|
|||
return result
|
||||
|
||||
|
||||
class LineTest(models.Model):
|
||||
_name = "base.automation.line.test"
|
||||
class BaseAutomationLeadThreadTest(models.Model):
|
||||
_name = 'base.automation.lead.thread.test'
|
||||
_description = "Automated Rule Test With Thread"
|
||||
_inherit = ['base.automation.lead.test', 'mail.thread']
|
||||
|
||||
user_id = fields.Many2one("res.users")
|
||||
|
||||
class BaseAutomationLineTest(models.Model):
|
||||
_name = 'base.automation.line.test'
|
||||
_description = "Automated Rule Line Test"
|
||||
|
||||
name = fields.Char()
|
||||
|
|
@ -51,31 +73,37 @@ class LineTest(models.Model):
|
|||
user_id = fields.Many2one('res.users')
|
||||
|
||||
|
||||
class ModelWithAccess(models.Model):
|
||||
_name = "base.automation.link.test"
|
||||
class BaseAutomationLinkTest(models.Model):
|
||||
_name = 'base.automation.link.test'
|
||||
_description = "Automated Rule Link Test"
|
||||
|
||||
name = fields.Char()
|
||||
linked_id = fields.Many2one('base.automation.linked.test', ondelete='cascade')
|
||||
|
||||
|
||||
class ModelWithoutAccess(models.Model):
|
||||
_name = "base.automation.linked.test"
|
||||
class BaseAutomationLinkedTest(models.Model):
|
||||
_name = 'base.automation.linked.test'
|
||||
_description = "Automated Rule Linked Test"
|
||||
|
||||
name = fields.Char()
|
||||
another_field = fields.Char()
|
||||
|
||||
|
||||
class Project(models.Model):
|
||||
_name = _description = 'test_base_automation.project'
|
||||
class Test_Base_AutomationProject(models.Model):
|
||||
_name = 'test_base_automation.project'
|
||||
_description = 'test_base_automation.project'
|
||||
|
||||
name = fields.Char()
|
||||
task_ids = fields.One2many('test_base_automation.task', 'project_id')
|
||||
stage_id = fields.Many2one('test_base_automation.stage')
|
||||
tag_ids = fields.Many2many('test_base_automation.tag')
|
||||
priority = fields.Selection([('0', 'Low'), ('1', 'Normal'), ('2', 'High')], default='1')
|
||||
user_ids = fields.Many2many('res.users')
|
||||
|
||||
|
||||
class Task(models.Model):
|
||||
_name = _description = 'test_base_automation.task'
|
||||
class Test_Base_AutomationTask(models.Model):
|
||||
_name = 'test_base_automation.task'
|
||||
_description = 'test_base_automation.task'
|
||||
|
||||
name = fields.Char()
|
||||
parent_id = fields.Many2one('test_base_automation.task')
|
||||
|
|
@ -83,9 +111,72 @@ class Task(models.Model):
|
|||
'test_base_automation.project',
|
||||
compute='_compute_project_id', recursive=True, store=True, readonly=False,
|
||||
)
|
||||
allocated_hours = fields.Float()
|
||||
trigger_hours = fields.Float("Save time to trigger effective hours")
|
||||
remaining_hours = fields.Float("Time Remaining", compute='_compute_remaining_hours', store=True, readonly=True, help="Number of allocated hours minus the number of hours spent.")
|
||||
effective_hours = fields.Float("Time Spent", compute='_compute_effective_hours', compute_sudo=True, store=True)
|
||||
|
||||
@api.depends('parent_id.project_id')
|
||||
def _compute_project_id(self):
|
||||
for task in self:
|
||||
if not task.project_id:
|
||||
task.project_id = task.parent_id.project_id
|
||||
|
||||
@api.depends('trigger_hours')
|
||||
def _compute_effective_hours(self):
|
||||
for task in self:
|
||||
task.effective_hours = task.trigger_hours
|
||||
|
||||
@api.depends('effective_hours')
|
||||
def _compute_remaining_hours(self):
|
||||
for task in self:
|
||||
if not task.allocated_hours:
|
||||
task.remaining_hours = 0.0
|
||||
else:
|
||||
task.remaining_hours = task.allocated_hours - task.effective_hours
|
||||
|
||||
|
||||
class Test_Base_AutomationStage(models.Model):
|
||||
_name = 'test_base_automation.stage'
|
||||
_description = 'test_base_automation.stage'
|
||||
name = fields.Char()
|
||||
|
||||
|
||||
class Test_Base_AutomationTag(models.Model):
|
||||
_name = 'test_base_automation.tag'
|
||||
_description = 'test_base_automation.tag'
|
||||
name = fields.Char()
|
||||
|
||||
|
||||
# pylint: disable=E0102
|
||||
class BaseAutomationLeadThreadTest(models.Model): # noqa: F811
|
||||
_name = 'base.automation.lead.thread.test'
|
||||
_inherit = ["base.automation.lead.test", "mail.thread"]
|
||||
_description = "Threaded Lead Test"
|
||||
|
||||
|
||||
class BaseAutomationModelWithRecnameChar(models.Model):
|
||||
_name = 'base.automation.model.with.recname.char'
|
||||
_description = "Model with Char as _rec_name"
|
||||
_rec_name = "description"
|
||||
description = fields.Char()
|
||||
user_id = fields.Many2one('res.users', string='Responsible')
|
||||
|
||||
|
||||
class BaseAutomationModelWithRecnameM2o(models.Model):
|
||||
_name = 'base.automation.model.with.recname.m2o'
|
||||
_description = "Model with Many2one as _rec_name and name_create"
|
||||
_rec_name = "user_id"
|
||||
user_id = fields.Many2one("base.automation.model.with.recname.char", string='Responsible')
|
||||
|
||||
@api.model
|
||||
def name_create(self, name):
|
||||
name = name.strip()
|
||||
user = self.env["base.automation.model.with.recname.char"].search([('description', '=ilike', name)], limit=1)
|
||||
if user:
|
||||
user_id = user.id
|
||||
else:
|
||||
user_id, _user_name = self.env["base.automation.model.with.recname.char"].name_create(name)
|
||||
|
||||
record = self.create({'user_id': user_id})
|
||||
return record.id, record.display_name
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_base_automation_lead_test,access_base_automation_lead_test,model_base_automation_lead_test,base.group_system,1,1,1,1
|
||||
access_base_automation_lead_thread_test,access_base_automation_lead_thread_test,model_base_automation_lead_thread_test,base.group_system,1,1,1,1
|
||||
access_base_automation_line_test,access_base_automation_line_test,model_base_automation_line_test,base.group_system,1,1,1,1
|
||||
access_base_automation_link_test,access_base_automation_link_test,model_base_automation_link_test,,1,1,1,1
|
||||
access_base_automation_linked_test,access_base_automation_linked_test,model_base_automation_linked_test,,1,1,1,1
|
||||
access_test_base_automation_project,access_test_base_automation_project,model_test_base_automation_project,,1,1,1,1
|
||||
access_test_base_automation_task,access_test_base_automation_task,model_test_base_automation_task,,1,1,1,1
|
||||
access_base_automation_link_test,access_base_automation_link_test,model_base_automation_link_test,base.group_user,1,1,1,1
|
||||
access_base_automation_linked_test,access_base_automation_linked_test,model_base_automation_linked_test,base.group_user,1,1,1,1
|
||||
access_base_automation_model_with_recname_char,access_base_automation_model_with_recname_char,model_base_automation_model_with_recname_char,base.group_user,1,1,1,1
|
||||
access_base_automation_model_with_recname_m2o,access_base_automation_model_with_recname_m2o,model_base_automation_model_with_recname_m2o,base.group_user,1,1,1,1
|
||||
access_test_base_automation_project,access_test_base_automation_project,model_test_base_automation_project,base.group_user,1,1,1,1
|
||||
access_test_base_automation_task,access_test_base_automation_task,model_test_base_automation_task,base.group_user,1,1,1,1
|
||||
access_test_base_automation_stage,access_test_base_automation_stage,model_test_base_automation_stage,base.group_user,1,1,1,1
|
||||
access_test_base_automation_tag,access_test_base_automation_tag,model_test_base_automation_tag,base.group_user,1,1,1,1
|
||||
|
|
|
|||
|
|
|
@ -0,0 +1,671 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
import { stepUtils } from "@web_tour/tour_utils";
|
||||
|
||||
function assertEqual(actual, expected) {
|
||||
if (actual !== expected) {
|
||||
throw new Error(`Assert failed: expected: ${expected} ; got: ${actual}`);
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("web_tour.tours").add("test_base_automation", {
|
||||
steps: () => [
|
||||
stepUtils.showAppsMenuItem(),
|
||||
{
|
||||
content: "Create new rule",
|
||||
trigger: ".o_control_panel button.o-kanban-button-new",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Enter rule name",
|
||||
trigger: ".o_form_renderer .oe_title .o_input",
|
||||
run: "edit Test rule",
|
||||
},
|
||||
{
|
||||
content: "Select model",
|
||||
trigger: '.o_form_renderer .o_group div[name="model_id"] input',
|
||||
run: "edit res.partner",
|
||||
},
|
||||
{
|
||||
trigger: ".dropdown-menu:contains(Contact)",
|
||||
},
|
||||
{
|
||||
content: "Select model contact",
|
||||
trigger: ".dropdown-menu li a:contains(Contact):not(:has(.fa-spin))",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Open select",
|
||||
trigger: ".o_form_renderer #trigger_0",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_select_menu_item:contains(On create and edit)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Add new action",
|
||||
trigger: '.o_form_renderer div[name="action_server_ids"] button',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Set new action to update the record",
|
||||
trigger:
|
||||
".modal .modal-content .o_form_renderer [name='state'] span[value*='object_write']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Focus on the 'update_path' field",
|
||||
trigger:
|
||||
".modal .modal-content .o_form_renderer [name='update_path'] .o_model_field_selector",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Input field name",
|
||||
trigger: ".o_model_field_selector_popover .o_model_field_selector_popover_search input",
|
||||
run: "edit Job Position",
|
||||
},
|
||||
{
|
||||
content: "Select field",
|
||||
trigger:
|
||||
'.o_model_field_selector_popover .o_model_field_selector_popover_page li[data-name="function"] button',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Open update select",
|
||||
trigger:
|
||||
'.modal .modal-content .o_form_renderer div[name="value"] textarea',
|
||||
run: "edit Test",
|
||||
},
|
||||
{
|
||||
content: "Open update select",
|
||||
trigger: ".modal .modal-content .o_form_button_save",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "body:not(:has(.modal))",
|
||||
},
|
||||
...stepUtils.saveForm(),
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_base_automation_on_tag_added", {
|
||||
steps: () => [
|
||||
stepUtils.showAppsMenuItem(),
|
||||
{
|
||||
trigger: ".o_control_panel button.o-kanban-button-new",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_form_renderer .oe_title .o_input",
|
||||
run: "edit Test rule",
|
||||
},
|
||||
{
|
||||
trigger: '.o_form_renderer .o_group div[name="model_id"] input',
|
||||
run: "edit test_base_automation.project",
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
".dropdown-menu li a:contains(test_base_automation.project):not(:has(.fa-spin))",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Open select",
|
||||
trigger: ".o_form_renderer #trigger_0",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_select_menu_menu",
|
||||
run() {
|
||||
const options = [...this.anchor.querySelectorAll(".o_select_menu_item")].map(
|
||||
(el) => el.textContent
|
||||
);
|
||||
|
||||
assertEqual(
|
||||
JSON.stringify(options),
|
||||
JSON.stringify([
|
||||
"Stage is set to",
|
||||
"User is set",
|
||||
"Tag is added",
|
||||
"Priority is set to",
|
||||
"Based on date field",
|
||||
"After creation",
|
||||
"After last update",
|
||||
"On create",
|
||||
"On create and edit",
|
||||
"On deletion",
|
||||
"On UI change",
|
||||
"On webhook"
|
||||
])
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".o_select_menu_item:contains(Tag is added)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: '.o_form_renderer div[name="trg_field_ref"] input',
|
||||
run: "edit test",
|
||||
},
|
||||
{
|
||||
trigger: ".dropdown-menu li a:contains(test):not(:has(.fa-spin))",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: '.o_form_renderer div[name="action_server_ids"] button',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
".modal .modal-content .o_form_renderer [name='state'] span[value*='object_write']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Focus on the 'update_path' field",
|
||||
trigger:
|
||||
".modal .modal-content .o_form_renderer [name='update_path'] .o_model_field_selector",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Input field name",
|
||||
trigger:
|
||||
".o_model_field_selector_popover .o_model_field_selector_popover_search input",
|
||||
run: "edit Name",
|
||||
},
|
||||
{
|
||||
content: "Select field",
|
||||
trigger:
|
||||
'.o_model_field_selector_popover .o_model_field_selector_popover_page li[data-name="name"] button',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
'.modal .modal-content .o_form_renderer div[name="value"] textarea',
|
||||
run: "edit Test",
|
||||
},
|
||||
{
|
||||
trigger: ".modal .modal-content .o_form_button_save",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "body:not(:has(.modal))",
|
||||
},
|
||||
{
|
||||
trigger: '.o_form_renderer div[name="action_server_ids"] button',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
".modal .modal-content .o_form_renderer [name='state'] span[value*='object_write']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Focus on the 'update_path' field",
|
||||
trigger:
|
||||
".modal .modal-content .o_form_renderer [name='update_path'] .o_model_field_selector",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Input field name",
|
||||
trigger:
|
||||
".o_model_field_selector_popover .o_model_field_selector_popover_search input",
|
||||
run: "edit Priority",
|
||||
},
|
||||
{
|
||||
content: "Select field",
|
||||
trigger:
|
||||
'.o_model_field_selector_popover .o_model_field_selector_popover_page li[data-name="priority"] button',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
'.modal .modal-content .o_form_renderer div[name="selection_value"] input',
|
||||
run: "edit High",
|
||||
},
|
||||
{
|
||||
trigger: ".dropdown-menu li a:contains(High):not(:has(.fa-spin))",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal .modal-content .o_form_button_save",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "body:not(:has(.modal-content))",
|
||||
},
|
||||
...stepUtils.saveForm(),
|
||||
{
|
||||
trigger: ".breadcrumb .o_back_button a",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_base_automation_kanban_view .o_kanban_record",
|
||||
run() {
|
||||
assertEqual(
|
||||
this.anchor.querySelector(".o_automation_base_info").textContent,
|
||||
"Test ruletest_base_automation.projectTag is addedtest"
|
||||
);
|
||||
assertEqual(
|
||||
this.anchor.querySelector(".o_automation_actions").textContent,
|
||||
"Update test_base_automation.projectUpdate test_base_automation.project"
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_open_automation_from_grouped_kanban", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: ".o_kanban_header:contains(test tag)",
|
||||
run: "hover && click .o_kanban_view .o_group_config button.dropdown-toggle",
|
||||
},
|
||||
{
|
||||
trigger: ".dropdown-menu .o_column_automations",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_base_automation_kanban_view .o_control_panel button.o-kanban-button-new",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_form_view",
|
||||
run() {
|
||||
assertEqual(
|
||||
this.anchor.querySelector(".o_field_widget[name='trigger'] input").value,
|
||||
"Tag is added"
|
||||
);
|
||||
assertEqual(
|
||||
this.anchor.querySelector(".o_field_widget[name='trg_field_ref'] input").value,
|
||||
"test tag"
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".o_form_view .o_field_widget[name='name'] input",
|
||||
run: "edit From Tour",
|
||||
},
|
||||
...stepUtils.saveForm(),
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_kanban_automation_view_stage_trigger", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: ".o_base_automation_kanban_view",
|
||||
},
|
||||
{
|
||||
trigger: ".o_kanban_record .fs-2:contains(Test Stage)",
|
||||
},
|
||||
{
|
||||
trigger: ".o_kanban_record .o_tag:contains(Stage value)",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_kanban_automation_view_time_trigger", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: ".o_base_automation_kanban_view",
|
||||
},
|
||||
{
|
||||
trigger: ".o_automation_base_info > div > div > span:nth-child(1):contains(1)",
|
||||
},
|
||||
{
|
||||
trigger: ".o_automation_base_info .text-lowercase:contains(hours)",
|
||||
},
|
||||
{
|
||||
trigger: `.o_kanban_record .o_tag:contains("Last Automation (Automated Rule Test)")`,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_kanban_automation_view_time_updated_trigger", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: ".o_base_automation_kanban_view",
|
||||
},
|
||||
{
|
||||
trigger: ".o_automation_base_info > div > div > span:nth-child(1):contains(1)",
|
||||
async run() {
|
||||
const lowercaseTexts = document.querySelectorAll(
|
||||
".o_automation_base_info .text-lowercase"
|
||||
);
|
||||
assertEqual(lowercaseTexts.length, 2);
|
||||
assertEqual(lowercaseTexts[0].innerText, "hours");
|
||||
assertEqual(lowercaseTexts[1].innerText, "after last update");
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_kanban_automation_view_create_action", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: ".o_base_automation_kanban_view",
|
||||
},
|
||||
{
|
||||
trigger: "div[name='action_server_ids']:contains(Create Contact with name NameX)",
|
||||
async run() {
|
||||
assertEqual(document.querySelectorAll(".fa.fa-plus-square").length, 1);
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_resize_kanban", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: ".o_base_automation_kanban_view",
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
".o_automation_actions:contains(Set Active To False Set Active To False Set Active To False)",
|
||||
async run() {
|
||||
document.body.style.setProperty("width", "500px");
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".o_automation_actions:contains(Set Active To False 2 actions)",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_form_view_resequence_actions", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger:
|
||||
".o_form_renderer .o_field_widget[name='action_server_ids'] .o_kanban_renderer",
|
||||
async run() {
|
||||
assertEqual(
|
||||
this.anchor.innerText,
|
||||
"Update Active 0\nUpdate Active 1\nUpdate Active 2"
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
".o_form_renderer .o_field_widget[name='action_server_ids'] .o_kanban_record:nth-child(3)",
|
||||
run: "drag_and_drop(.o_form_renderer .o_field_widget[name='action_server_ids'] .o_kanban_record:nth-child(1))",
|
||||
},
|
||||
...stepUtils.saveForm(),
|
||||
{
|
||||
trigger:
|
||||
".o_form_renderer .o_field_widget[name='action_server_ids'] .o_kanban_renderer",
|
||||
async run() {
|
||||
assertEqual(
|
||||
this.anchor.innerText,
|
||||
"Update Active 2\nUpdate Active 0\nUpdate Active 1"
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
".o_form_renderer .o_field_widget[name='action_server_ids'] .o_kanban_view .o_cp_buttons button",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal-content .o_form_renderer",
|
||||
run() {
|
||||
const allFields = this.anchor.querySelectorAll(".o_field_widget[name]");
|
||||
assertEqual(
|
||||
Array.from(allFields)
|
||||
.map((el) => el.getAttribute("name"))
|
||||
.includes("model_id"),
|
||||
false
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".modal-content .o_form_renderer [name='state'] span[value*='followers']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
".modal-content .o_form_renderer [name='state'] span.active[value*='followers']",
|
||||
},
|
||||
{
|
||||
trigger: ".modal-content .o_form_button_cancel",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "body:not(:has(.modal-content))",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_form_view_model_id", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: ".o_field_widget[name='model_id'] input",
|
||||
run: "edit base.automation.line.test",
|
||||
},
|
||||
{
|
||||
trigger: ".dropdown-menu li a:contains(Automated Rule Line Test)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_field_widget[name='trigger'] input",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_select_menu_menu",
|
||||
run() {
|
||||
assertEqual(
|
||||
Array.from(this.anchor.querySelectorAll(".o_select_menu_group"))
|
||||
.map((el) => el.textContent)
|
||||
.join(", "),
|
||||
"Values Updated, Timing Conditions, Custom, External"
|
||||
);
|
||||
assertEqual(
|
||||
Array.from(this.anchor.querySelectorAll(".o_select_menu_item"))
|
||||
.map((el) => el.textContent)
|
||||
.join(", "),
|
||||
"User is set, Based on date field, After creation, After last update, On create, On create and edit, On deletion, On UI change, On webhook"
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
trigger: ".o_field_widget[name='model_id'] input",
|
||||
run: "edit test_base_automation.project",
|
||||
},
|
||||
{
|
||||
trigger: ".dropdown-menu li a:contains(test_base_automation.project)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_field_widget[name='trigger'] input",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_select_menu_menu",
|
||||
run() {
|
||||
assertEqual(
|
||||
Array.from(this.anchor.querySelectorAll(".o_select_menu_group"))
|
||||
.map((el) => el.textContent)
|
||||
.join(", "),
|
||||
"Values Updated, Timing Conditions, Custom, External"
|
||||
);
|
||||
assertEqual(
|
||||
Array.from(this.anchor.querySelectorAll(".o_select_menu_item"))
|
||||
.map((el) => el.textContent)
|
||||
.join(", "),
|
||||
"Stage is set to, User is set, Tag is added, Priority is set to, Based on date field, After creation, After last update, On create, On create and edit, On deletion, On UI change, On webhook"
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
trigger: ".o_form_button_cancel",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_base_automation_kanban_view",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_form_view_custom_reference_field", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: ".o_field_widget[name='model_id'] input",
|
||||
run: "edit test_base_automation.project",
|
||||
},
|
||||
{
|
||||
trigger: ".dropdown-menu li a:contains(test_base_automation.project)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "body:not(:has(.o_field_widget[name='trg_field_ref']))",
|
||||
},
|
||||
{
|
||||
content: "Open select",
|
||||
trigger: ".o_form_renderer #trigger_0",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_select_menu_item:contains(Stage is set to)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_field_widget[name='trg_field_ref'] input",
|
||||
run: "fill test",
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
".o_field_widget[name='trg_field_ref'] .o-autocomplete--dropdown-menu:not(:has(a .fa-spin)",
|
||||
run() {
|
||||
assertEqual(this.anchor.innerText, "test stage\nSearch more...");
|
||||
},
|
||||
},
|
||||
{
|
||||
content: "Open select",
|
||||
trigger: ".o_form_renderer #trigger_0",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_select_menu_item:contains(Tag is added)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
".o_field_widget[name='trg_field_ref'] :not(:has(.o-autocomplete--dropdown-menu))",
|
||||
},
|
||||
{
|
||||
trigger: ".o_field_widget[name='trg_field_ref'] input",
|
||||
run: "fill test",
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
".o_field_widget[name='trg_field_ref'] .o-autocomplete--dropdown-menu:not(:has(a .fa-spin)",
|
||||
run() {
|
||||
assertEqual(this.anchor.innerText, "test tag\nSearch more...");
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".o_form_button_cancel",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_base_automation_kanban_view",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_form_view_mail_triggers", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: ".o_field_widget[name='model_id'] input",
|
||||
run: "edit base.automation.lead.test",
|
||||
},
|
||||
{
|
||||
trigger: ".dropdown-menu li a:contains(Automated Rule Test)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_field_widget[name='trigger'] input",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_select_menu_menu",
|
||||
run() {
|
||||
assertEqual(
|
||||
Array.from(this.anchor.querySelectorAll(".o_select_menu_group"))
|
||||
.map((el) => el.textContent)
|
||||
.join(", "),
|
||||
"Values Updated, Timing Conditions, Custom, External"
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".o_field_widget[name='model_id'] input",
|
||||
run: "edit base.automation.lead.thread.test",
|
||||
},
|
||||
{
|
||||
trigger: ".dropdown-menu li a:contains(Threaded Lead Test)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_field_widget[name='trigger'] input",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_select_menu_menu",
|
||||
run() {
|
||||
assertEqual(
|
||||
Array.from(this.anchor.querySelectorAll(".o_select_menu_group "))
|
||||
.map((el) => el.textContent)
|
||||
.join(", "),
|
||||
"Values Updated, Email Events, Timing Conditions, Custom, External"
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
trigger: "button.o_form_button_cancel",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "body:not(:has(button.o_form_button_cancel)",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("base_automation.on_change_rule_creation", {
|
||||
url: "/odoo/action-base_automation.base_automation_act",
|
||||
steps: () => [
|
||||
{
|
||||
trigger: ".o-kanban-button-new",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_field_widget[name=name] input",
|
||||
run: "edit Test rule",
|
||||
},
|
||||
{
|
||||
trigger: ".o_field_widget[name=model_id] input",
|
||||
run: "edit ir.ui.view",
|
||||
},
|
||||
{
|
||||
trigger: ".ui-menu-item > a:text(View)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Open select",
|
||||
trigger: ".o_form_renderer #trigger_0",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_select_menu_item:contains(On UI change)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_field_widget[name=on_change_field_ids] input",
|
||||
run: "edit Active",
|
||||
},
|
||||
{
|
||||
trigger: ".ui-menu-item > a:text(Active)",
|
||||
run: "click",
|
||||
},
|
||||
...stepUtils.saveForm(),
|
||||
],
|
||||
});
|
||||
|
|
@ -2,3 +2,5 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_flow
|
||||
from . import test_server_actions
|
||||
from . import test_tour
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,77 @@
|
|||
# # Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo.addons.base.models.ir_actions import ServerActionWithWarningsError
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.addons.base.tests.test_ir_actions import TestServerActionsBase
|
||||
|
||||
|
||||
class TestServerActionsValidation(TestServerActionsBase):
|
||||
def test_multi_action_children_warnings(self):
|
||||
self.action.write({
|
||||
'state': 'multi',
|
||||
'child_ids': [self.test_server_action.id]
|
||||
})
|
||||
self.assertEqual(self.action.model_id.model, "res.partner")
|
||||
self.assertEqual(self.test_server_action.model_id.model, "ir.actions.server")
|
||||
self.assertEqual(self.action.warning, "Following child actions should have the same model (Contact): TestDummyServerAction")
|
||||
|
||||
new_action = self.action.copy()
|
||||
with self.assertRaises(ValidationError) as ve:
|
||||
new_action.write({
|
||||
'child_ids': [self.action.id]
|
||||
})
|
||||
self.assertEqual(ve.exception.args[0], "Following child actions have warnings: TestAction")
|
||||
|
||||
def test_webhook_payload_includes_group_restricted_fields(self):
|
||||
self.test_server_action.write({
|
||||
'state': 'webhook',
|
||||
'webhook_field_ids': [self.env['ir.model.fields']._get('ir.actions.server', 'code').id],
|
||||
})
|
||||
self.assertEqual(self.test_server_action.warning, "Group-restricted fields cannot be included in "
|
||||
"webhook payloads, as it could allow any user to "
|
||||
"accidentally leak sensitive information. You will "
|
||||
"have to remove the following fields from the webhook payload:\n"
|
||||
"- Python Code")
|
||||
|
||||
def test_recursion_in_child(self):
|
||||
new_action = self.action.copy()
|
||||
self.action.write({
|
||||
'state': 'multi',
|
||||
'child_ids': [new_action.id]
|
||||
})
|
||||
with self.assertRaises(ValidationError) as ve:
|
||||
new_action.write({
|
||||
'child_ids': [self.action.id]
|
||||
})
|
||||
self.assertEqual(ve.exception.args[0], "Recursion found in child server actions")
|
||||
|
||||
def test_non_relational_field_traversal(self):
|
||||
self.action.write({
|
||||
'state': 'object_write',
|
||||
'update_path': 'parent_id.name',
|
||||
'value': 'TestNew',
|
||||
})
|
||||
with self.assertRaises(ValidationError) as ve:
|
||||
self.action.write({'update_path': 'parent_id.name.something_else'})
|
||||
self.assertEqual(ve.exception.args[0], "The path contained by the field "
|
||||
"'Field to Update Path' contains a non-relational field"
|
||||
" (Name) that is not the last field in the path. You "
|
||||
"can't traverse non-relational fields (even in the quantum"
|
||||
" realm). Make sure only the last field in the path is non-relational.")
|
||||
|
||||
def test_python_bad_expr(self):
|
||||
with self.assertRaises(ValidationError) as ve:
|
||||
self.test_server_action.write({'code': 'this is invalid python code'})
|
||||
self.assertEqual(
|
||||
ve.exception.args[0],
|
||||
"SyntaxError : invalid syntax at line 1\n"
|
||||
"this is invalid python code\n")
|
||||
|
||||
def test_cannot_run_if_warnings(self):
|
||||
self.action.write({
|
||||
'state': 'multi',
|
||||
'child_ids': [self.test_server_action.id]
|
||||
})
|
||||
self.assertTrue(self.action.warning)
|
||||
with self.assertRaises(ServerActionWithWarningsError) as e:
|
||||
self.action.run()
|
||||
self.assertEqual(e.exception.args[0], "Server action TestAction has one or more warnings, address them first.")
|
||||
|
|
@ -0,0 +1,304 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from urllib.parse import urlencode
|
||||
import ast
|
||||
|
||||
from odoo import Command
|
||||
|
||||
from odoo.tests import HttpCase, tagged
|
||||
|
||||
|
||||
def _urlencode_kwargs(**kwargs):
|
||||
return urlencode(kwargs)
|
||||
|
||||
|
||||
@tagged("post_install_l10n", "post_install", "-at_install")
|
||||
class BaseAutomationTestUi(HttpCase):
|
||||
def _neutralize_preexisting_automations(self, neutralize_action=True):
|
||||
self.env["base.automation"].with_context(active_test=False).search([]).write({"active": False})
|
||||
if neutralize_action:
|
||||
context = ast.literal_eval(self.env.ref("base_automation.base_automation_act").context)
|
||||
del context["search_default_inactive"]
|
||||
self.env.ref("base_automation.base_automation_act").context = str(context)
|
||||
|
||||
def test_01_base_automation_tour(self):
|
||||
self._neutralize_preexisting_automations()
|
||||
self.start_tour("/odoo/action-base_automation.base_automation_act?debug=tests", "test_base_automation", login="admin")
|
||||
base_automation = self.env["base.automation"].search([])
|
||||
self.assertEqual(base_automation.model_id.model, "res.partner")
|
||||
self.assertEqual(base_automation.trigger, "on_create_or_write")
|
||||
self.assertEqual(base_automation.action_server_ids.state, "object_write") # only one action
|
||||
self.assertEqual(base_automation.action_server_ids.model_name, "res.partner")
|
||||
self.assertEqual(base_automation.action_server_ids.update_field_id.name, "function")
|
||||
self.assertEqual(base_automation.action_server_ids.value, "Test")
|
||||
|
||||
def test_base_automation_on_tag_added(self):
|
||||
self._neutralize_preexisting_automations()
|
||||
self.env["test_base_automation.tag"].create({"name": "test"})
|
||||
self.start_tour("/odoo/action-base_automation.base_automation_act?debug=tests", "test_base_automation_on_tag_added", login="admin")
|
||||
|
||||
def test_open_automation_from_grouped_kanban(self):
|
||||
self._neutralize_preexisting_automations()
|
||||
|
||||
test_view = self.env["ir.ui.view"].create(
|
||||
{
|
||||
"name": "test_view",
|
||||
"model": "test_base_automation.project",
|
||||
"type": "kanban",
|
||||
"arch": """
|
||||
<kanban default_group_by="tag_ids">
|
||||
<templates>
|
||||
<t t-name="card">
|
||||
<field name="name" />
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
""",
|
||||
}
|
||||
)
|
||||
test_action = self.env["ir.actions.act_window"].create(
|
||||
{
|
||||
"name": "test action",
|
||||
"res_model": "test_base_automation.project",
|
||||
"view_ids": [Command.create({"view_id": test_view.id, "view_mode": "kanban"})],
|
||||
}
|
||||
)
|
||||
tag = self.env["test_base_automation.tag"].create({"name": "test tag"})
|
||||
self.env["test_base_automation.project"].create({"name": "test", "tag_ids": [Command.link(tag.id)]})
|
||||
|
||||
self.start_tour(f"/odoo/action-{test_action.id}?debug=0", "test_open_automation_from_grouped_kanban", login="admin")
|
||||
base_auto = self.env["base.automation"].search([])
|
||||
self.assertEqual(base_auto.name, "From Tour")
|
||||
self.assertEqual(base_auto.model_name, "test_base_automation.project")
|
||||
self.assertEqual(base_auto.trigger_field_ids.name, "tag_ids")
|
||||
self.assertEqual(base_auto.trigger, "on_tag_set")
|
||||
self.assertEqual(base_auto.trg_field_ref_model_name, "test_base_automation.tag")
|
||||
self.assertEqual(base_auto.trg_field_ref, tag.id)
|
||||
|
||||
def test_kanban_automation_view_stage_trigger(self):
|
||||
self._neutralize_preexisting_automations()
|
||||
|
||||
project_model = self.env.ref('test_base_automation.model_test_base_automation_project')
|
||||
stage_field = self.env['ir.model.fields'].search([
|
||||
('model_id', '=', project_model.id),
|
||||
('name', '=', 'stage_id'),
|
||||
])
|
||||
test_stage = self.env['test_base_automation.stage'].create({'name': 'Stage value'})
|
||||
|
||||
automation = self.env["base.automation"].create({
|
||||
"name": "Test Stage",
|
||||
"trigger": "on_stage_set",
|
||||
"model_id": project_model.id,
|
||||
"trigger_field_ids": [stage_field.id],
|
||||
"trg_field_ref": test_stage,
|
||||
})
|
||||
|
||||
action = {
|
||||
"name": "Set Active To False",
|
||||
"base_automation_id": automation.id,
|
||||
"state": "object_write",
|
||||
"update_path": "user_ids.active",
|
||||
"value": False,
|
||||
"model_id": project_model.id
|
||||
}
|
||||
automation.write({"action_server_ids": [Command.create(action)]})
|
||||
|
||||
self.start_tour(
|
||||
"/odoo/action-base_automation.base_automation_act",
|
||||
"test_kanban_automation_view_stage_trigger", login="admin"
|
||||
)
|
||||
|
||||
def test_kanban_automation_view_time_trigger(self):
|
||||
self._neutralize_preexisting_automations()
|
||||
model = self.env['ir.model']._get("base.automation.lead.test")
|
||||
|
||||
date_field = self.env['ir.model.fields'].search([
|
||||
('model_id', '=', model.id),
|
||||
('name', '=', 'date_automation_last'),
|
||||
])
|
||||
|
||||
self.env["base.automation"].create({
|
||||
"name": "Test Date",
|
||||
"trigger": "on_time",
|
||||
"model_id": model.id,
|
||||
"trg_date_range": 1,
|
||||
"trg_date_range_type": "hour",
|
||||
"trg_date_id": date_field.id,
|
||||
})
|
||||
|
||||
self.start_tour(
|
||||
"/odoo/action-base_automation.base_automation_act",
|
||||
"test_kanban_automation_view_time_trigger", login="admin"
|
||||
)
|
||||
|
||||
def test_kanban_automation_view_time_updated_trigger(self):
|
||||
self._neutralize_preexisting_automations()
|
||||
model = self.env.ref("base.model_res_partner")
|
||||
|
||||
self.env["base.automation"].create({
|
||||
"name": "Test Date",
|
||||
"trigger": "on_time_updated",
|
||||
"model_id": model.id,
|
||||
"trg_date_range": 1,
|
||||
"trg_date_range_type": "hour",
|
||||
})
|
||||
|
||||
self.start_tour(
|
||||
"/odoo/action-base_automation.base_automation_act",
|
||||
"test_kanban_automation_view_time_updated_trigger", login="admin"
|
||||
)
|
||||
|
||||
def test_kanban_automation_view_create_action(self):
|
||||
self._neutralize_preexisting_automations()
|
||||
model = self.env.ref("base.model_res_partner")
|
||||
|
||||
automation = self.env["base.automation"].create({
|
||||
"name": "Test",
|
||||
"trigger": "on_create_or_write",
|
||||
"model_id": model.id,
|
||||
})
|
||||
|
||||
action = {
|
||||
"name": "Create Contact with name NameX",
|
||||
"base_automation_id": automation.id,
|
||||
"state": "object_create",
|
||||
"value": "NameX",
|
||||
"model_id": model.id
|
||||
}
|
||||
|
||||
automation.write({"action_server_ids": [Command.create(action)]})
|
||||
|
||||
self.start_tour(
|
||||
"/odoo/action-base_automation.base_automation_act",
|
||||
"test_kanban_automation_view_create_action", login="admin"
|
||||
)
|
||||
|
||||
def test_resize_kanban(self):
|
||||
self._neutralize_preexisting_automations()
|
||||
model = self.env.ref("base.model_res_partner")
|
||||
|
||||
automation = self.env["base.automation"].create(
|
||||
{
|
||||
"name": "Test",
|
||||
"trigger": "on_create_or_write",
|
||||
"model_id": model.id,
|
||||
}
|
||||
)
|
||||
|
||||
action = {
|
||||
"name": "Set Active To False",
|
||||
"base_automation_id": automation.id,
|
||||
"state": "object_write",
|
||||
"update_path": "active",
|
||||
"value": False,
|
||||
"model_id": model.id,
|
||||
}
|
||||
automation.write({"action_server_ids": [Command.create(action) for i in range(3)]})
|
||||
|
||||
self.start_tour(
|
||||
"/odoo/action-base_automation.base_automation_act",
|
||||
"test_resize_kanban",
|
||||
login="admin",
|
||||
)
|
||||
|
||||
def test_form_view(self):
|
||||
model = self.env.ref("base.model_res_partner")
|
||||
automation = self.env["base.automation"].create(
|
||||
{
|
||||
"name": "Test",
|
||||
"trigger": "on_create_or_write",
|
||||
"model_id": model.id,
|
||||
}
|
||||
)
|
||||
action = {
|
||||
"name": "Update Active",
|
||||
"base_automation_id": automation.id,
|
||||
"state": "object_write",
|
||||
"update_path": "active",
|
||||
"update_boolean_value": "false",
|
||||
"model_id": model.id,
|
||||
}
|
||||
automation.write(
|
||||
{"action_server_ids": [Command.create(dict(action, name=action["name"] + f" {i}", sequence=i)) for i in range(3)]}
|
||||
)
|
||||
self.assertEqual(
|
||||
automation.action_server_ids.mapped("name"),
|
||||
["Update Active 0", "Update Active 1", "Update Active 2"],
|
||||
)
|
||||
|
||||
onchange_link_passes = 0
|
||||
origin_link_onchange = type(self.env["ir.actions.server"]).onchange
|
||||
|
||||
def _onchange_base_auto_link(self_model, *args):
|
||||
nonlocal onchange_link_passes
|
||||
onchange_link_passes += 1
|
||||
res = origin_link_onchange(self_model, *args)
|
||||
if onchange_link_passes == 1:
|
||||
default_keys = {k: v for k, v in self_model.env.context.items() if k.startswith("default_")}
|
||||
self.assertEqual(
|
||||
default_keys,
|
||||
{"default_model_id": model.id, "default_usage": "base_automation"},
|
||||
)
|
||||
if onchange_link_passes == 2:
|
||||
self.assertEqual(res["value"]["name"], "Add Followers")
|
||||
|
||||
return res
|
||||
|
||||
self.patch(type(self.env["ir.actions.server"]), "onchange", _onchange_base_auto_link)
|
||||
|
||||
self.start_tour(
|
||||
(
|
||||
f"/odoo/action-base_automation.base_automation_act/{automation.id}?debug=0"
|
||||
),
|
||||
"test_form_view_resequence_actions",
|
||||
login="admin",
|
||||
)
|
||||
self.assertEqual(onchange_link_passes, 2)
|
||||
self.assertEqual(
|
||||
automation.action_server_ids.mapped("name"),
|
||||
["Update Active 2", "Update Active 0", "Update Active 1"],
|
||||
)
|
||||
|
||||
def test_form_view_model_id(self):
|
||||
self.start_tour(
|
||||
(
|
||||
"/odoo/action-base_automation.base_automation_act/new?view_type='form'&debug=0)"
|
||||
),
|
||||
"test_form_view_model_id",
|
||||
login="admin",
|
||||
)
|
||||
|
||||
def test_form_view_custom_reference_field(self):
|
||||
self.env["test_base_automation.stage"].create({"name": "test stage"})
|
||||
self.env["test_base_automation.tag"].create({"name": "test tag"})
|
||||
self.start_tour(
|
||||
(
|
||||
"/odoo/action-base_automation.base_automation_act/new?view_type='form'&debug=0)"
|
||||
),
|
||||
"test_form_view_custom_reference_field",
|
||||
login="admin",
|
||||
)
|
||||
|
||||
def test_form_view_mail_triggers(self):
|
||||
self.start_tour(
|
||||
(
|
||||
"/odoo/action-base_automation.base_automation_act/new?view_type='form'&debug=0)"
|
||||
),
|
||||
"test_form_view_mail_triggers",
|
||||
login="admin",
|
||||
)
|
||||
|
||||
def test_on_change_rule_creation(self):
|
||||
""" test on_change rule creation from the UI """
|
||||
self.start_tour("/odoo/action-base_automation.base_automation_act", 'base_automation.on_change_rule_creation', login="admin")
|
||||
|
||||
rule = self.env['base.automation'].search([], order="create_date desc", limit=1)[0]
|
||||
view_model = self.env['ir.model']._get("ir.ui.view")
|
||||
active_field = self.env['ir.model.fields'].search([
|
||||
('name', '=', 'active'),
|
||||
('model', '=', 'ir.ui.view'),
|
||||
])[0]
|
||||
self.assertEqual(rule.name, "Test rule")
|
||||
self.assertEqual(rule.model_id, view_model)
|
||||
self.assertEqual(rule.trigger, 'on_change')
|
||||
self.assertEqual(len(rule.on_change_field_ids), 1)
|
||||
self.assertEqual(rule.on_change_field_ids[0], active_field)
|
||||
|
|
@ -12,7 +12,6 @@ pip install odoo-bringout-oca-ocb-test_crm_full
|
|||
|
||||
## Dependencies
|
||||
|
||||
This addon depends on:
|
||||
- crm
|
||||
- crm_iap_enrich
|
||||
- crm_iap_mine
|
||||
|
|
@ -24,34 +23,12 @@ This addon depends on:
|
|||
- website_crm_partner_assign
|
||||
- website_crm_livechat
|
||||
|
||||
## Manifest Information
|
||||
|
||||
- **Name**: Test Full Crm Flow
|
||||
- **Version**: 1.0
|
||||
- **Category**: Hidden/Tests
|
||||
- **License**: LGPL-3
|
||||
- **Installable**: False
|
||||
|
||||
## Source
|
||||
|
||||
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `test_crm_full`.
|
||||
- Repository: https://github.com/OCA/OCB
|
||||
- Branch: 19.0
|
||||
- Path: addons/test_crm_full
|
||||
|
||||
## License
|
||||
|
||||
This package maintains the original LGPL-3 license from the upstream Odoo project.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Overview: doc/OVERVIEW.md
|
||||
- Architecture: doc/ARCHITECTURE.md
|
||||
- Models: doc/MODELS.md
|
||||
- Controllers: doc/CONTROLLERS.md
|
||||
- Wizards: doc/WIZARDS.md
|
||||
- Reports: doc/REPORTS.md
|
||||
- Security: doc/SECURITY.md
|
||||
- Install: doc/INSTALL.md
|
||||
- Usage: doc/USAGE.md
|
||||
- Configuration: doc/CONFIGURATION.md
|
||||
- Dependencies: doc/DEPENDENCIES.md
|
||||
- Troubleshooting: doc/TROUBLESHOOTING.md
|
||||
- FAQ: doc/FAQ.md
|
||||
This package preserves the original LGPL-3 license.
|
||||
|
|
|
|||
|
|
@ -1,21 +1,23 @@
|
|||
[project]
|
||||
name = "odoo-bringout-oca-ocb-test_crm_full"
|
||||
version = "16.0.0"
|
||||
description = "Test Full Crm Flow - Odoo addon"
|
||||
description = "Test Full Crm Flow -
|
||||
Odoo addon
|
||||
"
|
||||
authors = [
|
||||
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
|
||||
]
|
||||
dependencies = [
|
||||
"odoo-bringout-oca-ocb-crm>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-crm_iap_enrich>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-crm_iap_mine>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-crm_sms>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-event_crm>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-sale_crm>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-website_crm>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-website_crm_iap_reveal>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-website_crm_partner_assign>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-website_crm_livechat>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-crm>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-crm_iap_enrich>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-crm_iap_mine>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-crm_sms>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-event_crm>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-sale_crm>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-website_crm>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-website_crm_iap_reveal>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-website_crm_partner_assign>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-website_crm_livechat>=19.0.0",
|
||||
"requests>=2.25.1"
|
||||
]
|
||||
readme = "README.md"
|
||||
|
|
@ -25,7 +27,7 @@ classifiers = [
|
|||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Office/Business",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -20,5 +20,6 @@ backend. It notably includes IAP bridges modules to test their impact. """,
|
|||
'website_crm_partner_assign',
|
||||
'website_crm_livechat',
|
||||
],
|
||||
'author': 'Odoo S.A.',
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ class TestCrmFullCommon(TestCrmCommon, MockIAPReveal, MockVisitor):
|
|||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestCrmFullCommon, cls).setUpClass()
|
||||
cls._init_mail_gateway()
|
||||
cls._activate_multi_company()
|
||||
|
||||
# Context data: dates
|
||||
|
|
@ -34,7 +33,6 @@ class TestCrmFullCommon(TestCrmCommon, MockIAPReveal, MockVisitor):
|
|||
'email': 'partner.email.%02d@test.example.com' % idx,
|
||||
'function': 'Noisy Customer',
|
||||
'lang': 'fr_BE',
|
||||
'mobile': '04569999%02d' % idx,
|
||||
'name': 'PartnerCustomer',
|
||||
'phone': '04560000%02d' % idx,
|
||||
'street': 'Super Street, %092d' % idx,
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@
|
|||
from freezegun import freeze_time
|
||||
|
||||
from odoo.addons.test_crm_full.tests.common import TestCrmFullCommon
|
||||
from odoo.tests.common import users, warmup, Form
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests import Form, users, warmup, tagged
|
||||
|
||||
|
||||
@tagged('crm_performance', 'post_install', '-at_install', '-standard')
|
||||
|
|
@ -15,10 +14,13 @@ class CrmPerformanceCase(TestCrmFullCommon):
|
|||
super(CrmPerformanceCase, self).setUp()
|
||||
# patch registry to simulate a ready environment
|
||||
self.patch(self.env.registry, 'ready', True)
|
||||
# we don't use mock_mail_gateway thus want to mock smtp to test the stack
|
||||
self._mock_smtplib_connection()
|
||||
|
||||
self._flush_tracking()
|
||||
|
||||
self.user_sales_leads.write({
|
||||
'groups_id': [
|
||||
'group_ids': [
|
||||
(4, self.env.ref('event.group_event_user').id),
|
||||
(4, self.env.ref('im_livechat.im_livechat_group_user').id),
|
||||
]
|
||||
|
|
@ -40,16 +42,15 @@ class TestCrmPerformance(CrmPerformanceCase):
|
|||
""" Test multiple lead creation (import) """
|
||||
batch_size = 10
|
||||
country_be = self.env.ref('base.be')
|
||||
lang_be_id = self.env['res.lang']._lang_get_id('fr_BE')
|
||||
lang_be_id = self.env['res.lang']._get_data(code='fr_BE').id
|
||||
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=194): # tcf 193 / com 194
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=192): # tcf 191
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
crm_values = [
|
||||
{'country_id': country_be.id,
|
||||
'email_from': 'address.email.%02d@test.example.com' % idx,
|
||||
'function': 'Noisy Customer',
|
||||
'lang_id': lang_be_id,
|
||||
'mobile': '04551111%02d' % idx,
|
||||
'name': 'Test Lead %02d' % idx,
|
||||
'phone': '04550000%02d' % idx,
|
||||
'street': 'Super Street, %092d' % idx,
|
||||
|
|
@ -70,14 +71,13 @@ class TestCrmPerformance(CrmPerformanceCase):
|
|||
country_be = self.env.ref('base.be')
|
||||
lang_be = self.env['res.lang']._lang_get('fr_BE')
|
||||
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=189): # tcf only: 173 - com runbot: 174/175
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=145): # tcf 142 / com 144
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
with Form(self.env['crm.lead']) as lead_form:
|
||||
lead_form.country_id = country_be
|
||||
lead_form.email_from = 'address.email@test.example.com'
|
||||
lead_form.function = 'Noisy Customer'
|
||||
lead_form.lang_id = lang_be
|
||||
lead_form.mobile = '0455111100'
|
||||
lead_form.name = 'Test Lead'
|
||||
lead_form.phone = '0455000011'
|
||||
lead_form.street = 'Super Street, 00'
|
||||
|
|
@ -89,7 +89,7 @@ class TestCrmPerformance(CrmPerformanceCase):
|
|||
@warmup
|
||||
def test_lead_create_form_partner(self):
|
||||
""" Test a single lead creation using Form with a partner """
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=199): # tcf 186 / com 188
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=144): # tcf 141 / com 143
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
with self.debug_mode():
|
||||
# {'invisible': ['|', ('type', '=', 'opportunity'), ('is_partner_visible', '=', False)]}
|
||||
|
|
@ -105,16 +105,15 @@ class TestCrmPerformance(CrmPerformanceCase):
|
|||
def test_lead_create_single_address(self):
|
||||
""" Test multiple lead creation (import) """
|
||||
country_be = self.env.ref('base.be')
|
||||
lang_be_id = self.env['res.lang']._lang_get_id('fr_BE')
|
||||
lang_be_id = self.env['res.lang']._get_data(code='fr_BE').id
|
||||
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=43): # tcf only: 41 - com runbot: 42
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=30): # tcf 29
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
crm_values = [
|
||||
{'country_id': country_be.id,
|
||||
'email_from': 'address.email.00@test.example.com',
|
||||
'function': 'Noisy Customer',
|
||||
'lang_id': lang_be_id,
|
||||
'mobile': '0455111100',
|
||||
'name': 'Test Lead',
|
||||
'phone': '0455000000',
|
||||
'street': 'Super Street, 00',
|
||||
|
|
@ -127,7 +126,7 @@ class TestCrmPerformance(CrmPerformanceCase):
|
|||
@warmup
|
||||
def test_lead_create_single_partner(self):
|
||||
""" Test multiple lead creation (import) """
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=49): # tcf only: 47 - com runbot: 48
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=30): # tcf 29
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
crm_values = [
|
||||
{'partner_id': self.partners[0].id,
|
||||
|
|
|
|||
|
|
@ -10,45 +10,27 @@ pip install odoo-bringout-oca-ocb-test_discuss_full
|
|||
|
||||
## Dependencies
|
||||
|
||||
This addon depends on:
|
||||
- calendar
|
||||
- crm
|
||||
- crm_livechat
|
||||
- hr_attendance
|
||||
- hr_fleet
|
||||
- hr_holidays
|
||||
- hr_homeworking
|
||||
- im_livechat
|
||||
- mail
|
||||
- mail_bot
|
||||
- note
|
||||
- project_todo
|
||||
- website_livechat
|
||||
|
||||
## Manifest Information
|
||||
|
||||
- **Name**: Test Discuss (full)
|
||||
- **Version**: 1.0
|
||||
- **Category**: Hidden
|
||||
- **License**: LGPL-3
|
||||
- **Installable**: True
|
||||
- website_sale
|
||||
- website_slides
|
||||
|
||||
## Source
|
||||
|
||||
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `test_discuss_full`.
|
||||
- Repository: https://github.com/OCA/OCB
|
||||
- Branch: 19.0
|
||||
- Path: addons/test_discuss_full
|
||||
|
||||
## License
|
||||
|
||||
This package maintains the original LGPL-3 license from the upstream Odoo project.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Overview: doc/OVERVIEW.md
|
||||
- Architecture: doc/ARCHITECTURE.md
|
||||
- Models: doc/MODELS.md
|
||||
- Controllers: doc/CONTROLLERS.md
|
||||
- Wizards: doc/WIZARDS.md
|
||||
- Reports: doc/REPORTS.md
|
||||
- Security: doc/SECURITY.md
|
||||
- Install: doc/INSTALL.md
|
||||
- Usage: doc/USAGE.md
|
||||
- Configuration: doc/CONFIGURATION.md
|
||||
- Dependencies: doc/DEPENDENCIES.md
|
||||
- Troubleshooting: doc/TROUBLESHOOTING.md
|
||||
- FAQ: doc/FAQ.md
|
||||
This package preserves the original LGPL-3 license.
|
||||
|
|
|
|||
|
|
@ -1,20 +1,27 @@
|
|||
[project]
|
||||
name = "odoo-bringout-oca-ocb-test_discuss_full"
|
||||
version = "16.0.0"
|
||||
description = "Test Discuss (full) - Test of Discuss with all possible overrides installed."
|
||||
description = "Test Discuss (full) -
|
||||
Test of Discuss with all possible overrides installed.
|
||||
"
|
||||
authors = [
|
||||
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
|
||||
]
|
||||
dependencies = [
|
||||
"odoo-bringout-oca-ocb-calendar>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-crm>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-crm_livechat>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-hr_holidays>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-im_livechat>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-mail>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-mail_bot>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-note>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-website_livechat>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-calendar>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-crm>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-crm_livechat>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-hr_attendance>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-hr_fleet>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-hr_holidays>=19.0.0",
|
||||
"TODO_MAP-hr_homeworking>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-im_livechat>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-mail>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-mail_bot>=19.0.0",
|
||||
"TODO_MAP-project_todo>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-website_livechat>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-website_sale>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-website_slides>=19.0.0",
|
||||
"requests>=2.25.1"
|
||||
]
|
||||
readme = "README.md"
|
||||
|
|
@ -24,7 +31,7 @@ classifiers = [
|
|||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Office/Business",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -2,23 +2,34 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Test Discuss (full)',
|
||||
'version': '1.0',
|
||||
'category': 'Hidden',
|
||||
'sequence': 9877,
|
||||
'summary': 'Test of Discuss with all possible overrides installed.',
|
||||
'description': """Test of Discuss with all possible overrides installed, including feature and performance tests.""",
|
||||
'depends': [
|
||||
'calendar',
|
||||
'crm',
|
||||
'crm_livechat',
|
||||
'hr_holidays',
|
||||
'im_livechat',
|
||||
'mail',
|
||||
'mail_bot',
|
||||
'note',
|
||||
'website_livechat',
|
||||
"name": "Test Discuss (full)",
|
||||
"version": "1.0",
|
||||
"category": "Productivity/Discuss",
|
||||
"sequence": 9877,
|
||||
"summary": "Test of Discuss with all possible overrides installed.",
|
||||
"description": """Test of Discuss with all possible overrides installed, including feature and performance tests.""",
|
||||
"depends": [
|
||||
"calendar",
|
||||
"crm",
|
||||
"crm_livechat",
|
||||
"hr_attendance",
|
||||
"hr_fleet",
|
||||
"hr_holidays",
|
||||
"hr_homeworking",
|
||||
"im_livechat",
|
||||
"mail",
|
||||
"mail_bot",
|
||||
"project_todo",
|
||||
"website_livechat",
|
||||
"website_sale",
|
||||
"website_slides",
|
||||
],
|
||||
'installable': True,
|
||||
'license': 'LGPL-3',
|
||||
"installable": True,
|
||||
"assets": {
|
||||
"web.assets_tests": [
|
||||
"test_discuss_full/static/tests/tours/**/*",
|
||||
],
|
||||
},
|
||||
"author": "Odoo S.A.",
|
||||
"license": "LGPL-3",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
|
||||
const steps = [
|
||||
{
|
||||
content: "Open the avatar card popover",
|
||||
trigger: ".o-mail-Message-avatar",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Check that the employee's work email is displayed",
|
||||
trigger: ".o_avatar_card:contains(test_employee@test.com)",
|
||||
},
|
||||
{
|
||||
content: "Check that the employee's department is displayed",
|
||||
trigger: ".o_avatar_card:contains(Test Department)",
|
||||
},
|
||||
{
|
||||
content: "Check that the employee's work phone is displayed",
|
||||
trigger: ".o_avatar_card:contains(123456789)",
|
||||
},
|
||||
{
|
||||
content: "Check that the employee's holiday status is displayed",
|
||||
trigger: ".o_avatar_card:contains(Back on)",
|
||||
},
|
||||
];
|
||||
|
||||
registry.category("web_tour.tours").add("avatar_card_tour", {
|
||||
steps: () => [
|
||||
...steps,
|
||||
{
|
||||
content: "Check that the employee's job title is displayed",
|
||||
trigger: ".o_avatar_card:contains(Test Job Title)",
|
||||
},
|
||||
{
|
||||
trigger: ".o-mail-ActivityMenu-counter:text('2')",
|
||||
},
|
||||
{
|
||||
trigger: ".o_switch_company_menu button",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: `[role=button][title='Switch to Company 2']`,
|
||||
run: "click",
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
{
|
||||
trigger: ".o-mail-ActivityMenu-counter:text('1')",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("avatar_card_tour_no_hr_access", {
|
||||
steps: () => [
|
||||
...steps,
|
||||
{
|
||||
content: "Check that the employee's job title is displayed",
|
||||
trigger: ":not(.o_avatar_card:contains(Test Job Title))",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("chatbot_redirect_to_portal", {
|
||||
url: "/contactus",
|
||||
steps: () => [
|
||||
{
|
||||
trigger: ".o-livechat-root:shadow .o-livechat-LivechatButton",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
".o-livechat-root:shadow .o-mail-Message:contains(Hello, were do you want to go?)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o-livechat-root:shadow li button:contains(Go to the portal page)",
|
||||
run: "click",
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
{
|
||||
trigger: ".o-livechat-root:shadow .o-mail-Message:contains('Go to the portal page')",
|
||||
},
|
||||
{ trigger: "#chatterRoot:shadow .o-mail-Chatter" },
|
||||
{
|
||||
trigger: ".o-livechat-root:shadow .o-mail-Message:last:contains('Tadam')",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("im_livechat_session_open", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: "button.o_switch_view.o_list",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_data_cell:contains(Visitor)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o-mail-Thread:contains('The conversation is empty.')",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -1,4 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_avatar_card_tour
|
||||
from . import test_livechat_hr_holidays
|
||||
from . import test_im_livechat_portal
|
||||
from . import test_performance
|
||||
from . import test_performance_inbox
|
||||
from . import test_livechat_session_open
|
||||
from . import test_res_partner
|
||||
|
|
|
|||
|
|
@ -0,0 +1,138 @@
|
|||
from datetime import date, timedelta
|
||||
|
||||
from odoo import Command
|
||||
from odoo.tests import tagged, users
|
||||
from odoo.tests.common import HttpCase, new_test_user
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestAvatarCardTour(MailCommon, HttpCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
new_test_user(
|
||||
cls.env,
|
||||
login="hr_user",
|
||||
company_ids=[Command.link(cls.env.company.id), Command.link(cls.company_2.id)],
|
||||
groups="hr.group_hr_user",
|
||||
)
|
||||
|
||||
# hr setup for multi-company
|
||||
department = (
|
||||
cls.env["hr.department"]
|
||||
.with_company(cls.company_2)
|
||||
.create({"name": "Test Department", "company_id": cls.company_2.id})
|
||||
)
|
||||
job = (
|
||||
cls.env["hr.job"]
|
||||
.with_company(cls.company_2)
|
||||
.create({"name": "Test Job Title", "company_id": cls.company_2.id})
|
||||
)
|
||||
other_partner = (
|
||||
cls.env["res.partner"]
|
||||
.with_company(cls.company_2)
|
||||
.create({
|
||||
"name": "Test Other Partner",
|
||||
"company_id": cls.company_2.id,
|
||||
"phone": "987654321",
|
||||
})
|
||||
)
|
||||
test_employee = (
|
||||
cls.env["hr.employee"]
|
||||
.with_company(cls.company_2)
|
||||
.create({
|
||||
"name": "Test Employee",
|
||||
"user_id": cls.user_employee_c2.id,
|
||||
"company_id": cls.company_2.id,
|
||||
"department_id": department.id,
|
||||
"job_id": job.id,
|
||||
"address_id": other_partner.id,
|
||||
"work_email": "test_employee@test.com",
|
||||
"work_phone": "123456789",
|
||||
})
|
||||
)
|
||||
cls.test_employee = test_employee
|
||||
cls.user_employee_c2.write({"employee_ids": [Command.link(test_employee.id)]})
|
||||
new_test_user(
|
||||
cls.env,
|
||||
login="base_user",
|
||||
company_ids=[Command.link(cls.env.company.id), Command.link(cls.company_2.id)],
|
||||
)
|
||||
|
||||
# hr_holidays setup for multi-company
|
||||
leave_type = (
|
||||
cls.env["hr.leave.type"]
|
||||
.with_company(cls.company_2)
|
||||
.create(
|
||||
{
|
||||
"name": "Time Off multi company",
|
||||
"company_id": cls.company_2.id,
|
||||
"time_type": "leave",
|
||||
"requires_allocation": False,
|
||||
}
|
||||
)
|
||||
)
|
||||
cls.env["hr.leave"].with_company(cls.company_2).with_context(
|
||||
leave_skip_state_check=True
|
||||
).create(
|
||||
{
|
||||
"name": "Test Leave",
|
||||
"company_id": cls.company_2.id,
|
||||
"holiday_status_id": leave_type.id,
|
||||
"employee_id": cls.test_employee.id,
|
||||
"request_date_from": (date.today() - timedelta(days=1)),
|
||||
"request_date_to": (date.today() + timedelta(days=1)),
|
||||
"state": "validate",
|
||||
}
|
||||
)
|
||||
cls.test_record = cls.env["hr.department"].create(
|
||||
[
|
||||
{"name": "Test", "company_id": cls.env.company.id},
|
||||
{"name": "Test 2", "company_id": cls.env.company.id},
|
||||
{"name": "Test 3", "company_id": cls.company_2.id},
|
||||
]
|
||||
)
|
||||
|
||||
def _setup_channel(self, user):
|
||||
self.user_employee_c2.partner_id.sudo().with_user(self.user_employee_c2).message_post(
|
||||
body="Test message in chatter",
|
||||
message_type="comment",
|
||||
subtype_xmlid="mail.mt_comment",
|
||||
)
|
||||
activity_type_todo = "mail.mail_activity_data_todo"
|
||||
self.test_record[0].activity_schedule(
|
||||
activity_type_todo,
|
||||
summary="Test Activity for Company 2",
|
||||
user_id=user.id,
|
||||
)
|
||||
self.test_record[1].activity_schedule(
|
||||
activity_type_todo,
|
||||
summary="Another Test Activity for Company 2",
|
||||
user_id=user.id,
|
||||
)
|
||||
self.test_record[2].activity_schedule(
|
||||
activity_type_todo,
|
||||
summary="Test Activity for Company 3",
|
||||
user_id=user.id,
|
||||
)
|
||||
|
||||
@users("admin", "hr_user")
|
||||
def test_avatar_card_tour_multi_company(self):
|
||||
# Clear existing activities to avoid interference with the test
|
||||
self.env["mail.activity"].with_user(self.env.user).search([]).unlink()
|
||||
self._setup_channel(self.env.user)
|
||||
self.start_tour(
|
||||
f"/odoo/res.partner/{self.user_employee_c2.partner_id.id}",
|
||||
"avatar_card_tour",
|
||||
login=self.env.user.login,
|
||||
)
|
||||
|
||||
@users("base_user")
|
||||
def test_avatar_card_tour_multi_company_no_hr_access(self):
|
||||
self._setup_channel(self.env.user)
|
||||
self.start_tour(
|
||||
f"/odoo/res.partner/{self.user_employee_c2.partner_id.id}",
|
||||
"avatar_card_tour_no_hr_access",
|
||||
login=self.env.user.login,
|
||||
)
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
from odoo import Command, tests
|
||||
from odoo.addons.website_livechat.tests.test_chatbot_ui import TestLivechatChatbotUI
|
||||
|
||||
|
||||
@tests.common.tagged("post_install", "-at_install")
|
||||
class TestImLivechatPortal(TestLivechatChatbotUI):
|
||||
def test_chatbot_redirect_to_portal(self):
|
||||
project = self.env["project.project"].create({"name": "Portal Project"})
|
||||
task = self.env["project.task"].create(
|
||||
{"name": "Test Task Name Match", "project_id": project.id}
|
||||
)
|
||||
chatbot_redirect_script = self.env["chatbot.script"].create({"title": "Redirection Bot"})
|
||||
question_step = self.env["chatbot.script.step"].create(
|
||||
[
|
||||
{
|
||||
"chatbot_script_id": chatbot_redirect_script.id,
|
||||
"message": "Hello, were do you want to go?",
|
||||
"step_type": "question_selection",
|
||||
},
|
||||
{
|
||||
"chatbot_script_id": chatbot_redirect_script.id,
|
||||
"message": "Tadam, we are on the page you asked for!",
|
||||
"step_type": "text",
|
||||
},
|
||||
]
|
||||
)[0]
|
||||
self.env["chatbot.script.answer"].create(
|
||||
[
|
||||
{
|
||||
"name": "Go to the portal page",
|
||||
"redirect_link": f"/my/tasks/{task.id}?access_token={task.access_token}",
|
||||
"script_step_id": question_step.id,
|
||||
},
|
||||
]
|
||||
)
|
||||
livechat_channel = self.env["im_livechat.channel"].create(
|
||||
{
|
||||
"name": "Redirection Channel",
|
||||
"rule_ids": [
|
||||
Command.create(
|
||||
{
|
||||
"regex_url": "/",
|
||||
"chatbot_script_id": chatbot_redirect_script.id,
|
||||
}
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
default_website = self.env.ref("website.default_website")
|
||||
default_website.channel_id = livechat_channel.id
|
||||
self.env.ref("website.default_website").channel_id = livechat_channel.id
|
||||
self.start_tour("/contactus", "chatbot_redirect_to_portal")
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import Command, fields
|
||||
from odoo.tests.common import HttpCase, tagged
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestLivechatHrHolidays(HttpCase, MailCommon):
|
||||
"""Tests for bridge between im_livechat and hr_holidays modules."""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.env["mail.presence"]._update_presence(cls.user_employee)
|
||||
leave_type = cls.env["hr.leave.type"].create(
|
||||
{"name": "Legal Leaves", "requires_allocation": False, "time_type": "leave"}
|
||||
)
|
||||
employee = cls.env["hr.employee"].create({"user_id": cls.user_employee.id})
|
||||
cls.env["hr.leave"].with_context(leave_skip_state_check=True).create(
|
||||
{
|
||||
"employee_id": employee.id,
|
||||
"holiday_status_id": leave_type.id,
|
||||
"request_date_from": fields.Datetime.today() + relativedelta(days=-2),
|
||||
"request_date_to": fields.Datetime.today() + relativedelta(days=2),
|
||||
"state": "validate",
|
||||
}
|
||||
)
|
||||
|
||||
def test_operator_available_on_leave(self):
|
||||
"""Test operator is available on leave when they are online."""
|
||||
livechat_channel = self.env["im_livechat.channel"].create(
|
||||
{"name": "support", "user_ids": [Command.link(self.user_employee.id)]}
|
||||
)
|
||||
self.assertEqual(self.user_employee.im_status, "leave_online")
|
||||
self.assertEqual(livechat_channel.available_operator_ids, self.user_employee)
|
||||
|
||||
def test_operator_limit_on_leave(self):
|
||||
"""Test livechat limit is correctly applied when operator is on leave and online."""
|
||||
livechat_channel = self.env["im_livechat.channel"].create(
|
||||
{
|
||||
"max_sessions_mode": "limited",
|
||||
"max_sessions": 1,
|
||||
"name": "support",
|
||||
"user_ids": [Command.link(self.user_employee.id)],
|
||||
}
|
||||
)
|
||||
self.make_jsonrpc_request("/im_livechat/get_session", {"channel_id": livechat_channel.id})
|
||||
self.assertEqual(self.user_employee.im_status, "leave_online")
|
||||
self.assertFalse(livechat_channel.available_operator_ids)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import odoo
|
||||
from odoo.addons.im_livechat.tests.common import TestImLivechatCommon
|
||||
from odoo.tests import new_test_user
|
||||
|
||||
|
||||
@odoo.tests.tagged("-at_install", "post_install")
|
||||
class TestImLivechatSessions(TestImLivechatCommon):
|
||||
def test_livechat_session_open(self):
|
||||
new_test_user(
|
||||
self.env,
|
||||
login="operator",
|
||||
groups="base.group_user,im_livechat.im_livechat_group_manager",
|
||||
)
|
||||
self.make_jsonrpc_request(
|
||||
"/im_livechat/get_session", {"channel_id": self.livechat_channel.id}
|
||||
)
|
||||
action = self.env.ref("im_livechat.discuss_channel_action_from_livechat_channel")
|
||||
self.start_tour(
|
||||
f"/odoo/livechat/{self.livechat_channel.id}/action-{action.id}", "im_livechat_session_open",
|
||||
login="operator"
|
||||
)
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,82 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from itertools import chain
|
||||
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.tests.common import HttpCase, tagged, warmup
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install", "is_query_count")
|
||||
class TestInboxPerformance(HttpCase, MailCommon):
|
||||
@warmup
|
||||
def test_fetch_with_rating_stats_enabled(self):
|
||||
"""
|
||||
Computation of rating_stats should run a single query per model with rating_stats enabled.
|
||||
"""
|
||||
# Queries (in order):
|
||||
# - search website (get_current_website by domain)
|
||||
# - search website (get_current_website default)
|
||||
# - search website_rewrite (_get_rewrites) sometimes occurs depending on the routing cache
|
||||
# - insert res_device_log
|
||||
# - _xmlid_lookup (_get_public_users)
|
||||
# - fetch website (_get_cached_values)
|
||||
# - get_param ir_config_parameter (_pre_dispatch website_sale)
|
||||
# 4 _message_fetch:
|
||||
# 2 _search_needaction:
|
||||
# - fetch res_users (current user)
|
||||
# - search ir_rule (_get_rules for mail.notification)
|
||||
# - search ir_rule (_get_rules)
|
||||
# - search mail_message
|
||||
# 30 message _to_store:
|
||||
# - search mail_message_schedule
|
||||
# - fetch mail_message
|
||||
# - search mail_followers
|
||||
# 2 thread _to_store:
|
||||
# - fetch slide_channel
|
||||
# - fetch product_template
|
||||
# - search mail_message_res_partner_starred_rel (_compute_starred)
|
||||
# - search message_attachment_rel
|
||||
# - search mail_link_preview
|
||||
# - search mail_message_reaction
|
||||
# - search mail_message_res_partner_rel
|
||||
# - fetch mail_message_subtype
|
||||
# - search mail_notification
|
||||
# 7 _filtered_for_web_client:
|
||||
# - fetch mail_notification
|
||||
# 4 _compute_domain:
|
||||
# - search ir_rule (_get_rules for res.partner)
|
||||
# - search res_groups_users_rel
|
||||
# - search rule_group_rel
|
||||
# - fetch ir_rule
|
||||
# - fetch res_company
|
||||
# - fetch res_partner
|
||||
# 2 _compute_rating_id:
|
||||
# - search rating_rating
|
||||
# - fetch rating_rating
|
||||
# - search mail_tracking_value
|
||||
# 3 _author_to_store:
|
||||
# - fetch res_partner
|
||||
# - search res_users
|
||||
# - fetch res_users
|
||||
# - search ir_rule (_get_rules for rating.rating)
|
||||
# - read group rating_rating (_rating_get_stats_per_record for slide.channel)
|
||||
# - read group rating_rating (_compute_rating_stats for slide.channel)
|
||||
# - read group rating_rating (_rating_get_stats_per_record for product.template)
|
||||
# - read group rating_rating (_compute_rating_stats for product.template)
|
||||
# - get_param ir_config_parameter (_save_session)
|
||||
first_model_records = self.env["product.template"].create(
|
||||
[{"name": "Product A1"}, {"name": "Product A2"}]
|
||||
)
|
||||
second_model_records = self.env["slide.channel"].create(
|
||||
[{"name": "Course B1"}, {"name": "Course B2"}]
|
||||
)
|
||||
for record in chain(first_model_records, second_model_records):
|
||||
record.message_post(
|
||||
body=f"<p>Test message for {record.name}</p>",
|
||||
message_type="comment",
|
||||
partner_ids=[self.user_employee.partner_id.id],
|
||||
rating_value="4",
|
||||
)
|
||||
self.authenticate(self.user_employee.login, self.user_employee.password)
|
||||
with self.assertQueryCount(43):
|
||||
self.make_jsonrpc_request("/mail/inbox/messages")
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.addons.mail.tools.discuss import Store
|
||||
|
||||
|
||||
class TestResPartner(MailCommon):
|
||||
|
||||
def test_portal_user_store_data_access(self):
|
||||
portal_user = mail_new_test_user(self.env, login="portal-user", groups="base.group_portal")
|
||||
Store().add(portal_user.partner_id.with_user(self.user_employee_c2))
|
||||
|
|
@ -14,7 +14,6 @@ pip install odoo-bringout-oca-ocb-test_event_full
|
|||
|
||||
## Dependencies
|
||||
|
||||
This addon depends on:
|
||||
- event
|
||||
- event_booth
|
||||
- event_crm
|
||||
|
|
@ -23,43 +22,18 @@ This addon depends on:
|
|||
- event_sms
|
||||
- payment_demo
|
||||
- website_event_booth_sale_exhibitor
|
||||
- website_event_crm_questions
|
||||
- website_event_exhibitor
|
||||
- website_event_questions
|
||||
- website_event_meet
|
||||
- website_event_sale
|
||||
- website_event_track
|
||||
- website_event_track_live
|
||||
- website_event_track_quiz
|
||||
|
||||
## Manifest Information
|
||||
|
||||
- **Name**: Test Full Event Flow
|
||||
- **Version**: 1.0
|
||||
- **Category**: Hidden/Tests
|
||||
- **License**: LGPL-3
|
||||
- **Installable**: False
|
||||
|
||||
## Source
|
||||
|
||||
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `test_event_full`.
|
||||
- Repository: https://github.com/OCA/OCB
|
||||
- Branch: 19.0
|
||||
- Path: addons/test_event_full
|
||||
|
||||
## License
|
||||
|
||||
This package maintains the original LGPL-3 license from the upstream Odoo project.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Overview: doc/OVERVIEW.md
|
||||
- Architecture: doc/ARCHITECTURE.md
|
||||
- Models: doc/MODELS.md
|
||||
- Controllers: doc/CONTROLLERS.md
|
||||
- Wizards: doc/WIZARDS.md
|
||||
- Reports: doc/REPORTS.md
|
||||
- Security: doc/SECURITY.md
|
||||
- Install: doc/INSTALL.md
|
||||
- Usage: doc/USAGE.md
|
||||
- Configuration: doc/CONFIGURATION.md
|
||||
- Dependencies: doc/DEPENDENCIES.md
|
||||
- Troubleshooting: doc/TROUBLESHOOTING.md
|
||||
- FAQ: doc/FAQ.md
|
||||
This package preserves the original LGPL-3 license.
|
||||
|
|
|
|||
|
|
@ -1,27 +1,26 @@
|
|||
[project]
|
||||
name = "odoo-bringout-oca-ocb-test_event_full"
|
||||
version = "16.0.0"
|
||||
description = "Test Full Event Flow - Odoo addon"
|
||||
description = "Test Full Event Flow -
|
||||
Odoo addon
|
||||
"
|
||||
authors = [
|
||||
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
|
||||
]
|
||||
dependencies = [
|
||||
"odoo-bringout-oca-ocb-event>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-event_booth>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-event_crm>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-event_crm_sale>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-event_sale>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-event_sms>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-payment_demo>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-website_event_booth_sale_exhibitor>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-website_event_crm_questions>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-website_event_exhibitor>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-website_event_questions>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-website_event_meet>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-website_event_sale>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-website_event_track>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-website_event_track_live>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-website_event_track_quiz>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-event>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-event_booth>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-event_crm>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-event_crm_sale>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-event_sale>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-event_sms>=19.0.0",
|
||||
"TODO_MAP-payment_demo>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-website_event_booth_sale_exhibitor>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-website_event_exhibitor>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-website_event_sale>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-website_event_track>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-website_event_track_live>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-website_event_track_quiz>=19.0.0",
|
||||
"requests>=2.25.1"
|
||||
]
|
||||
readme = "README.md"
|
||||
|
|
@ -31,7 +30,7 @@ classifiers = [
|
|||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Office/Business",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -19,10 +19,7 @@ automatic lead generation, full Online support, ...
|
|||
'event_sms',
|
||||
'payment_demo',
|
||||
'website_event_booth_sale_exhibitor',
|
||||
'website_event_crm_questions',
|
||||
'website_event_exhibitor',
|
||||
'website_event_questions',
|
||||
'website_event_meet',
|
||||
'website_event_sale',
|
||||
'website_event_track',
|
||||
'website_event_track_live',
|
||||
|
|
@ -35,8 +32,12 @@ automatic lead generation, full Online support, ...
|
|||
],
|
||||
'assets': {
|
||||
'web.assets_tests': [
|
||||
'test_event_full/static/**/*',
|
||||
'test_event_full/static/src/js/tours/*',
|
||||
],
|
||||
'web.assets_unit_tests': [
|
||||
'test_event_full/static/src/js/tests/*',
|
||||
],
|
||||
},
|
||||
'author': 'Odoo S.A.',
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,122 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo><data noupdate="0">
|
||||
|
||||
<record id="event_booth_category_data_1" model="event.booth.category">
|
||||
<field name="description" type="html"><p>Standard</p></field>
|
||||
<field name="name">Standard</field>
|
||||
<field name="product_id" ref="event_booth_sale.product_product_event_booth"/>
|
||||
</record>
|
||||
<record id="event_booth_category_data_2" model="event.booth.category">
|
||||
<field name="description" type="html"><p>Premium</p></field>
|
||||
<field name="name">Premium</field>
|
||||
<field name="product_id" ref="event_booth_sale.product_product_event_booth"/>
|
||||
<field name="price">90</field>
|
||||
</record>
|
||||
|
||||
<record id="event_type_data_full" model="event.type">
|
||||
<field name="auto_confirm" eval="True"/>
|
||||
<field name="default_timezone">Europe/Paris</field>
|
||||
<field name="event_type_booth_ids" eval="[
|
||||
(5, 0),
|
||||
(0, 0, {'booth_category_id': ref('test_event_full.event_booth_category_data_1'),
|
||||
'name': 'Standard Booth',
|
||||
}
|
||||
),
|
||||
(0, 0, {'booth_category_id': ref('test_event_full.event_booth_category_data_1'),
|
||||
'name': 'Standard Booth 2',
|
||||
}
|
||||
),
|
||||
(0, 0, {'booth_category_id': ref('test_event_full.event_booth_category_data_2'),
|
||||
'name': 'Premium Booth',
|
||||
}
|
||||
),
|
||||
(0, 0, {'booth_category_id': ref('test_event_full.event_booth_category_data_2'),
|
||||
'name': 'Premium Booth 2',
|
||||
}
|
||||
)]"/>
|
||||
<field name="event_type_mail_ids" eval="[
|
||||
(5, 0),
|
||||
(0, 0, {'interval_unit': 'now',
|
||||
'interval_type': 'after_sub',
|
||||
'notification_type': 'mail',
|
||||
'template_ref': 'mail.template,%i' % ref('event.event_subscription'),
|
||||
}
|
||||
),
|
||||
(0, 0, {'interval_nbr': 1,
|
||||
'interval_unit': 'days',
|
||||
'interval_type': 'before_event',
|
||||
'notification_type': 'mail',
|
||||
'template_ref': 'mail.template,%i' % ref('event.event_reminder'),
|
||||
}
|
||||
),
|
||||
(0, 0, {'interval_nbr': 1,
|
||||
'interval_unit': 'days',
|
||||
'interval_type': 'after_event',
|
||||
'notification_type': 'sms',
|
||||
'template_ref': 'sms.template,%i' % ref('event_sms.sms_template_data_event_reminder'),
|
||||
}
|
||||
)]"/>
|
||||
<field name="event_type_ticket_ids" eval="[
|
||||
(5, 0),
|
||||
(0, 0, {'description': 'Ticket1 Description',
|
||||
'name': 'Ticket1',
|
||||
'product_id': ref('event_sale.product_product_event'),
|
||||
'seats_max': 10,
|
||||
}
|
||||
),
|
||||
(0, 0, {'description': 'Ticket2 Description',
|
||||
'name': 'Ticket2',
|
||||
'product_id': ref('event_sale.product_product_event'),
|
||||
'price': 45,
|
||||
}
|
||||
)]"/>
|
||||
<field name="has_seats_limitation" eval="True"/>
|
||||
<field name="name">Test Type</field>
|
||||
<field name="note" type="html"><p>Template note</p></field>
|
||||
<field name="question_ids" eval="[(5, 0)]"/>
|
||||
<field name="seats_max">30</field>
|
||||
<field name="tag_ids" eval="[(5, 0)]"/>
|
||||
<field name="ticket_instructions" type="html"><p>Ticket Instructions</p></field>
|
||||
<field name="website_menu" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="event_question_type_full_1" model="event.question">
|
||||
<field name="question_type">simple_choice</field>
|
||||
<field name="once_per_order" eval="False"/>
|
||||
<field name="event_type_id" ref="test_event_full.event_type_data_full"/>
|
||||
<field name="title">Question1</field>
|
||||
</record>
|
||||
<record id="event_question_type_full_1_answer_1" model="event.question.answer">
|
||||
<field name="name">Q1-Answer1</field>
|
||||
<field name="sequence">1</field>
|
||||
<field name="question_id" ref="test_event_full.event_question_type_full_1"/>
|
||||
</record>
|
||||
<record id="event_question_type_full_1_answer_2" model="event.question.answer">
|
||||
<field name="name">Q1-Answer2</field>
|
||||
<field name="sequence">2</field>
|
||||
<field name="question_id" ref="test_event_full.event_question_type_full_1"/>
|
||||
</record>
|
||||
<record id="event_question_type_full_2" model="event.question">
|
||||
<field name="question_type">simple_choice</field>
|
||||
<field name="once_per_order" eval="False"/>
|
||||
<field name="event_type_id" ref="test_event_full.event_type_data_full"/>
|
||||
<field name="title">Question2</field>
|
||||
</record>
|
||||
<record id="event_question_type_full_2_answer_1" model="event.question.answer">
|
||||
<field name="name">Q2-Answer1</field>
|
||||
<field name="sequence">1</field>
|
||||
<field name="question_id" ref="test_event_full.event_question_type_full_2"/>
|
||||
</record>
|
||||
<record id="event_question_type_full_2_answer_2" model="event.question.answer">
|
||||
<field name="name">Q2-Answer2</field>
|
||||
<field name="sequence">2</field>
|
||||
<field name="question_id" ref="test_event_full.event_question_type_full_2"/>
|
||||
</record>
|
||||
<record id="event_question_type_full_3" model="event.question">
|
||||
<field name="question_type">text_box</field>
|
||||
<field name="once_per_order" eval="True"/>
|
||||
<field name="event_type_id" ref="test_event_full.event_type_data_full"/>
|
||||
<field name="title">Question3</field>
|
||||
</record>
|
||||
|
||||
</data></odoo>
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
import { defineMailModels } from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { click, select } from "@odoo/hoot-dom";
|
||||
import { animationFrame } from "@odoo/hoot-mock";
|
||||
import { defineModels, fields, models, mountView, onRpc } from "@web/../tests/web_test_helpers";
|
||||
|
||||
class EventMail extends models.Model {
|
||||
_name = "event.mail";
|
||||
|
||||
template_ref = fields.Reference({
|
||||
selection: [
|
||||
["mail.template", "Mail Template"],
|
||||
["sms.template", "SMS Template"],
|
||||
["some.template", "Some Template"],
|
||||
],
|
||||
});
|
||||
|
||||
_records = [
|
||||
{
|
||||
id: 1,
|
||||
template_ref: "mail.template,1",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
template_ref: "sms.template,1",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
template_ref: "some.template,1",
|
||||
},
|
||||
];
|
||||
}
|
||||
class MailTemplate extends models.Model {
|
||||
_name = "mail.template";
|
||||
|
||||
name = fields.Char();
|
||||
|
||||
_records = [{ id: 1, name: "Mail Template 1" }];
|
||||
}
|
||||
class SmsTemplate extends models.Model {
|
||||
_name = "sms.template";
|
||||
|
||||
name = fields.Char();
|
||||
|
||||
_records = [{ id: 1, name: "SMS template 1" }];
|
||||
}
|
||||
class SomeTemplate extends models.Model {
|
||||
_name = "some.template";
|
||||
|
||||
name = fields.Char();
|
||||
|
||||
_records = [{ id: 1, name: "Some Template 1" }];
|
||||
}
|
||||
defineMailModels();
|
||||
defineModels([EventMail, MailTemplate, SmsTemplate, SomeTemplate]);
|
||||
|
||||
describe.current.tags("desktop");
|
||||
|
||||
test("Reference field displays right icons", async () => {
|
||||
// bypass list controller check
|
||||
onRpc("has_group", () => true);
|
||||
|
||||
await mountView({
|
||||
type: "list",
|
||||
resModel: "event.mail",
|
||||
arch: `
|
||||
<list editable="top">
|
||||
<field name="template_ref" widget="EventMailTemplateReferenceField"/>
|
||||
</list>`,
|
||||
});
|
||||
|
||||
// each field cell will be the field of a different record (1 field/line)
|
||||
expect(".o_field_cell").toHaveCount(3);
|
||||
expect(".o_field_cell.o_EventMailTemplateReferenceField_cell").toHaveCount(3);
|
||||
expect(".o_field_cell:eq(0) .fa-envelope").toHaveCount(1);
|
||||
expect(".o_field_cell:eq(1) .fa-mobile").toHaveCount(1);
|
||||
expect(".o_field_cell:eq(2) .fa-envelope").toHaveCount(0);
|
||||
expect(".o_field_cell:eq(2) .fa-mobile").toHaveCount(0);
|
||||
|
||||
// select a sms.template instead of mail.template
|
||||
|
||||
await click(".o_field_cell:eq(0)");
|
||||
await animationFrame();
|
||||
await click(".o_field_cell:eq(0) select.o_input");
|
||||
await select("sms.template");
|
||||
await animationFrame();
|
||||
await click(".o_field_cell:eq(0) .o_field_many2one_selection input");
|
||||
await animationFrame();
|
||||
await click(".o_field_cell:eq(0) .o-autocomplete--dropdown-item");
|
||||
// click out
|
||||
await click(".o_list_renderer");
|
||||
await animationFrame();
|
||||
|
||||
expect(".o_field_cell:eq(0) .fa-mobile").toHaveCount(1);
|
||||
expect(".o_field_cell:eq(0) .fa-envelope").toHaveCount(0);
|
||||
|
||||
// select a some other model to check it has no icon
|
||||
|
||||
await click(".o_field_cell:eq(0)");
|
||||
await animationFrame();
|
||||
await click(".o_field_cell:eq(0) select.o_input");
|
||||
await select("some.template");
|
||||
await animationFrame();
|
||||
await click(".o_field_cell:eq(0) .o_field_many2one_selection input");
|
||||
await animationFrame();
|
||||
await click(".o_field_cell:eq(0) .o-autocomplete--dropdown-item");
|
||||
await click(".o_list_renderer");
|
||||
await animationFrame();
|
||||
|
||||
expect(".o_field_cell:eq(0) .fa-mobile").toHaveCount(0);
|
||||
expect(".o_field_cell:eq(0) .fa-envelope").toHaveCount(0);
|
||||
|
||||
// select no record for the model
|
||||
|
||||
await click(".o_field_cell:eq(1)");
|
||||
await animationFrame();
|
||||
await click(".o_field_cell:eq(1) select.o_input");
|
||||
await select("mail.template");
|
||||
await click(".o_list_renderer");
|
||||
await animationFrame();
|
||||
|
||||
expect(".o_field_cell:eq(1) .fa-mobile").toHaveCount(0);
|
||||
expect(".o_field_cell:eq(1) .fa-envelope").toHaveCount(0);
|
||||
});
|
||||
|
|
@ -1,85 +1,76 @@
|
|||
odoo.define('test_event_full.tour.performance', function (require) {
|
||||
"use strict";
|
||||
|
||||
var tour = require('web_tour.tour');
|
||||
import { registry } from "@web/core/registry";
|
||||
import * as wsTourUtils from '@website_sale/js/tours/tour_utils';
|
||||
|
||||
var registerSteps = [{
|
||||
content: "Select 2 units of 'Ticket1' ticket type",
|
||||
trigger: '#o_wevent_tickets_collapse .row.o_wevent_ticket_selector[name="Ticket1"] select',
|
||||
run: 'text 2',
|
||||
content: "Open ticket modal",
|
||||
trigger: 'button.btn-primary:contains("Register")',
|
||||
run: "click",
|
||||
}, {
|
||||
content: "Select 1 unit of 'Ticket2' ticket type",
|
||||
trigger: '#o_wevent_tickets_collapse .row.o_wevent_ticket_selector[name="Ticket2"] select',
|
||||
run: 'text 1',
|
||||
content: "Add 2 units of 'Ticket1' ticket type by clicking the '+' button",
|
||||
trigger: 'button[data-increment-type*="plus"]',
|
||||
run: "dblclick",
|
||||
}, {
|
||||
content: "Edit 1 unit of 'Ticket2' ticket type",
|
||||
trigger: '.modal input:eq(2)',
|
||||
run: "edit 1",
|
||||
}, {
|
||||
content: "Click on 'Register' button",
|
||||
trigger: '#o_wevent_tickets .btn-primary:contains("Register"):not(:disabled)',
|
||||
run: 'click',
|
||||
}, {
|
||||
content: "Fill attendees details",
|
||||
trigger: 'form[id="attendee_registration"] .btn:contains("Continue")',
|
||||
run: function () {
|
||||
$("input[name='1-name']").val("Raoulette Poiluchette");
|
||||
$("input[name='1-phone']").val("0456112233");
|
||||
$("input[name='1-email']").val("raoulette@example.com");
|
||||
$("div[name*='Question1'] select[name*='question_answer-1']").val($("select[name*='question_answer-1'] option:contains('Q1-Answer2')").val());
|
||||
$("div[name*='Question2'] select[name*='question_answer-1']").val($("select[name*='question_answer-1'] option:contains('Q2-Answer1')").val());
|
||||
$("input[name='2-name']").val("Michel Tractopelle");
|
||||
$("input[name='2-phone']").val("0456332211");
|
||||
$("input[name='2-email']").val("michel@example.com");
|
||||
$("div[name*='Question1'] select[name*='question_answer-2']").val($("select[name*='question_answer-2'] option:contains('Q1-Answer1')").val());
|
||||
$("div[name*='Question2'] select[name*='question_answer-2']").val($("select[name*='question_answer-2'] option:contains('Q2-Answer2')").val());
|
||||
$("input[name='3-name']").val("Hubert Boitaclous");
|
||||
$("input[name='3-phone']").val("0456995511");
|
||||
$("input[name='3-email']").val("hubert@example.com");
|
||||
$("div[name*='Question1'] select[name*='question_answer-3']").val($("select[name*='question_answer-3'] option:contains('Q1-Answer2')").val());
|
||||
$("div[name*='Question2'] select[name*='question_answer-3']").val($("select[name*='question_answer-3'] option:contains('Q2-Answer2')").val());
|
||||
$("textarea[name*='question_answer']").text("Random answer from random guy");
|
||||
},
|
||||
content: "Choose the 'Q1-Answer2' answer of the 'Question1' for the first ticket",
|
||||
trigger: 'select[name*="1-simple_choice"]',
|
||||
run: "selectByIndex 2",
|
||||
}, {
|
||||
content: "Choose the 'Q2-Answer1' answer of the 'Question2' for the first ticket",
|
||||
trigger: 'select[name*="1-simple_choice"]:last',
|
||||
run: "selectByIndex 1",
|
||||
}, {
|
||||
content: "Choose the 'Q1-Answer1' answer of the 'Question1' for the second ticket",
|
||||
trigger: 'select[name*="2-simple_choice"]',
|
||||
run: "selectByIndex 1",
|
||||
}, {
|
||||
content: "Choose the 'Q2-Answer2' answer of the 'Question2' for the second ticket",
|
||||
trigger: 'select[name*="2-simple_choice"]:last',
|
||||
run: "selectByIndex 2",
|
||||
}, {
|
||||
content: "Choose the 'Q1-Answer2' answer of the 'Question1' for the third ticket",
|
||||
trigger: 'select[name*="3-simple_choice"]',
|
||||
run: "selectByIndex 2",
|
||||
}, {
|
||||
content: "Choose the 'Q2-Answer2' answer of the 'Question2' for the third ticket",
|
||||
trigger: 'select[name*="3-simple_choice"]:last',
|
||||
run: "selectByIndex 2",
|
||||
}, {
|
||||
content: "Fill the text content of the 'Question3' for the third ticket",
|
||||
trigger: 'textarea[name*="text_box"]',
|
||||
run: "edit Random answer from random guy",
|
||||
}, {
|
||||
content: "Validate attendees details",
|
||||
extra_trigger: "input[name='1-name'], input[name='2-name'], input[name='3-name']",
|
||||
trigger: 'button:contains("Continue")',
|
||||
trigger: 'button[type=submit]:last',
|
||||
run: 'click',
|
||||
}, {
|
||||
content: "Address filling",
|
||||
trigger: 'select[name="country_id"]',
|
||||
run: function () {
|
||||
$('input[name="name"]').val('Raoulette Poiluchette');
|
||||
$('input[name="phone"]').val('0456112233');
|
||||
$('input[name="email"]').val('raoulette@example.com');
|
||||
$('input[name="street"]').val('Cheesy Crust Street, 42');
|
||||
$('input[name="city"]').val('CheeseCity');
|
||||
$('input[name="zip"]').val('8888');
|
||||
$('#country_id option:eq(1)').attr('selected', true);
|
||||
},
|
||||
}, {
|
||||
content: "Next",
|
||||
trigger: '.oe_cart .btn:contains("Next")',
|
||||
}, {
|
||||
content: 'Select Test payment provider',
|
||||
trigger: '.o_payment_option_card:contains("Demo")'
|
||||
}, {
|
||||
content: 'Add card number',
|
||||
trigger: 'input[name="customer_input"]',
|
||||
run: 'text 4242424242424242'
|
||||
}, {
|
||||
content: "Pay now",
|
||||
extra_trigger: "#cart_products:contains(Ticket1):contains(Ticket2)",
|
||||
trigger: 'button:contains(Pay Now)',
|
||||
run: 'click',
|
||||
}, {
|
||||
content: 'Payment is successful',
|
||||
trigger: '.oe_website_sale_tx_status:contains("Your payment has been successfully processed.")',
|
||||
run: function () {}
|
||||
}];
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
...wsTourUtils.fillAdressForm({
|
||||
name: "Raoulette Poiluchette",
|
||||
phone: "0456112233",
|
||||
email: "raoulette@example.com",
|
||||
street: "Cheesy Crust Street, 42",
|
||||
city: "CheeseCity",
|
||||
zip: "8888",
|
||||
}),
|
||||
{
|
||||
content: "Confirm address",
|
||||
trigger: 'a[name="website_sale_main_button"]',
|
||||
run: "click",
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
...wsTourUtils.payWithDemo(),
|
||||
];
|
||||
|
||||
|
||||
tour.register('wevent_performance_register', {
|
||||
test: true
|
||||
}, [].concat(
|
||||
registry.category("web_tour.tours").add('wevent_performance_register', {
|
||||
steps: () => [].concat(
|
||||
registerSteps,
|
||||
)
|
||||
);
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,18 +1,57 @@
|
|||
odoo.define('test_event_full.tour.register', function (require) {
|
||||
"use strict";
|
||||
|
||||
var tour = require('web_tour.tour');
|
||||
import { registry } from "@web/core/registry";
|
||||
import { session } from "@web/session";
|
||||
|
||||
/**
|
||||
* TALKS STEPS
|
||||
*/
|
||||
const reminderToggleSteps = function (talkName, reminderOn, toggleReminder) {
|
||||
let steps = [];
|
||||
if (reminderOn) {
|
||||
steps = steps.concat([{
|
||||
content: `Check Favorite for ${talkName} was already on`,
|
||||
trigger: "div.o_wetrack_js_reminder i.fa-bell",
|
||||
}]);
|
||||
}
|
||||
else {
|
||||
steps = steps.concat([{
|
||||
content: `Check Favorite for ${talkName} was off`,
|
||||
trigger: "div.o_wetrack_js_reminder i.fa-bell-o",
|
||||
}]);
|
||||
if (toggleReminder) {
|
||||
steps = steps.concat([{
|
||||
content: "Set Favorite",
|
||||
trigger: "i[title='Set Favorite']",
|
||||
run: "click",
|
||||
}]);
|
||||
if (session.is_public){
|
||||
steps = steps.concat([{
|
||||
content: "The form of the email reminder modal is filled",
|
||||
trigger: "#o_wetrack_email_reminder_form input[name='email']",
|
||||
run: "fill visitor@odoo.com",
|
||||
},
|
||||
{
|
||||
content: "The form is submit",
|
||||
trigger: "#o_wetrack_email_reminder_form button[type='submit']",
|
||||
run: "click",
|
||||
}]);
|
||||
}
|
||||
steps = steps.concat([{
|
||||
content: `Check Favorite for ${talkName} is now on`,
|
||||
trigger: "div.o_wetrack_js_reminder i.fa-bell",
|
||||
}]);
|
||||
}
|
||||
}
|
||||
return steps;
|
||||
};
|
||||
|
||||
var discoverTalkSteps = function (talkName, fromList, reminderOn, toggleReminder) {
|
||||
const discoverTalkSteps = function (talkName, fromList, checkToggleReminder, reminderOn, toggleReminder) {
|
||||
var steps;
|
||||
if (fromList) {
|
||||
steps = [{
|
||||
content: 'Go on "' + talkName + '" talk in List',
|
||||
trigger: 'a:contains("' + talkName + '")',
|
||||
run: "click",
|
||||
expectUnloadPage: true,
|
||||
}];
|
||||
}
|
||||
else {
|
||||
|
|
@ -20,109 +59,98 @@ var discoverTalkSteps = function (talkName, fromList, reminderOn, toggleReminder
|
|||
content: 'Click on Live Track',
|
||||
trigger: 'article span:contains("' + talkName + '")',
|
||||
run: 'click',
|
||||
expectUnloadPage: true,
|
||||
}];
|
||||
}
|
||||
steps = steps.concat([{
|
||||
content: `Check we are on the "${talkName}" talk page`,
|
||||
trigger: 'div.o_wesession_track_main',
|
||||
run: function () {}, // it's a check
|
||||
}]);
|
||||
|
||||
if (reminderOn) {
|
||||
steps = steps.concat([{
|
||||
content: `Check Favorite for ${talkName} was already on`,
|
||||
trigger: 'div.o_wetrack_js_reminder i.fa-bell',
|
||||
extra_trigger: 'span.o_wetrack_js_reminder_text:contains("Favorite On")',
|
||||
run: function () {}, // it's a check
|
||||
}]);
|
||||
}
|
||||
else {
|
||||
steps = steps.concat([{
|
||||
content: `Check Favorite for ${talkName} was off`,
|
||||
trigger: 'span.o_wetrack_js_reminder_text:contains("Set Favorite")',
|
||||
run: function () {}, // it's a check
|
||||
}]);
|
||||
if (toggleReminder) {
|
||||
steps = steps.concat([{
|
||||
content: "Set Favorite",
|
||||
trigger: 'span.o_wetrack_js_reminder_text',
|
||||
run: 'click',
|
||||
}, {
|
||||
content: `Check Favorite for ${talkName} is now on`,
|
||||
trigger: 'div.o_wetrack_js_reminder i.fa-bell',
|
||||
extra_trigger: 'span.o_wetrack_js_reminder_text:contains("Favorite On")',
|
||||
run: function () {}, // it's a check
|
||||
}]);
|
||||
}
|
||||
if (checkToggleReminder){
|
||||
steps = steps.concat(reminderToggleSteps(talkName, reminderOn, toggleReminder));
|
||||
}
|
||||
return steps;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* ROOMS STEPS
|
||||
*/
|
||||
|
||||
var discoverRoomSteps = function (roomName) {
|
||||
var steps = [{
|
||||
content: 'Go on "' + roomName + '" room in List',
|
||||
trigger: 'a.o_wevent_meeting_room_card h4:contains("' + roomName + '")',
|
||||
run: function() {
|
||||
// can't click on it, it will try to launch Jitsi and fail on chrome headless
|
||||
},
|
||||
}];
|
||||
return steps;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* REGISTER STEPS
|
||||
*/
|
||||
|
||||
var registerSteps = [{
|
||||
content: 'Go on Register',
|
||||
trigger: 'a.btn-primary:contains("Register")',
|
||||
}, {
|
||||
content: "Select 2 units of 'Standard' ticket type",
|
||||
trigger: '#o_wevent_tickets_collapse .row:has(.o_wevent_registration_multi_select:contains("Free")) select',
|
||||
run: 'text 2',
|
||||
}, {
|
||||
content: "Click on 'Register' button",
|
||||
trigger: '#o_wevent_tickets .btn-primary:contains("Register"):not(:disabled)',
|
||||
run: 'click',
|
||||
}, {
|
||||
content: "Fill attendees details",
|
||||
trigger: 'form[id="attendee_registration"] .btn:contains("Continue")',
|
||||
run: function () {
|
||||
$("input[name='1-name']").val("Raoulette Poiluchette");
|
||||
$("input[name='1-phone']").val("0456112233");
|
||||
$("input[name='1-email']").val("raoulette@example.com");
|
||||
$("select[name*='question_answer-1']").val($("select[name*='question_answer-1'] option:contains('Consumers')").val());
|
||||
$("input[name='2-name']").val("Michel Tractopelle");
|
||||
$("input[name='2-phone']").val("0456332211");
|
||||
$("input[name='2-email']").val("michel@example.com");
|
||||
$("select[name*='question_answer-2']").val($("select[name*='question_answer-1'] option:contains('Research')").val());
|
||||
$("textarea[name*='question_answer']").text("An unicorn told me about you. I ate it afterwards.");
|
||||
const registerSteps = [
|
||||
{
|
||||
content: "Open ticket modal",
|
||||
trigger: "button.btn-primary:contains(Register):enabled",
|
||||
run: "click",
|
||||
},
|
||||
}, {
|
||||
content: "Validate attendees details",
|
||||
extra_trigger: "input[name='1-name'], input[name='2-name'], input[name='3-name']",
|
||||
trigger: 'button:contains("Continue")',
|
||||
run: 'click',
|
||||
}, {
|
||||
trigger: 'div.o_wereg_confirmed_attendees span:contains("Raoulette Poiluchette")',
|
||||
run: function () {} // check
|
||||
}, {
|
||||
trigger: 'div.o_wereg_confirmed_attendees span:contains("Michel Tractopelle")',
|
||||
run: function () {} // check
|
||||
}, {
|
||||
content: "Click on 'register favorites talks' button",
|
||||
trigger: 'a:contains("register to your favorites talks now")',
|
||||
run: 'click',
|
||||
}, {
|
||||
trigger: 'h1:contains("Book your talks")',
|
||||
run: function() {},
|
||||
}];
|
||||
{
|
||||
content: "Edit 2 units of 'Standard' ticket type",
|
||||
trigger: ".modal .o_wevent_ticket_selector input",
|
||||
run: "edit 2",
|
||||
},
|
||||
{
|
||||
content: "Click on 'Register' button",
|
||||
trigger: ".modal #o_wevent_tickets .btn-primary:contains(Register):enabled",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Wait the modal is shown before continue",
|
||||
trigger: ".modal.modal_shown.show form[id=attendee_registration]",
|
||||
},
|
||||
{
|
||||
trigger: ".modal input[name*='1-name']",
|
||||
run: "edit Raoulette Poiluchette",
|
||||
},
|
||||
{
|
||||
trigger: ".modal input[name*='1-phone']",
|
||||
run: "edit 0456112233",
|
||||
},
|
||||
{
|
||||
trigger: ".modal input[name*='1-email']",
|
||||
run: "edit raoulette@example.com",
|
||||
},
|
||||
{
|
||||
trigger: ".modal select[name*='1-simple_choice']",
|
||||
run: "selectByLabel Consumers",
|
||||
},
|
||||
{
|
||||
trigger: ".modal input[name*='2-name']",
|
||||
run: "edit Michel Tractopelle",
|
||||
},
|
||||
{
|
||||
trigger: ".modal input[name*='2-phone']",
|
||||
run: "edit 0456332211",
|
||||
},
|
||||
{
|
||||
trigger: ".modal input[name*='2-email']",
|
||||
run: "edit michel@example.com",
|
||||
},
|
||||
{
|
||||
trigger: ".modal select[name*='2-simple_choice']",
|
||||
run: "selectByLabel Research",
|
||||
},
|
||||
{
|
||||
trigger: ".modal textarea[name*='text_box']",
|
||||
run: "edit An unicorn told me about you. I ate it afterwards.",
|
||||
},
|
||||
{
|
||||
trigger: ".modal input[name*='1-name'], input[name*='2-name'], input[name*='3-name']",
|
||||
},
|
||||
{
|
||||
content: "Validate attendees details",
|
||||
trigger: ".modal button[type=submit]:enabled",
|
||||
run: "click",
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
{
|
||||
content: "Click on 'register favorites talks' button",
|
||||
trigger: "a:contains(register to your favorites talks now)",
|
||||
run: "click",
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
{
|
||||
trigger: "h5:contains(Book your talks)",
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* MAIN STEPS
|
||||
|
|
@ -132,43 +160,46 @@ var initTourSteps = function (eventName) {
|
|||
return [{
|
||||
content: 'Go on "' + eventName + '" page',
|
||||
trigger: 'a[href*="/event"]:contains("' + eventName + '"):first',
|
||||
run: "click",
|
||||
expectUnloadPage: true,
|
||||
}];
|
||||
};
|
||||
|
||||
var browseTalksSteps = [{
|
||||
content: 'Browse Talks',
|
||||
trigger: 'a:contains("Talks")',
|
||||
content: 'Browse Talks Menu',
|
||||
trigger: 'a[href*="#"]:contains("Talks")',
|
||||
run: "click",
|
||||
}, {
|
||||
content: 'Browse Talks Submenu',
|
||||
trigger: 'a.dropdown-item span:contains("Talks")',
|
||||
run: "click",
|
||||
expectUnloadPage: true,
|
||||
}, {
|
||||
content: 'Check we are on the talk list page',
|
||||
trigger: 'h1:contains("Book your talks")',
|
||||
run: function () {} // check
|
||||
trigger: 'h5:contains("Book your talks")',
|
||||
}];
|
||||
|
||||
var browseMeetSteps = [{
|
||||
content: 'Browse Meet',
|
||||
trigger: 'a:contains("Community")',
|
||||
var browseBackSteps = [{
|
||||
content: 'Browse Back',
|
||||
trigger: 'a:contains("All Talks")',
|
||||
run: "click",
|
||||
expectUnloadPage: true,
|
||||
}, {
|
||||
content: 'Check we are on the community page',
|
||||
trigger: 'span:contains("Join a room")',
|
||||
run: function () {} // check
|
||||
content: 'Check we are back on the talk list page',
|
||||
trigger: 'h5:contains("Book your talks")',
|
||||
}];
|
||||
|
||||
|
||||
tour.register('wevent_register', {
|
||||
registry.category("web_tour.tours").add('wevent_register', {
|
||||
url: '/event',
|
||||
test: true
|
||||
}, [].concat(
|
||||
steps: () => [].concat(
|
||||
initTourSteps('Online Reveal'),
|
||||
browseTalksSteps,
|
||||
discoverTalkSteps('What This Event Is All About', true, true),
|
||||
browseTalksSteps,
|
||||
discoverTalkSteps('Live Testimonial', false, false, false),
|
||||
browseTalksSteps,
|
||||
discoverTalkSteps('Our Last Day Together !', true, false, true),
|
||||
browseMeetSteps,
|
||||
discoverRoomSteps('Best wood for furniture'),
|
||||
discoverTalkSteps('What This Event Is All About', true, true, true),
|
||||
browseBackSteps,
|
||||
discoverTalkSteps('Live Testimonial', false, false, false, false),
|
||||
browseBackSteps,
|
||||
discoverTalkSteps('Our Last Day Together!', true, true, false, true),
|
||||
browseBackSteps,
|
||||
registerSteps,
|
||||
)
|
||||
);
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,5 +7,5 @@ from . import test_event_event
|
|||
from . import test_event_mail
|
||||
from . import test_event_security
|
||||
from . import test_performance
|
||||
from . import test_wevent_menu
|
||||
from . import test_wevent_register
|
||||
from . import test_event_discount
|
||||
|
|
|
|||
|
|
@ -3,10 +3,14 @@
|
|||
|
||||
from datetime import datetime, timedelta, time
|
||||
|
||||
from odoo import Command
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserDemo, HttpCaseWithUserPortal
|
||||
from odoo.addons.base.tests.test_ir_cron import CronMixinCase
|
||||
from odoo.addons.event.tests.common import EventCase
|
||||
from odoo.addons.event_crm.tests.common import EventCrmCase
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user, MailCase
|
||||
from odoo.addons.sales_team.tests.common import TestSalesCommon
|
||||
from odoo.addons.sms.tests.common import SMSCase
|
||||
from odoo.addons.website.tests.test_website_visitor import MockVisitor
|
||||
|
||||
|
||||
|
|
@ -15,7 +19,6 @@ class TestEventFullCommon(EventCrmCase, TestSalesCommon, MockVisitor):
|
|||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestEventFullCommon, cls).setUpClass()
|
||||
cls._init_mail_gateway()
|
||||
|
||||
# Context data: dates
|
||||
# ------------------------------------------------------------
|
||||
|
|
@ -37,6 +40,7 @@ class TestEventFullCommon(EventCrmCase, TestSalesCommon, MockVisitor):
|
|||
# set country in order to format Belgian numbers
|
||||
cls.company_admin.write({
|
||||
'country_id': cls.env.ref('base.be').id,
|
||||
'email': 'info@yourcompany.com',
|
||||
})
|
||||
cls.event_user = mail_new_test_user(
|
||||
cls.env,
|
||||
|
|
@ -55,7 +59,6 @@ class TestEventFullCommon(EventCrmCase, TestSalesCommon, MockVisitor):
|
|||
'country_id': cls.env.ref('base.be').id,
|
||||
'email': 'customer.test@example.com',
|
||||
'name': 'Test Customer',
|
||||
'mobile': '0456123456',
|
||||
'phone': '0456123456',
|
||||
})
|
||||
# make a SO for a customer, selling some tickets
|
||||
|
|
@ -68,14 +71,16 @@ class TestEventFullCommon(EventCrmCase, TestSalesCommon, MockVisitor):
|
|||
|
||||
cls.ticket_product = cls.env['product.product'].create({
|
||||
'description_sale': 'Ticket Product Description',
|
||||
'detailed_type': 'event',
|
||||
'type': 'service',
|
||||
'service_tracking': 'event',
|
||||
'list_price': 10,
|
||||
'name': 'Test Registration Product',
|
||||
'standard_price': 30.0,
|
||||
})
|
||||
cls.booth_product = cls.env['product.product'].create({
|
||||
'description_sale': 'Booth Product Description',
|
||||
'detailed_type': 'event_booth',
|
||||
'type': 'service',
|
||||
'service_tracking': 'event_booth',
|
||||
'list_price': 20,
|
||||
'name': 'Test Booth Product',
|
||||
'standard_price': 60.0,
|
||||
|
|
@ -120,9 +125,8 @@ class TestEventFullCommon(EventCrmCase, TestSalesCommon, MockVisitor):
|
|||
# ------------------------------------------------------------
|
||||
test_registration_report = cls.env.ref('test_event_full.event_registration_report_test')
|
||||
subscription_template = cls.env.ref('event.event_subscription')
|
||||
subscription_template.write({'report_template': test_registration_report.id})
|
||||
subscription_template.write({'report_template_ids': [(6, 0, test_registration_report.ids)]})
|
||||
cls.test_event_type = cls.env['event.type'].create({
|
||||
'auto_confirm': True,
|
||||
'default_timezone': 'Europe/Paris',
|
||||
'event_type_booth_ids': [
|
||||
(0, 0, {'booth_category_id': cls.event_booth_categories[0].id,
|
||||
|
|
@ -145,21 +149,18 @@ class TestEventFullCommon(EventCrmCase, TestSalesCommon, MockVisitor):
|
|||
'event_type_mail_ids': [
|
||||
(0, 0, {'interval_unit': 'now', # right at subscription
|
||||
'interval_type': 'after_sub',
|
||||
'notification_type': 'mail',
|
||||
'template_ref': 'mail.template,%i' % subscription_template.id,
|
||||
}
|
||||
),
|
||||
(0, 0, {'interval_nbr': 1, # 1 days before event
|
||||
'interval_unit': 'days',
|
||||
'interval_type': 'before_event',
|
||||
'notification_type': 'mail',
|
||||
'template_ref': 'mail.template,%i' % cls.env['ir.model.data']._xmlid_to_res_id('event.event_reminder'),
|
||||
}
|
||||
),
|
||||
(0, 0, {'interval_nbr': 1, # 1 days after event
|
||||
'interval_unit': 'days',
|
||||
'interval_type': 'after_event',
|
||||
'notification_type': 'sms',
|
||||
'template_ref': 'sms.template,%i' % cls.env['ir.model.data']._xmlid_to_res_id('event_sms.sms_template_data_event_reminder'),
|
||||
}
|
||||
),
|
||||
|
|
@ -228,15 +229,15 @@ class TestEventFullCommon(EventCrmCase, TestSalesCommon, MockVisitor):
|
|||
'is_published': True,
|
||||
}
|
||||
|
||||
cls.test_event = cls.env['event.event'].create({
|
||||
'name': 'Test Event',
|
||||
'auto_confirm': True,
|
||||
'date_begin': datetime.now() + timedelta(days=1),
|
||||
'date_end': datetime.now() + timedelta(days=5),
|
||||
'date_tz': 'Europe/Brussels',
|
||||
'event_type_id': cls.test_event_type.id,
|
||||
'is_published': True,
|
||||
})
|
||||
with cls.mock_datetime_and_now(cls, cls.reference_now):
|
||||
cls.test_event = cls.env['event.event'].create({
|
||||
'name': 'Test Event',
|
||||
'date_begin': datetime.now() + timedelta(days=1),
|
||||
'date_end': datetime.now() + timedelta(days=5),
|
||||
'date_tz': 'Europe/Brussels',
|
||||
'event_type_id': cls.test_event_type.id,
|
||||
'is_published': True,
|
||||
})
|
||||
# update post-synchronize data
|
||||
ticket_1 = cls.test_event.event_ticket_ids.filtered(lambda t: t.name == 'Ticket1')
|
||||
ticket_2 = cls.test_event.event_ticket_ids.filtered(lambda t: t.name == 'Ticket2')
|
||||
|
|
@ -251,39 +252,36 @@ class TestEventFullCommon(EventCrmCase, TestSalesCommon, MockVisitor):
|
|||
], limit=1)
|
||||
|
||||
cls.customer_data = [
|
||||
{'email': 'customer.email.%02d@test.example.com' % x,
|
||||
'name': 'My Customer %02d' % x,
|
||||
'mobile': '04569999%02d' % x,
|
||||
{'email': f'customer.email.{idx:02d}@test.example.com',
|
||||
'name': f'My Customer {idx:02d}',
|
||||
'partner_id': False,
|
||||
'phone': '04560000%02d' % x,
|
||||
} for x in range(0, 10)
|
||||
'phone': f'04560000{idx:02d}',
|
||||
} for idx in range(0, 10)
|
||||
]
|
||||
cls.website_customer_data = [
|
||||
{'email': 'website.email.%02d@test.example.com' % x,
|
||||
'name': 'My Customer %02d' % x,
|
||||
'mobile': '04569999%02d' % x,
|
||||
{'email': f'website.email.{idx:02d}@test.example.com',
|
||||
'name': f'My Customer {idx:02d}',
|
||||
'partner_id': cls.env.ref('base.public_partner').id,
|
||||
'phone': '04560000%02d' % x,
|
||||
'phone': f'04560000{idx:02d}',
|
||||
'registration_answer_ids': [
|
||||
(0, 0, {
|
||||
'question_id': cls.test_event.question_ids[0].id,
|
||||
'value_answer_id': cls.test_event.question_ids[0].answer_ids[(x % 2)].id,
|
||||
'value_answer_id': cls.test_event.question_ids[0].answer_ids[(idx % 2)].id,
|
||||
}), (0, 0, {
|
||||
'question_id': cls.test_event.question_ids[1].id,
|
||||
'value_answer_id': cls.test_event.question_ids[1].answer_ids[(x % 2)].id,
|
||||
'value_answer_id': cls.test_event.question_ids[1].answer_ids[(idx % 2)].id,
|
||||
}), (0, 0, {
|
||||
'question_id': cls.test_event.question_ids[2].id,
|
||||
'value_text_box': 'CustomerAnswer%s' % x,
|
||||
'value_text_box': f'CustomerAnswer{idx}',
|
||||
})
|
||||
],
|
||||
} for x in range(0, 10)
|
||||
} for idx in range(0, 10)
|
||||
]
|
||||
cls.partners = cls.env['res.partner'].create([
|
||||
{'email': 'partner.email.%02d@test.example.com' % x,
|
||||
'name': 'PartnerCustomer',
|
||||
'mobile': '04569999%02d' % x,
|
||||
'phone': '04560000%02d' % x,
|
||||
} for x in range(0, 10)
|
||||
{'email': f'partner.email.{idx:02d}@test.example.com',
|
||||
'name': f'PartnerCustomer {idx:02d}',
|
||||
'phone': f'04560000{idx:02d}',
|
||||
} for idx in range(0, 10)
|
||||
])
|
||||
|
||||
def assertLeadConvertion(self, rule, registrations, partner=None, **expected):
|
||||
|
|
@ -304,6 +302,98 @@ class TestEventFullCommon(EventCrmCase, TestSalesCommon, MockVisitor):
|
|||
self.assertIn(answer.value_text_box, lead.description) # better: check multi line
|
||||
|
||||
|
||||
class TestEventMailCommon(EventCase, SMSCase, MailCase, CronMixinCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.event_cron_id = cls.env.ref('event.event_mail_scheduler')
|
||||
# deactivate other schedulers to avoid messing with crons
|
||||
cls.env['event.mail'].search([]).unlink()
|
||||
# consider asynchronous sending as default sending
|
||||
cls.env["ir.config_parameter"].set_param("event.event_mail_async", False)
|
||||
|
||||
cls.env.company.write({
|
||||
'email': 'info@yourcompany.example.com',
|
||||
'name': 'YourCompany',
|
||||
})
|
||||
|
||||
# prepare SMS templates
|
||||
cls.sms_template_sub = cls.env['sms.template'].create({
|
||||
'name': 'Test SMS Subscription',
|
||||
'model_id': cls.env.ref('event.model_event_registration').id,
|
||||
'body': '{{ object.event_id.organizer_id.name }} registration confirmation.',
|
||||
'lang': '{{ object.partner_id.lang }}'
|
||||
})
|
||||
cls.sms_template_rem = cls.env['sms.template'].create({
|
||||
'name': 'Test SMS Reminder',
|
||||
'model_id': cls.env.ref('event.model_event_registration').id,
|
||||
'body': '{{ object.event_id.organizer_id.name }} reminder',
|
||||
'lang': '{{ object.partner_id.lang }}'
|
||||
})
|
||||
|
||||
# freeze some datetimes, and ensure more than 1D+1H before event starts
|
||||
# to ease time-based scheduler check
|
||||
# Since `now` is used to set the `create_date` of an event and create_date
|
||||
# has often microseconds, we set it to ensure that the scheduler we still be
|
||||
# launched if scheduled_date == create_date - microseconds
|
||||
cls.reference_now = datetime(2021, 3, 20, 14, 30, 15, 123456)
|
||||
cls.event_date_begin = datetime(2021, 3, 25, 8, 0, 0)
|
||||
cls.event_date_end = datetime(2021, 3, 28, 18, 0, 0)
|
||||
|
||||
cls._setup_test_reports()
|
||||
with cls.mock_datetime_and_now(cls, cls.reference_now):
|
||||
cls.test_event = cls.env['event.event'].create({
|
||||
'name': 'TestEventMail',
|
||||
'user_id': cls.user_eventmanager.id,
|
||||
'date_begin': cls.event_date_begin,
|
||||
'date_end': cls.event_date_end,
|
||||
'event_mail_ids': [
|
||||
(0, 0, { # right at subscription: mail
|
||||
'interval_unit': 'now',
|
||||
'interval_type': 'after_sub',
|
||||
'notification_type': 'mail',
|
||||
'template_ref': f'mail.template,{cls.template_subscription.id}',
|
||||
}),
|
||||
(0, 0, { # right at subscription: sms
|
||||
'interval_unit': 'now',
|
||||
'interval_type': 'after_sub',
|
||||
'notification_type': 'sms',
|
||||
'template_ref': f'sms.template,{cls.sms_template_sub.id}',
|
||||
}),
|
||||
(0, 0, { # 3 days before event: mail
|
||||
'interval_nbr': 3,
|
||||
'interval_unit': 'days',
|
||||
'interval_type': 'before_event',
|
||||
'notification_type': 'mail',
|
||||
'template_ref': f'mail.template,{cls.template_reminder.id}',
|
||||
}),
|
||||
(0, 0, { # 3 days before event: SMS
|
||||
'interval_nbr': 3,
|
||||
'interval_unit': 'days',
|
||||
'interval_type': 'before_event',
|
||||
'notification_type': 'sms',
|
||||
'template_ref': f'sms.template,{cls.sms_template_rem.id}',
|
||||
}),
|
||||
(0, 0, { # 1h after event: mail
|
||||
'interval_nbr': 1,
|
||||
'interval_unit': 'hours',
|
||||
'interval_type': 'after_event',
|
||||
'notification_type': 'mail',
|
||||
'template_ref': f'mail.template,{cls.template_reminder.id}',
|
||||
}),
|
||||
(0, 0, { # 1h after event: SMS
|
||||
'interval_nbr': 1,
|
||||
'interval_unit': 'hours',
|
||||
'interval_type': 'after_event',
|
||||
'notification_type': 'sms',
|
||||
'template_ref': f'sms.template,{cls.sms_template_rem.id}',
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
class TestWEventCommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal, MockVisitor):
|
||||
|
||||
def setUp(self):
|
||||
|
|
@ -322,7 +412,8 @@ class TestWEventCommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal, MockVisitor
|
|||
'description_sale': 'Mighty Description',
|
||||
'list_price': 10,
|
||||
'standard_price': 30.0,
|
||||
'detailed_type': 'event',
|
||||
'type': 'service',
|
||||
'service_tracking': 'event',
|
||||
})
|
||||
|
||||
self.event_tag_category_1 = self.env['event.tag.category'].create({
|
||||
|
|
@ -336,13 +427,12 @@ class TestWEventCommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal, MockVisitor
|
|||
'color': 8,
|
||||
})
|
||||
self.env['event.event'].search(
|
||||
[('name', 'like', '%Online Reveal%')]
|
||||
[('name', 'like', 'Online Reveal')]
|
||||
).write(
|
||||
{'name': 'Do not click on me'}
|
||||
)
|
||||
self.event = self.env['event.event'].create({
|
||||
'name': 'Online Reveal TestEvent',
|
||||
'auto_confirm': True,
|
||||
'stage_id': self.env.ref('event.event_stage_booked').id,
|
||||
'address_id': False,
|
||||
'user_id': self.user_demo.id,
|
||||
|
|
@ -377,7 +467,6 @@ class TestWEventCommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal, MockVisitor
|
|||
'email': 'constantin@test.example.com',
|
||||
'country_id': self.env.ref('base.be').id,
|
||||
'phone': '0485112233',
|
||||
'mobile': False,
|
||||
})
|
||||
self.event_speaker = self.env['res.partner'].create({
|
||||
'name': 'Brandon Freeman',
|
||||
|
|
@ -392,7 +481,7 @@ class TestWEventCommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal, MockVisitor
|
|||
self.event_question_1 = self.env['event.question'].create({
|
||||
'title': 'Which field are you working in',
|
||||
'question_type': 'simple_choice',
|
||||
'event_id': self.event.id,
|
||||
'event_ids': [Command.set(self.event.ids)],
|
||||
'once_per_order': False,
|
||||
'answer_ids': [
|
||||
(0, 0, {'name': 'Consumers'}),
|
||||
|
|
@ -403,7 +492,7 @@ class TestWEventCommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal, MockVisitor
|
|||
self.event_question_2 = self.env['event.question'].create({
|
||||
'title': 'How did you hear about us ?',
|
||||
'question_type': 'text_box',
|
||||
'event_id': self.event.id,
|
||||
'event_ids': [Command.set(self.event.ids)],
|
||||
'once_per_order': True,
|
||||
})
|
||||
|
||||
|
|
@ -421,6 +510,7 @@ class TestWEventCommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal, MockVisitor
|
|||
'wishlisted_by_default': True,
|
||||
'user_id': self.user_admin.id,
|
||||
'partner_id': self.event_speaker.id,
|
||||
'description': 'Performance of Raoul Grosbedon.'
|
||||
})
|
||||
self.track_1 = self.env['event.track'].create({
|
||||
'name': 'Live Testimonial',
|
||||
|
|
@ -431,9 +521,10 @@ class TestWEventCommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal, MockVisitor
|
|||
'is_published': True,
|
||||
'user_id': self.user_admin.id,
|
||||
'partner_id': self.event_speaker.id,
|
||||
'description': 'Description of the live.'
|
||||
})
|
||||
self.track_2 = self.env['event.track'].create({
|
||||
'name': 'Our Last Day Together !',
|
||||
'name': 'Our Last Day Together!',
|
||||
'event_id': self.event.id,
|
||||
'stage_id': self.env.ref('website_event_track.event_track_stage3').id,
|
||||
'date': self.reference_now + timedelta(days=1),
|
||||
|
|
@ -441,22 +532,7 @@ class TestWEventCommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal, MockVisitor
|
|||
'is_published': True,
|
||||
'user_id': self.user_admin.id,
|
||||
'partner_id': self.event_speaker.id,
|
||||
})
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# MEETING ROOMS
|
||||
# ----------------------------------------------------------
|
||||
|
||||
self.env['event.meeting.room'].create({
|
||||
'name': 'Best wood for furniture',
|
||||
'summary': 'Let\'s talk about wood types for furniture',
|
||||
'target_audience': 'wood expert(s)',
|
||||
'is_pinned': True,
|
||||
'website_published': True,
|
||||
'event_id': self.event.id,
|
||||
'room_lang_id': self.env.ref('base.lang_en').id,
|
||||
'room_max_capacity': '12',
|
||||
'room_participant_count': 9,
|
||||
'description': 'Description of our last day together.'
|
||||
})
|
||||
|
||||
self.env.flush_all()
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.test_event_full.tests.common import TestEventFullCommon
|
||||
from odoo.tests import users
|
||||
from odoo.tests import tagged, users
|
||||
|
||||
|
||||
@tagged("event_crm")
|
||||
class TestEventCrm(TestEventFullCommon):
|
||||
|
||||
@classmethod
|
||||
|
|
|
|||
|
|
@ -1,78 +0,0 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import time
|
||||
|
||||
from odoo.tests import tagged
|
||||
from odoo.fields import Command
|
||||
|
||||
from odoo.addons.test_event_full.tests.common import TestEventFullCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestEventTicketPriceRounding(TestEventFullCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.ticket_product.write({
|
||||
'lst_price': 1.0
|
||||
})
|
||||
|
||||
cls.currency_jpy = cls.env['res.currency'].create({
|
||||
'name': 'JPX',
|
||||
'symbol': '¥',
|
||||
'rounding': 1.0,
|
||||
'rate_ids': [Command.create({'rate': 133.6200, 'name': time.strftime('%Y-%m-%d')})],
|
||||
})
|
||||
|
||||
cls.currency_cad = cls.env['res.currency'].create({
|
||||
'name': 'CXD',
|
||||
'symbol': '$',
|
||||
'rounding': 0.01,
|
||||
'rate_ids': [Command.create({'rate': 1.338800, 'name': time.strftime('%Y-%m-%d')})],
|
||||
})
|
||||
|
||||
cls.pricelist_usd = cls.env['product.pricelist'].create({
|
||||
'name': 'Pricelist USD',
|
||||
'currency_id': cls.env.ref('base.USD').id,
|
||||
})
|
||||
|
||||
cls.pricelist_jpy = cls.env['product.pricelist'].create({
|
||||
'name': 'Pricelist JPY',
|
||||
'currency_id': cls.currency_jpy.id,
|
||||
})
|
||||
|
||||
cls.pricelist_cad = cls.env['product.pricelist'].create({
|
||||
'name': 'Pricelist CAD',
|
||||
'currency_id': cls.currency_cad.id,
|
||||
})
|
||||
|
||||
cls.event_type = cls.env['event.type'].create({
|
||||
'name': 'Test Event Type',
|
||||
'auto_confirm': True,
|
||||
'event_type_ticket_ids': [
|
||||
(0, 0, {
|
||||
'name': 'Test Event Ticket',
|
||||
'product_id': cls.ticket_product.id,
|
||||
'price': 30.0,
|
||||
})
|
||||
],
|
||||
})
|
||||
|
||||
cls.event_ticket = cls.event_type.event_type_ticket_ids[0]
|
||||
|
||||
def test_no_discount_usd(self):
|
||||
ticket = self.event_ticket.with_context(pricelist=self.pricelist_usd.id)
|
||||
ticket._compute_price_reduce()
|
||||
self.assertAlmostEqual(ticket.price_reduce, 30.0, places=6, msg="No discount should be applied for the USD pricelist.")
|
||||
|
||||
def test_no_discount_jpy(self):
|
||||
ticket = self.event_ticket.with_context(pricelist=self.pricelist_jpy.id)
|
||||
ticket._compute_price_reduce()
|
||||
self.assertAlmostEqual(ticket.price_reduce, 30.0, places=6, msg="No discount should be applied for the JPY pricelist.")
|
||||
|
||||
def test_no_discount_cad(self):
|
||||
ticket = self.event_ticket.with_context(pricelist=self.pricelist_cad.id)
|
||||
ticket._compute_price_reduce()
|
||||
self.assertAlmostEqual(ticket.price_reduce, 30.0, places=6, msg="No discount should be applied for the CAD pricelist.")
|
||||
|
|
@ -36,16 +36,13 @@ class TestEventEvent(TestEventFullCommon):
|
|||
|
||||
# check result
|
||||
self.assertEqual(event.address_id, self.env.user.company_id.partner_id)
|
||||
self.assertTrue(event.auto_confirm)
|
||||
self.assertEqual(event.country_id, self.env.user.company_id.country_id)
|
||||
self.assertEqual(event.date_tz, 'Europe/Paris')
|
||||
self.assertEqual(event.event_booth_count, 4)
|
||||
self.assertEqual(len(event.event_mail_ids), 3)
|
||||
self.assertEqual(len(event.event_ticket_ids), 2)
|
||||
self.assertTrue(event.introduction_menu)
|
||||
self.assertTrue(event.location_menu)
|
||||
self.assertTrue(event.menu_register_cta)
|
||||
self.assertEqual(event.message_partner_ids, self.env.user.partner_id + self.env.user.company_id.partner_id)
|
||||
self.assertEqual(event.message_partner_ids, self.env.user.partner_id)
|
||||
self.assertEqual(event.note, '<p>Template note</p>')
|
||||
self.assertTrue(event.register_menu)
|
||||
self.assertEqual(len(event.question_ids), 3)
|
||||
|
|
@ -75,12 +72,23 @@ class TestEventEvent(TestEventFullCommon):
|
|||
self.assertTrue(event.is_ongoing)
|
||||
self.assertTrue(event.event_registrations_started)
|
||||
|
||||
def test_event_kanban_state_on_stage_change(self):
|
||||
"""Test that kanban_state updates correctly when stage is changed."""
|
||||
test_event_1 = self.env['event.event'].browse(self.test_event.ids)
|
||||
test_event_2 = test_event_1.copy()
|
||||
|
||||
test_event_1.kanban_state = 'done'
|
||||
test_event_2.kanban_state = 'cancel' # Event Cancelled
|
||||
|
||||
new_stage = self.env['event.stage'].create({'name': 'New Stage', 'sequence': 1})
|
||||
(test_event_1 | test_event_2).stage_id = new_stage.id # Change event stage
|
||||
|
||||
self.assertEqual(test_event_1.kanban_state, 'normal', 'kanban state should reset to "normal" on stage change')
|
||||
self.assertEqual(test_event_2.kanban_state, 'cancel', 'kanban state should not reset on stage change')
|
||||
|
||||
@freeze_time('2021-12-01 11:00:00')
|
||||
@users('event_user')
|
||||
def test_event_seats_and_schedulers(self):
|
||||
now = datetime.now() # used to force create_date, as sql is not wrapped by freeze gun
|
||||
self.env.cr._now = now
|
||||
|
||||
test_event = self.env['event.event'].browse(self.test_event.ids)
|
||||
ticket_1 = test_event.event_ticket_ids.filtered(lambda ticket: ticket.name == 'Ticket1')
|
||||
ticket_2 = test_event.event_ticket_ids.filtered(lambda ticket: ticket.name == 'Ticket2')
|
||||
|
|
@ -94,14 +102,15 @@ class TestEventEvent(TestEventFullCommon):
|
|||
self.assertFalse(ticket_2.sale_available)
|
||||
|
||||
# make 9 registrations (let 1 on ticket)
|
||||
with self.mock_mail_gateway():
|
||||
with self.mock_datetime_and_now(self.reference_now), \
|
||||
self.mock_mail_gateway():
|
||||
self.env['event.registration'].create([
|
||||
{'create_date': now,
|
||||
'email': 'test.customer.%02d@test.example.com' % x,
|
||||
'phone': '04560011%02d' % x,
|
||||
'event_id': test_event.id,
|
||||
'event_ticket_id': ticket_1.id,
|
||||
'name': 'Customer %d' % x,
|
||||
{
|
||||
'email': 'test.customer.%02d@test.example.com' % x,
|
||||
'phone': '04560011%02d' % x,
|
||||
'event_id': test_event.id,
|
||||
'event_ticket_id': ticket_1.id,
|
||||
'name': 'Customer %d' % x,
|
||||
}
|
||||
for x in range(0, 9)
|
||||
])
|
||||
|
|
@ -114,27 +123,29 @@ class TestEventEvent(TestEventFullCommon):
|
|||
self.assertEqual(ticket_2.seats_available, 0)
|
||||
|
||||
# prevent registration due to ticket limit
|
||||
with self.assertRaises(exceptions.ValidationError):
|
||||
with self.mock_datetime_and_now(self.reference_now), \
|
||||
self.assertRaises(exceptions.ValidationError):
|
||||
self.env['event.registration'].create([
|
||||
{'create_date': now,
|
||||
'email': 'additional.customer.%02d@test.example.com' % x,
|
||||
'phone': '04560011%02d' % x,
|
||||
'event_id': test_event.id,
|
||||
'event_ticket_id': ticket_1.id,
|
||||
'name': 'Additional Customer %d' % x,
|
||||
{
|
||||
'email': 'additional.customer.%02d@test.example.com' % x,
|
||||
'phone': '04560011%02d' % x,
|
||||
'event_id': test_event.id,
|
||||
'event_ticket_id': ticket_1.id,
|
||||
'name': 'Additional Customer %d' % x,
|
||||
}
|
||||
for x in range(0, 2)
|
||||
])
|
||||
|
||||
# make 20 registrations (on free ticket)
|
||||
with self.mock_mail_gateway():
|
||||
with self.mock_datetime_and_now(self.reference_now), \
|
||||
self.mock_mail_gateway():
|
||||
self.env['event.registration'].create([
|
||||
{'create_date': now,
|
||||
'email': 'other.customer.%02d@test.example.com' % x,
|
||||
'phone': '04560011%02d' % x,
|
||||
'event_id': test_event.id,
|
||||
'event_ticket_id': ticket_2.id,
|
||||
'name': 'Other Customer %d' % x,
|
||||
{
|
||||
'email': 'other.customer.%02d@test.example.com' % x,
|
||||
'phone': '04560011%02d' % x,
|
||||
'event_id': test_event.id,
|
||||
'event_ticket_id': ticket_2.id,
|
||||
'name': 'Other Customer %d' % x,
|
||||
}
|
||||
for x in range(0, 20)
|
||||
])
|
||||
|
|
@ -145,14 +156,15 @@ class TestEventEvent(TestEventFullCommon):
|
|||
self.assertEqual(ticket_2.seats_available, 0)
|
||||
|
||||
# prevent registration due to event limit
|
||||
with self.assertRaises(exceptions.ValidationError):
|
||||
with self.mock_datetime_and_now(self.reference_now), \
|
||||
self.assertRaises(exceptions.ValidationError):
|
||||
self.env['event.registration'].create([
|
||||
{'create_date': now,
|
||||
'email': 'additional.customer.%02d@test.example.com' % x,
|
||||
'phone': '04560011%02d' % x,
|
||||
'event_id': test_event.id,
|
||||
'event_ticket_id': ticket_2.id,
|
||||
'name': 'Additional Customer %d' % x,
|
||||
{
|
||||
'email': 'additional.customer.%02d@test.example.com' % x,
|
||||
'phone': '04560011%02d' % x,
|
||||
'event_id': test_event.id,
|
||||
'event_ticket_id': ticket_2.id,
|
||||
'name': 'Additional Customer %d' % x,
|
||||
}
|
||||
for x in range(0, 2)
|
||||
])
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from freezegun import freeze_time
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.addons.mail.tests.common import MockEmail
|
||||
from odoo.addons.sms.tests.common import MockSMS
|
||||
from odoo.addons.test_event_full.tests.common import TestWEventCommon
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.addons.test_event_full.tests.common import TestEventFullCommon, TestEventMailCommon
|
||||
from odoo.tests import tagged, users
|
||||
from odoo.tools import formataddr
|
||||
|
||||
class TestTemplateRefModel(TestWEventCommon):
|
||||
|
||||
@tagged('event_mail', 'post_install', '-at_install')
|
||||
class TestEventMailInternals(TestEventMailCommon):
|
||||
|
||||
def test_template_ref_delete_lines(self):
|
||||
""" When deleting a template, related lines should be deleted too """
|
||||
|
|
@ -51,117 +51,303 @@ class TestTemplateRefModel(TestWEventCommon):
|
|||
self.assertEqual(len(event_type.event_type_mail_ids.exists()), 0)
|
||||
self.assertEqual(len(event.event_mail_ids.exists()), 0)
|
||||
|
||||
def test_template_ref_model_constraint(self):
|
||||
|
||||
test_cases = [
|
||||
('mail', 'mail.template', True),
|
||||
('mail', 'sms.template', False),
|
||||
('sms', 'sms.template', True),
|
||||
('sms', 'mail.template', False),
|
||||
]
|
||||
@tagged('event_mail', 'post_install', '-at_install')
|
||||
class TestEventMailSchedule(TestEventMailCommon):
|
||||
|
||||
for notification_type, template_type, valid in test_cases:
|
||||
with self.subTest(notification_type=notification_type, template_type=template_type):
|
||||
if template_type == 'mail.template':
|
||||
template = self.env[template_type].create({
|
||||
'name': 'test template',
|
||||
'model_id': self.env['ir.model']._get_id('event.registration'),
|
||||
})
|
||||
else:
|
||||
template = self.env[template_type].create({
|
||||
'name': 'test template',
|
||||
'body': 'Body Test',
|
||||
'model_id': self.env['ir.model']._get_id('event.registration'),
|
||||
})
|
||||
if not valid:
|
||||
with self.assertRaises(ValidationError) as cm:
|
||||
self.env['event.mail'].create({
|
||||
'event_id': self.event.id,
|
||||
'notification_type': notification_type,
|
||||
'interval_unit': 'now',
|
||||
'interval_type': 'before_event',
|
||||
'template_ref': template,
|
||||
})
|
||||
if notification_type == 'mail':
|
||||
self.assertEqual(str(cm.exception), 'The template which is referenced should be coming from mail.template model.')
|
||||
else:
|
||||
self.assertEqual(str(cm.exception), 'The template which is referenced should be coming from sms.template model.')
|
||||
|
||||
class TestEventSmsMailSchedule(TestWEventCommon, MockEmail, MockSMS):
|
||||
|
||||
@freeze_time('2020-07-06 12:00:00')
|
||||
@mute_logger('odoo.addons.base.models.ir_model', 'odoo.models')
|
||||
def test_event_mail_before_trigger_sent_count(self):
|
||||
""" Emails are sent to both confirmed and unconfirmed attendees.
|
||||
This test checks that the count of sent emails includes the emails sent to unconfirmed ones
|
||||
|
||||
Time in the test is frozen to simulate the following state:
|
||||
|
||||
NOW Event Start Event End
|
||||
12:00 13:00 14:00
|
||||
| | |
|
||||
──────────────────────────────────────►
|
||||
| | time
|
||||
◄─────────────────►
|
||||
3 hours
|
||||
Trigger before event
|
||||
"""
|
||||
self.sms_template_rem = self.env['sms.template'].create({
|
||||
'name': 'Test reminder',
|
||||
'model_id': self.env.ref('event.model_event_registration').id,
|
||||
'body': '{{ object.event_id.organizer_id.name }} reminder',
|
||||
'lang': '{{ object.partner_id.lang }}'
|
||||
})
|
||||
test_event = self.env['event.event'].create({
|
||||
'name': 'TestEventMail',
|
||||
# 'user_id': self.env.ref('base.user_admin').id,
|
||||
'auto_confirm': False,
|
||||
'date_begin': datetime.now() + timedelta(hours=1),
|
||||
'date_end': datetime.now() + timedelta(hours=2),
|
||||
'event_mail_ids': [
|
||||
(0, 0, { # email 3 hours before event
|
||||
'interval_nbr': 3,
|
||||
'interval_unit': 'hours',
|
||||
'interval_type': 'before_event',
|
||||
'template_ref': 'mail.template,%i' % self.env['ir.model.data']._xmlid_to_res_id('event.event_reminder')}),
|
||||
(0, 0, { # sms 3 hours before event
|
||||
'interval_nbr': 3,
|
||||
'interval_unit': 'hours',
|
||||
'interval_type': 'before_event',
|
||||
'notification_type': 'sms',
|
||||
'template_ref': 'sms.template,%i' % self.sms_template_rem.id}),
|
||||
]
|
||||
})
|
||||
mail_scheduler = test_event.event_mail_ids
|
||||
self.assertEqual(len(mail_scheduler), 2, 'There should be two mail schedulers. One for mail one for sms. Cannot perform test')
|
||||
""" Emails are only sent to confirmed attendees. """
|
||||
test_event = self.test_event
|
||||
mail_schedulers = test_event.event_mail_ids
|
||||
self.assertEqual(len(mail_schedulers), 6)
|
||||
before = mail_schedulers.filtered(lambda m: m.interval_type == "before_event" and m.interval_unit == "days")
|
||||
self.assertEqual(len(before), 2)
|
||||
|
||||
# Add registrations
|
||||
self.env['event.registration'].create([{
|
||||
_dummy, _dummy, open_reg, done_reg = self.env['event.registration'].create([{
|
||||
'event_id': test_event.id,
|
||||
'name': 'RegistrationUnconfirmed',
|
||||
'email': 'Registration@Unconfirmed.com',
|
||||
'phone': '1',
|
||||
'state': 'draft',
|
||||
}, {
|
||||
'event_id': test_event.id,
|
||||
'name': 'RegistrationCanceled',
|
||||
'email': 'Registration@Canceled.com',
|
||||
'phone': '2',
|
||||
'state': 'cancel',
|
||||
}, {
|
||||
'event_id': test_event.id,
|
||||
'name': 'RegistrationConfirmed',
|
||||
'email': 'Registration@Confirmed.com',
|
||||
'phone': '3',
|
||||
'state': 'open',
|
||||
}, {
|
||||
'event_id': test_event.id,
|
||||
'name': 'RegistrationDone',
|
||||
'email': 'Registration@Done.com',
|
||||
'phone': '4',
|
||||
'state': 'done',
|
||||
}])
|
||||
|
||||
with self.mock_mail_gateway(), self.mockSMSGateway():
|
||||
mail_scheduler.execute()
|
||||
with self.mock_datetime_and_now(self.event_date_begin - timedelta(days=2)), \
|
||||
self.mock_mail_gateway(), \
|
||||
self.mockSMSGateway():
|
||||
before.execute()
|
||||
|
||||
self.assertEqual(len(self._new_mails), 2, 'Mails were not created')
|
||||
self.assertEqual(len(self._new_sms), 2, 'SMS were not created')
|
||||
for registration in open_reg, done_reg:
|
||||
with self.subTest(registration_state=registration.state, medium='mail'):
|
||||
self.assertMailMailWEmails(
|
||||
[formataddr((registration.name, registration.email.lower()))],
|
||||
'outgoing',
|
||||
)
|
||||
with self.subTest(registration_state=registration.state, medium='sms'):
|
||||
self.assertSMS(
|
||||
self.env['res.partner'],
|
||||
registration.phone,
|
||||
None,
|
||||
)
|
||||
self.assertEqual(len(self._new_mails), 2, 'Mails should not be sent to draft or cancel registrations')
|
||||
self.assertEqual(len(self._new_sms), 2, 'SMS should not be sent to draft or cancel registrations')
|
||||
|
||||
self.assertEqual(test_event.seats_expected, 2, 'Wrong number of expected seats (attendees)')
|
||||
self.assertEqual(test_event.seats_taken, 2, 'Wrong number of seats_taken')
|
||||
|
||||
self.assertEqual(mail_scheduler.filtered(lambda r: r.notification_type == 'mail').mail_count_done, 2,
|
||||
'Wrong Emails Sent Count! Probably emails sent to unconfirmed attendees were not included into the Sent Count')
|
||||
self.assertEqual(mail_scheduler.filtered(lambda r: r.notification_type == 'sms').mail_count_done, 2,
|
||||
'Wrong SMS Sent Count! Probably SMS sent to unconfirmed attendees were not included into the Sent Count')
|
||||
for scheduler in before:
|
||||
self.assertEqual(
|
||||
scheduler.mail_count_done, 2,
|
||||
'Wrong Emails Sent Count! Probably emails sent to unconfirmed attendees were not included into the Sent Count'
|
||||
)
|
||||
|
||||
@users('user_eventmanager')
|
||||
def test_schedule_event_scalability(self):
|
||||
""" Test scalability / iterative work on event-based schedulers """
|
||||
test_event = self.env['event.event'].browse(self.test_event.ids)
|
||||
registrations = self._create_registrations(test_event, 30)
|
||||
registrations = registrations.sorted("id")
|
||||
|
||||
# check event-based schedulers
|
||||
after_mail = test_event.event_mail_ids.filtered(lambda s: s.interval_type == "after_event" and s.notification_type == "mail")
|
||||
self.assertEqual(len(after_mail), 1)
|
||||
self.assertEqual(after_mail.mail_count_done, 0)
|
||||
self.assertFalse(after_mail.mail_done)
|
||||
after_sms = test_event.event_mail_ids.filtered(lambda s: s.interval_type == "after_event" and s.notification_type == "sms")
|
||||
self.assertEqual(len(after_sms), 1)
|
||||
self.assertEqual(after_sms.mail_count_done, 0)
|
||||
self.assertFalse(after_sms.mail_done)
|
||||
before_mail = test_event.event_mail_ids.filtered(lambda s: s.interval_type == "before_event" and s.notification_type == "mail")
|
||||
self.assertEqual(len(before_mail), 1)
|
||||
self.assertEqual(before_mail.mail_count_done, 0)
|
||||
self.assertFalse(before_mail.mail_done)
|
||||
before_sms = test_event.event_mail_ids.filtered(lambda s: s.interval_type == "before_event" and s.notification_type == "sms")
|
||||
self.assertEqual(len(before_sms), 1)
|
||||
self.assertEqual(before_sms.mail_count_done, 0)
|
||||
self.assertFalse(before_sms.mail_done)
|
||||
|
||||
# setup batch and cron limit sizes to check iterative behavior
|
||||
batch_size, cron_limit = 5, 20
|
||||
self.env["ir.config_parameter"].sudo().set_param("mail.batch_size", batch_size)
|
||||
self.env["ir.config_parameter"].sudo().set_param("mail.render.cron.limit", cron_limit)
|
||||
|
||||
# launch before event schedulers -> all communications are sent
|
||||
current_now = self.event_date_begin - timedelta(days=1)
|
||||
EventMail = type(self.env['event.mail'])
|
||||
exec_origin = EventMail._execute_event_based_for_registrations
|
||||
with (
|
||||
patch.object(
|
||||
EventMail, '_execute_event_based_for_registrations', autospec=True, wraps=EventMail, side_effect=exec_origin,
|
||||
) as mock_exec,
|
||||
self.mock_datetime_and_now(current_now),
|
||||
self.mockSMSGateway(),
|
||||
self.mock_mail_gateway(),
|
||||
self.capture_triggers('event.event_mail_scheduler') as capture,
|
||||
):
|
||||
self.event_cron_id.method_direct_trigger()
|
||||
|
||||
self.assertFalse(after_mail.last_registration_id)
|
||||
self.assertEqual(after_mail.mail_count_done, 0)
|
||||
self.assertFalse(after_mail.mail_done)
|
||||
self.assertFalse(after_sms.last_registration_id)
|
||||
self.assertEqual(after_sms.mail_count_done, 0)
|
||||
self.assertFalse(after_sms.mail_done)
|
||||
# iterative work on registrations: only 20 (cron limit) are taken into account
|
||||
self.assertEqual(before_mail.last_registration_id, registrations[19])
|
||||
self.assertEqual(before_mail.mail_count_done, 20)
|
||||
self.assertFalse(before_mail.mail_done)
|
||||
self.assertEqual(before_sms.last_registration_id, registrations[19])
|
||||
self.assertEqual(before_sms.mail_count_done, 20)
|
||||
self.assertFalse(before_sms.mail_done)
|
||||
self.assertEqual(mock_exec.call_count, 8, "Batch of 5 to make 20 registrations: 4 calls / scheduler")
|
||||
# cron should have been triggered for the remaining registrations
|
||||
self.assertSchedulerCronTriggers(capture, [current_now] * 2)
|
||||
|
||||
# relaunch to close scheduler
|
||||
with (
|
||||
self.mock_datetime_and_now(current_now),
|
||||
self.mockSMSGateway(),
|
||||
self.mock_mail_gateway(),
|
||||
self.capture_triggers('event.event_mail_scheduler') as capture,
|
||||
):
|
||||
self.event_cron_id.method_direct_trigger()
|
||||
self.assertEqual(before_mail.last_registration_id, registrations[-1])
|
||||
self.assertEqual(before_mail.mail_count_done, 30)
|
||||
self.assertTrue(before_mail.mail_done)
|
||||
self.assertEqual(before_sms.last_registration_id, registrations[-1])
|
||||
self.assertEqual(before_sms.mail_count_done, 30)
|
||||
self.assertTrue(before_sms.mail_done)
|
||||
self.assertFalse(capture.records)
|
||||
|
||||
# launch after event schedulers -> all communications are sent
|
||||
current_now = self.event_date_end + timedelta(hours=1)
|
||||
with (
|
||||
self.mock_datetime_and_now(current_now),
|
||||
self.mockSMSGateway(),
|
||||
self.mock_mail_gateway(),
|
||||
self.capture_triggers('event.event_mail_scheduler') as capture,
|
||||
):
|
||||
self.event_cron_id.method_direct_trigger()
|
||||
|
||||
# iterative work on registrations: only 20 (cron limit) are taken into account
|
||||
self.assertEqual(after_mail.last_registration_id, registrations[19])
|
||||
self.assertEqual(after_mail.mail_count_done, 20)
|
||||
self.assertFalse(after_mail.mail_done)
|
||||
self.assertEqual(after_sms.last_registration_id, registrations[19])
|
||||
self.assertEqual(after_sms.mail_count_done, 20)
|
||||
self.assertFalse(after_sms.mail_done)
|
||||
self.assertEqual(mock_exec.call_count, 8, "Batch of 5 to make 20 registrations: 4 calls / scheduler")
|
||||
# cron should have been triggered for the remaining registrations
|
||||
self.assertSchedulerCronTriggers(capture, [current_now] * 2)
|
||||
|
||||
# relaunch to close scheduler
|
||||
with (
|
||||
self.mock_datetime_and_now(current_now),
|
||||
self.mockSMSGateway(),
|
||||
self.mock_mail_gateway(),
|
||||
self.capture_triggers('event.event_mail_scheduler') as capture,
|
||||
):
|
||||
self.event_cron_id.method_direct_trigger()
|
||||
self.assertEqual(after_mail.last_registration_id, registrations[-1])
|
||||
self.assertEqual(after_mail.mail_count_done, 30)
|
||||
self.assertTrue(after_mail.mail_done)
|
||||
self.assertEqual(after_sms.last_registration_id, registrations[-1])
|
||||
self.assertEqual(after_sms.mail_count_done, 30)
|
||||
self.assertTrue(after_sms.mail_done)
|
||||
self.assertFalse(capture.records)
|
||||
|
||||
@users('user_eventmanager')
|
||||
def test_schedule_subscription_scalability(self):
|
||||
""" Test scalability / iterative work on subscription-based schedulers """
|
||||
test_event = self.env['event.event'].browse(self.test_event.ids)
|
||||
|
||||
sub_mail = test_event.event_mail_ids.filtered(lambda s: s.interval_type == "after_sub" and s.interval_unit == "now" and s.notification_type == "mail")
|
||||
self.assertEqual(len(sub_mail), 1)
|
||||
self.assertEqual(sub_mail.mail_count_done, 0)
|
||||
sub_sms = test_event.event_mail_ids.filtered(lambda s: s.interval_type == "after_sub" and s.interval_unit == "now" and s.notification_type == "sms")
|
||||
self.assertEqual(len(sub_sms), 1)
|
||||
self.assertEqual(sub_sms.mail_count_done, 0)
|
||||
|
||||
# setup batch and cron limit sizes to check iterative behavior
|
||||
batch_size, cron_limit = 5, 20
|
||||
self.env["ir.config_parameter"].sudo().set_param("mail.batch_size", batch_size)
|
||||
self.env["ir.config_parameter"].sudo().set_param("mail.render.cron.limit", cron_limit)
|
||||
|
||||
# create registrations -> each one receives its on subscribe communication
|
||||
EventMailRegistration = type(self.env['event.mail.registration'])
|
||||
exec_origin = EventMailRegistration._execute_on_registrations
|
||||
with patch.object(
|
||||
EventMailRegistration, '_execute_on_registrations', autospec=True, wraps=EventMailRegistration, side_effect=exec_origin,
|
||||
) as mock_exec, \
|
||||
self.mock_datetime_and_now(self.reference_now + timedelta(hours=1)), \
|
||||
self.mockSMSGateway(), \
|
||||
self.mock_mail_gateway(), \
|
||||
self.capture_triggers('event.event_mail_scheduler') as capture:
|
||||
self._create_registrations(test_event, 30)
|
||||
|
||||
# iterative work on registrations: only 20 (cron limit) are taken into account
|
||||
self.assertEqual(sub_mail.mail_count_done, 20)
|
||||
self.assertEqual(sub_sms.mail_count_done, 20)
|
||||
self.assertEqual(mock_exec.call_count, 8, "Batch of 5 to make 20 registrations: 4 calls / scheduler")
|
||||
# cron should have been triggered for the remaining registrations
|
||||
self.assertSchedulerCronTriggers(capture, [self.reference_now + timedelta(hours=1)] * 2)
|
||||
|
||||
# iterative work on registrations, force cron to close those
|
||||
with (
|
||||
patch.object(
|
||||
EventMailRegistration, '_execute_on_registrations', autospec=True, wraps=EventMailRegistration, side_effect=exec_origin,
|
||||
) as mock_exec,
|
||||
self.mock_datetime_and_now(self.reference_now + timedelta(hours=1)),
|
||||
self.mockSMSGateway(),
|
||||
self.mock_mail_gateway(),
|
||||
self.capture_triggers('event.event_mail_scheduler') as capture,
|
||||
):
|
||||
self.event_cron_id.method_direct_trigger()
|
||||
|
||||
# finished sending communications
|
||||
self.assertEqual(sub_mail.mail_count_done, 30)
|
||||
self.assertEqual(sub_sms.mail_count_done, 30)
|
||||
self.assertFalse(capture.records)
|
||||
self.assertEqual(mock_exec.call_count, 4, "Batch of 5 to make 10 remaining registrations: 2 calls / scheduler")
|
||||
|
||||
|
||||
@tagged('event_mail', 'post_install', '-at_install')
|
||||
class TestEventSaleMail(TestEventFullCommon):
|
||||
|
||||
def test_event_mail_on_sale_confirmation(self):
|
||||
"""Test that a mail is sent to the customer when a sale order is confirmed."""
|
||||
ticket = self.test_event.event_ticket_ids[0]
|
||||
self.test_event.env.company.partner_id.email = 'test.email@test.example.com'
|
||||
order_line_vals = {
|
||||
"event_id": self.test_event.id,
|
||||
"event_ticket_id": ticket.id,
|
||||
"product_id": ticket.product_id.id,
|
||||
"product_uom_qty": 1,
|
||||
}
|
||||
self.customer_so.write({"order_line": [(0, 0, order_line_vals)]})
|
||||
|
||||
# check sale mail configuration
|
||||
aftersub = self.test_event.event_mail_ids.filtered(
|
||||
lambda m: m.interval_type == "after_sub"
|
||||
)
|
||||
self.assertTrue(aftersub)
|
||||
aftersub.template_ref.email_from = "{{ (object.event_id.organizer_id.email_formatted or object.event_id.user_id.email_formatted or '') }}"
|
||||
self.assertEqual(self.test_event.organizer_id, self.test_event.env.company.partner_id)
|
||||
|
||||
registration = self.env["event.registration"].create(
|
||||
{
|
||||
**self.website_customer_data[0],
|
||||
"partner_id": self.event_customer.id,
|
||||
"sale_order_line_id": self.customer_so.order_line[0].id,
|
||||
}
|
||||
)
|
||||
self.assertEqual(self.test_event.registration_ids, registration)
|
||||
self.assertEqual(self.customer_so.state, "draft")
|
||||
self.assertEqual(registration.state, "draft")
|
||||
|
||||
with self.mock_mail_gateway():
|
||||
self.customer_so.action_confirm()
|
||||
# mail send is done when writing state value, hence flushing for the test
|
||||
registration.flush_recordset()
|
||||
self.assertEqual(self.customer_so.state, "sale")
|
||||
self.assertEqual(registration.state, "open")
|
||||
|
||||
# Ensure mails are sent to customers right after subscription
|
||||
self.assertMailMailWRecord(
|
||||
registration,
|
||||
[self.event_customer.id],
|
||||
"outgoing",
|
||||
author=self.test_event.organizer_id,
|
||||
fields_values={
|
||||
"email_from": self.test_event.organizer_id.email_formatted,
|
||||
},
|
||||
)
|
||||
|
||||
def test_registration_template_body_translation(self):
|
||||
self.env['res.lang']._activate_lang('fr_BE')
|
||||
test_event = self.test_event
|
||||
self.partners[0].lang = 'fr_BE'
|
||||
self.env.ref('event.event_subscription').with_context(lang='fr_BE').body_html = 'Bonjour'
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
self.env['event.registration'].create({
|
||||
'event_id': test_event.id,
|
||||
'partner_id': self.partners[0].id
|
||||
})
|
||||
self.assertEqual(self._new_mails[0].body_html, "<p>Bonjour</p>")
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from datetime import datetime, timedelta
|
|||
|
||||
from odoo.addons.test_event_full.tests.common import TestEventFullCommon
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import users
|
||||
from odoo.tools import mute_logger
|
||||
|
|
@ -55,7 +56,7 @@ class TestEventSecurity(TestEventFullCommon):
|
|||
def test_event_access_event_registration(self):
|
||||
# Event: read ok
|
||||
event = self.test_event.with_user(self.env.user)
|
||||
event.read(['name', 'user_id', 'kanban_state_label'])
|
||||
event.read(['name', 'user_id', 'kanban_state'])
|
||||
|
||||
# Event: read only
|
||||
with self.assertRaises(AccessError):
|
||||
|
|
@ -77,7 +78,7 @@ class TestEventSecurity(TestEventFullCommon):
|
|||
def test_event_access_event_user(self):
|
||||
# Event
|
||||
event = self.test_event.with_user(self.env.user)
|
||||
event.read(['name', 'user_id', 'kanban_state_label'])
|
||||
event.read(['name', 'user_id', 'kanban_state'])
|
||||
event.write({'name': 'New name'})
|
||||
self.env['event.event'].create({
|
||||
'name': 'Event',
|
||||
|
|
@ -133,7 +134,7 @@ class TestEventSecurity(TestEventFullCommon):
|
|||
event_type.unlink()
|
||||
|
||||
# Settings access rights required to enable some features
|
||||
self.user_eventmanager.write({'groups_id': [
|
||||
self.user_eventmanager.write({'group_ids': [
|
||||
(3, self.env.ref('base.group_system').id),
|
||||
(4, self.env.ref('base.group_erp_manager').id)
|
||||
]})
|
||||
|
|
@ -142,6 +143,45 @@ class TestEventSecurity(TestEventFullCommon):
|
|||
})
|
||||
event_config.execute()
|
||||
|
||||
def test_event_question_access(self):
|
||||
""" Check that some user groups have access to questions and answers only if they are linked to at
|
||||
least one published event. """
|
||||
question = self.env['event.question'].create({
|
||||
"title": "Question",
|
||||
"event_ids": [Command.create({
|
||||
'name': 'Unpublished Event',
|
||||
'is_published': False,
|
||||
})]
|
||||
})
|
||||
answer = self.env['event.question.answer'].create({
|
||||
"name": "Answer",
|
||||
"question_id": question.id,
|
||||
})
|
||||
restricted_users = [self.user_employee, self.user_portal, self.user_public]
|
||||
unrestricted_users = [self.user_eventmanager, self.user_eventuser]
|
||||
|
||||
for user in restricted_users:
|
||||
with self.assertRaises(AccessError, msg=f'{user.name} should not have access to questions of unpublished events'):
|
||||
question.with_user(user).read(['title'])
|
||||
with self.assertRaises(AccessError, msg=f'{user.name} should not have access to answers of unpublished events'):
|
||||
answer.with_user(user).read(['name'])
|
||||
|
||||
for user in unrestricted_users:
|
||||
question.with_user(user).read(['title'])
|
||||
answer.with_user(user).read(['name'])
|
||||
|
||||
# To check the access of user groups to questions and answers linked to at least one published event.
|
||||
self.env['event.event'].create({
|
||||
'name': 'Published Event',
|
||||
'is_published': True,
|
||||
'question_ids': [Command.set(question.ids)],
|
||||
})
|
||||
|
||||
# Check that all user groups have access to questions and answers linked to at least one published event.
|
||||
for user in restricted_users + unrestricted_users:
|
||||
question.with_user(user).read(['title'])
|
||||
answer.with_user(user).read(['name'])
|
||||
|
||||
def test_implied_groups(self):
|
||||
"""Test that the implied groups are correctly set.
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ from freezegun import freeze_time
|
|||
|
||||
from odoo.addons.test_event_full.tests.common import TestEventFullCommon
|
||||
from odoo.addons.website.tests.test_performance import UtilPerf
|
||||
from odoo.tests.common import users, warmup, Form
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests import Form, users, warmup, tagged
|
||||
|
||||
|
||||
@tagged('event_performance', 'post_install', '-at_install', '-standard')
|
||||
|
|
@ -17,6 +16,9 @@ class EventPerformanceCase(TestEventFullCommon):
|
|||
super(EventPerformanceCase, self).setUp()
|
||||
# patch registry to simulate a ready environment
|
||||
self.patch(self.env.registry, 'ready', True)
|
||||
# we don't use mock_mail_gateway thus want to mock smtp to test the stack
|
||||
self._mock_smtplib_connection()
|
||||
|
||||
self._flush_tracking()
|
||||
|
||||
def _flush_tracking(self):
|
||||
|
|
@ -52,7 +54,7 @@ class TestEventPerformance(EventPerformanceCase):
|
|||
batch_size = 20
|
||||
|
||||
# simple without type involved + website
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=5368): # tef 4944 / com 4943
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=3418): # tef 3316 / com 3315
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
event_values = [
|
||||
dict(self.event_base_vals,
|
||||
|
|
@ -60,7 +62,7 @@ class TestEventPerformance(EventPerformanceCase):
|
|||
)
|
||||
for x in range(batch_size)
|
||||
]
|
||||
self.env['event.event'].create(event_values)
|
||||
self.env['event.event'].with_context(lang='en_US').create(event_values)
|
||||
|
||||
@users('event_user')
|
||||
@warmup
|
||||
|
|
@ -70,7 +72,7 @@ class TestEventPerformance(EventPerformanceCase):
|
|||
event_type = self.env['event.type'].browse(self.test_event_type.ids)
|
||||
|
||||
# complex with type
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=439): # 439
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=432): # tef 432
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
event_values = [
|
||||
dict(self.event_base_vals,
|
||||
|
|
@ -89,7 +91,7 @@ class TestEventPerformance(EventPerformanceCase):
|
|||
event_type = self.env['event.type'].browse(self.test_event_type.ids)
|
||||
|
||||
# complex with type + website
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=5480): # tef 5056 / com 5055
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=3522): # tef 3420 / com 3419
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
event_values = [
|
||||
dict(self.event_base_vals,
|
||||
|
|
@ -97,7 +99,7 @@ class TestEventPerformance(EventPerformanceCase):
|
|||
)
|
||||
for x in range(batch_size)
|
||||
]
|
||||
self.env['event.event'].create(event_values)
|
||||
self.env['event.event'].with_context(lang='en_US').create(event_values)
|
||||
|
||||
|
||||
@users('event_user')
|
||||
|
|
@ -107,7 +109,7 @@ class TestEventPerformance(EventPerformanceCase):
|
|||
has_social = 'social_menu' in self.env['event.event'] # otherwise view may crash in enterprise
|
||||
|
||||
# no type, no website
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=206): # tef 160 / com 160
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=108): # tef 103 / com 103
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
# Require for `website_menu` to be visible
|
||||
# <div name="event_menu_configuration" groups="base.group_no_one">
|
||||
|
|
@ -128,7 +130,7 @@ class TestEventPerformance(EventPerformanceCase):
|
|||
has_social = 'social_menu' in self.env['event.event'] # otherwise view may crash in enterprise
|
||||
|
||||
# no type, website
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=666): # tef 565 / com 566
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=429): # tef 379 / com 380
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
# Require for `website_menu` to be visible
|
||||
# <div name="event_menu_configuration" groups="base.group_no_one">
|
||||
|
|
@ -150,7 +152,7 @@ class TestEventPerformance(EventPerformanceCase):
|
|||
has_social = 'social_menu' in self.env['event.event'] # otherwise view may crash in enterprise
|
||||
|
||||
# type and website
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=692): # tef 593 / com 596
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=472): # tef 426 / com 428
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
# Require for `website_menu` to be visible
|
||||
# <div name="event_menu_configuration" groups="base.group_no_one">
|
||||
|
|
@ -168,7 +170,7 @@ class TestEventPerformance(EventPerformanceCase):
|
|||
def test_event_create_single_notype(self):
|
||||
""" Test a single event creation """
|
||||
# simple without type involved
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=31): # 31
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=31): # tef 31
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
event_values = dict(
|
||||
self.event_base_vals,
|
||||
|
|
@ -181,13 +183,13 @@ class TestEventPerformance(EventPerformanceCase):
|
|||
def test_event_create_single_notype_website(self):
|
||||
""" Test a single event creation """
|
||||
# simple without type involved + website
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=352): # tef 327 / com 326
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=242): # tef 228 / com 234
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
event_values = dict(
|
||||
self.event_base_vals,
|
||||
website_menu=True
|
||||
)
|
||||
self.env['event.event'].create([event_values])
|
||||
self.env['event.event'].with_context(lang='en_US').create([event_values])
|
||||
|
||||
@users('event_user')
|
||||
@warmup
|
||||
|
|
@ -196,7 +198,7 @@ class TestEventPerformance(EventPerformanceCase):
|
|||
event_type = self.env['event.type'].browse(self.test_event_type.ids)
|
||||
|
||||
# complex with type
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=58): # 58
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=52): # tef 52
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
event_values = dict(
|
||||
self.event_base_vals,
|
||||
|
|
@ -212,13 +214,13 @@ class TestEventPerformance(EventPerformanceCase):
|
|||
event_type = self.env['event.type'].browse(self.test_event_type.ids)
|
||||
|
||||
# complex with type + website
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=387): # tef 362 / com 361
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=274): # tef 266 / com 265
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
event_values = dict(
|
||||
self.event_base_vals,
|
||||
event_type_id=event_type.id,
|
||||
)
|
||||
self.env['event.event'].create([event_values])
|
||||
self.env['event.event'].with_context(lang='en_US').create([event_values])
|
||||
|
||||
|
||||
@tagged('event_performance', 'registration_performance', 'post_install', '-at_install', '-standard')
|
||||
|
|
@ -234,7 +236,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
|
|||
"""
|
||||
event = self.env['event.event'].browse(self.test_event.ids)
|
||||
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=720): # tef only: 674? - com runbot 716 - ent runbot 719
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=638): # tef 633 / com 636
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
registration_values = [
|
||||
dict(reg_data,
|
||||
|
|
@ -258,7 +260,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
|
|||
"""
|
||||
event = self.env['event.event'].browse(self.test_event.ids)
|
||||
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=210): # tef 167 / com runbot 206
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=165): # tef 164 / com runbot 163
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
registration_values = [
|
||||
dict(reg_data,
|
||||
|
|
@ -280,7 +282,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
|
|||
form like) """
|
||||
event = self.env['event.event'].browse(self.test_event.ids)
|
||||
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=731): # tef only: 685? - com runbot 727
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=651): # tef 647 - com 649
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
registration_values = [
|
||||
dict(reg_data,
|
||||
|
|
@ -301,12 +303,11 @@ class TestRegistrationPerformance(EventPerformanceCase):
|
|||
""" Test a single registration creation using Form """
|
||||
event = self.env['event.event'].browse(self.test_event.ids)
|
||||
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=231): # tef only: 210? - com runbot 216
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=145): # tef 140 / com 143
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
with Form(self.env['event.registration']) as reg_form:
|
||||
reg_form.event_id = event
|
||||
reg_form.email = 'email.00@test.example.com'
|
||||
reg_form.mobile = '0456999999'
|
||||
reg_form.name = 'My Customer'
|
||||
reg_form.phone = '0456000000'
|
||||
_registration = reg_form.save()
|
||||
|
|
@ -317,7 +318,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
|
|||
""" Test a single registration creation using Form """
|
||||
event = self.env['event.event'].browse(self.test_event.ids)
|
||||
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=233): # tef only: 213? - com runbot 217
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=146): # tef 141 / com 144
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
with Form(self.env['event.registration']) as reg_form:
|
||||
reg_form.event_id = event
|
||||
|
|
@ -330,7 +331,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
|
|||
""" Test a single registration creation using Form """
|
||||
event = self.env['event.event'].browse(self.test_event.ids)
|
||||
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=124): # tef 107 / com 109
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=63): # tef 61 / com 61
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
with Form(self.env['event.registration'].with_context(event_lead_rule_skip=True)) as reg_form:
|
||||
reg_form.event_id = event
|
||||
|
|
@ -344,7 +345,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
|
|||
event = self.env['event.event'].browse(self.test_event.ids)
|
||||
|
||||
# simple customer data
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=143): # tef only: 135? - com runbot 140
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=122): # tef 118 / com 120
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
registration_values = dict(
|
||||
self.customer_data[0],
|
||||
|
|
@ -358,7 +359,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
|
|||
event = self.env['event.event'].browse(self.test_event.ids)
|
||||
|
||||
# partner-based customer
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=149): # tef only: 142? - com runbot 146
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=121): # tef 117 / com 120
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
registration_values = {
|
||||
'event_id': event.id,
|
||||
|
|
@ -373,7 +374,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
|
|||
event = self.env['event.event'].browse(self.test_event.ids)
|
||||
|
||||
# partner-based customer
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=46): # tef 41 / com 43
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=39): # tef 38 / com 38
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
registration_values = {
|
||||
'event_id': event.id,
|
||||
|
|
@ -388,7 +389,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
|
|||
event = self.env['event.event'].browse(self.test_event.ids)
|
||||
|
||||
# website customer data
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=151): # tef only: 142? - com runbot 146
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(event_user=126): # tef 122 / com 124
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
registration_values = dict(
|
||||
self.website_customer_data[0],
|
||||
|
|
@ -429,7 +430,6 @@ class TestOnlineEventPerformance(EventPerformanceCase, UtilPerf):
|
|||
])
|
||||
|
||||
def _test_url_open(self, url):
|
||||
url += ('?' not in url and '?' or '') + '&debug=disable-t-cache'
|
||||
return self.url_open(url)
|
||||
|
||||
@warmup
|
||||
|
|
@ -437,7 +437,7 @@ class TestOnlineEventPerformance(EventPerformanceCase, UtilPerf):
|
|||
# website customer data
|
||||
with freeze_time(self.reference_now):
|
||||
self.authenticate('user_eventmanager', 'user_eventmanager')
|
||||
with self.assertQueryCount(default=36): # tef 35
|
||||
with self.assertQueryCount(default=35): # tef 34
|
||||
self._test_url_open('/event/%i' % self.test_event.id)
|
||||
|
||||
@warmup
|
||||
|
|
@ -445,7 +445,7 @@ class TestOnlineEventPerformance(EventPerformanceCase, UtilPerf):
|
|||
# website customer data
|
||||
with freeze_time(self.reference_now):
|
||||
self.authenticate(None, None)
|
||||
with self.assertQueryCount(default=27):
|
||||
with self.assertQueryCount(default=25):
|
||||
self._test_url_open('/event/%i' % self.test_event.id)
|
||||
|
||||
@warmup
|
||||
|
|
@ -453,7 +453,7 @@ class TestOnlineEventPerformance(EventPerformanceCase, UtilPerf):
|
|||
# website customer data
|
||||
with freeze_time(self.reference_now):
|
||||
self.authenticate('user_eventmanager', 'user_eventmanager')
|
||||
with self.assertQueryCount(default=39): # tef 38
|
||||
with self.assertQueryCount(default=43): # tef 42
|
||||
self._test_url_open('/event')
|
||||
|
||||
@warmup
|
||||
|
|
@ -461,23 +461,22 @@ class TestOnlineEventPerformance(EventPerformanceCase, UtilPerf):
|
|||
# website customer data
|
||||
with freeze_time(self.reference_now):
|
||||
self.authenticate(None, None)
|
||||
with self.assertQueryCount(default=28):
|
||||
with self.assertQueryCount(default=39):
|
||||
self._test_url_open('/event')
|
||||
|
||||
# @warmup
|
||||
# def test_register_public(self):
|
||||
# with freeze_time(self.reference_now + timedelta(hours=3)): # be sure sales has started
|
||||
# self.assertTrue(self.test_event.event_registrations_started)
|
||||
# self.authenticate(None, None)
|
||||
# with self.assertQueryCount(default=99999): # tef only: 1110
|
||||
# self.browser_js(
|
||||
# '/event/%i/register' % self.test_event.id,
|
||||
# 'odoo.__DEBUG__.services["web_tour.tour"].run("wevent_performance_register")',
|
||||
# 'odoo.__DEBUG__.services["web_tour.tour"].tours.wevent_performance_register.ready',
|
||||
# login=None,
|
||||
# timeout=200,
|
||||
# )
|
||||
@warmup
|
||||
def test_register_public(self):
|
||||
with freeze_time(self.reference_now + timedelta(hours=3)): # be sure sales has started
|
||||
self.assertTrue(self.test_event.event_registrations_started)
|
||||
self.authenticate(None, None)
|
||||
with self.assertQueryCount(default=1197): # tef: 1197
|
||||
self.start_tour(
|
||||
'/event/%i/register' % self.test_event.id,
|
||||
'wevent_performance_register',
|
||||
login=None,
|
||||
timeout=200,
|
||||
)
|
||||
|
||||
# # minimal checkup, to be improved in future tests independently from performance
|
||||
# self.assertEqual(len(self.test_event.registration_ids), 3)
|
||||
# self.assertEqual(len(self.test_event.registration_ids.visitor_id), 1)
|
||||
# minimal checkup, to be improved in future tests independently from performance
|
||||
self.assertEqual(len(self.test_event.registration_ids), 3)
|
||||
self.assertEqual(len(self.test_event.registration_ids.visitor_id), 1)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
from odoo.addons.test_event_full.tests.common import TestWEventCommon
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import users
|
||||
|
||||
|
||||
@tagged('event_online', 'post_install', '-at_install')
|
||||
class TestWEventMenu(TestWEventCommon):
|
||||
|
||||
@users('admin')
|
||||
def test_seo_data(self):
|
||||
"""Test SEO data for submenus on event website page"""
|
||||
|
||||
self.assertFalse(self.event.website_meta_title, 'Event should initially have no meta title')
|
||||
self.event.write({
|
||||
'website_meta_title': 'info',
|
||||
})
|
||||
self.assertTrue(self.event.website_meta_title, 'Event should have a meta title after writing')
|
||||
|
||||
menus = [
|
||||
('booth_menu_ids', 'Get a Booth'),
|
||||
('exhibitor_menu_ids', 'Exhibitor'),
|
||||
('community_menu_ids', 'Leaderboard'),
|
||||
('track_menu_ids', 'Talks'),
|
||||
('track_menu_ids', 'Agenda'),
|
||||
('track_proposal_menu_ids', 'Talk Proposal'),
|
||||
]
|
||||
|
||||
for menu_field, menu_name in menus:
|
||||
menu = self.event[menu_field]
|
||||
|
||||
if menu_field == 'track_menu_ids':
|
||||
menu_url = '/track' if menu_name == 'Talks' else '/agenda'
|
||||
menu = self.event[menu_field].filtered(lambda menu: menu.menu_id.url.endswith(menu_url))
|
||||
|
||||
self.assertFalse(menu.website_meta_title, f"{menu_name} page should initially have no meta title")
|
||||
menu.write({'website_meta_title': menu_name})
|
||||
|
||||
web_page = self.url_open(menu.menu_id.url)
|
||||
|
||||
self.assertTrue(menu.website_meta_title, f"{menu_name} page should have a meta title after writing")
|
||||
self.assertIn(f"<title>{menu.website_meta_title}</title>", web_page.text)
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
from freezegun import freeze_time
|
||||
|
||||
from odoo import tests
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.test_event_full.tests.common import TestWEventCommon
|
||||
|
||||
|
||||
|
|
@ -11,13 +12,9 @@ from odoo.addons.test_event_full.tests.common import TestWEventCommon
|
|||
class TestWEventRegister(TestWEventCommon):
|
||||
|
||||
def test_register(self):
|
||||
self.env.company.country_id = self.env.ref('base.us')
|
||||
with freeze_time(self.reference_now, tick=True):
|
||||
self.browser_js(
|
||||
'/event',
|
||||
'odoo.__DEBUG__.services["web_tour.tour"].run("wevent_register")',
|
||||
'odoo.__DEBUG__.services["web_tour.tour"].tours.wevent_register.ready',
|
||||
login=None
|
||||
)
|
||||
self.start_tour('/event', 'wevent_register', login=None)
|
||||
new_registrations = self.event.registration_ids
|
||||
visitor = new_registrations.visitor_id
|
||||
|
||||
|
|
@ -40,5 +37,15 @@ class TestWEventRegister(TestWEventCommon):
|
|||
self.assertEqual(visitor.display_name, "Raoulette Poiluchette")
|
||||
self.assertEqual(visitor.event_registration_ids, new_registrations)
|
||||
self.assertEqual(visitor.partner_id, self.env['res.partner'])
|
||||
self.assertEqual(visitor.mobile, "0456112233")
|
||||
self.assertEqual(visitor.email, "raoulette@example.com")
|
||||
|
||||
def test_internal_user_register(self):
|
||||
mail_new_test_user(
|
||||
self.env,
|
||||
name='User Internal',
|
||||
login='user_internal',
|
||||
email='user_internal@example.com',
|
||||
groups='base.group_user',
|
||||
)
|
||||
with freeze_time(self.reference_now, tick=True):
|
||||
self.start_tour('/event', 'wevent_register', login='user_internal')
|
||||
|
|
|
|||
|
|
@ -12,38 +12,15 @@ pip install odoo-bringout-oca-ocb-test_mail
|
|||
|
||||
## Dependencies
|
||||
|
||||
This addon depends on:
|
||||
- mail
|
||||
- test_performance
|
||||
|
||||
## Manifest Information
|
||||
|
||||
- **Name**: Mail Tests
|
||||
- **Version**: 1.0
|
||||
- **Category**: Hidden
|
||||
- **License**: LGPL-3
|
||||
- **Installable**: True
|
||||
- test_orm
|
||||
|
||||
## Source
|
||||
|
||||
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `test_mail`.
|
||||
- Repository: https://github.com/OCA/OCB
|
||||
- Branch: 19.0
|
||||
- Path: addons/test_mail
|
||||
|
||||
## License
|
||||
|
||||
This package maintains the original LGPL-3 license from the upstream Odoo project.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Overview: doc/OVERVIEW.md
|
||||
- Architecture: doc/ARCHITECTURE.md
|
||||
- Models: doc/MODELS.md
|
||||
- Controllers: doc/CONTROLLERS.md
|
||||
- Wizards: doc/WIZARDS.md
|
||||
- Reports: doc/REPORTS.md
|
||||
- Security: doc/SECURITY.md
|
||||
- Install: doc/INSTALL.md
|
||||
- Usage: doc/USAGE.md
|
||||
- Configuration: doc/CONFIGURATION.md
|
||||
- Dependencies: doc/DEPENDENCIES.md
|
||||
- Troubleshooting: doc/TROUBLESHOOTING.md
|
||||
- FAQ: doc/FAQ.md
|
||||
This package preserves the original LGPL-3 license.
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
[project]
|
||||
name = "odoo-bringout-oca-ocb-test_mail"
|
||||
version = "16.0.0"
|
||||
description = "Mail Tests - Mail Tests: performances and tests specific to mail"
|
||||
description = "Mail Tests -
|
||||
Mail Tests: performances and tests specific to mail
|
||||
"
|
||||
authors = [
|
||||
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
|
||||
]
|
||||
dependencies = [
|
||||
"odoo-bringout-oca-ocb-mail>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-test_performance>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-mail>=19.0.0",
|
||||
"requests>=2.25.1"
|
||||
]
|
||||
readme = "README.md"
|
||||
|
|
@ -17,7 +18,7 @@ classifiers = [
|
|||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Office/Business",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ present in a separate module as it contains models used only to perform
|
|||
tests independently to functional aspects of other models. """,
|
||||
'depends': [
|
||||
'mail',
|
||||
'test_performance',
|
||||
'test_orm',
|
||||
],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
|
|
@ -21,16 +21,14 @@ tests independently to functional aspects of other models. """,
|
|||
'data/subtype_data.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.qunit_suite_tests': [
|
||||
'test_mail/static/tests/*',
|
||||
'web.assets_unit_tests': [
|
||||
'test_mail/static/tests/**/*',
|
||||
],
|
||||
'web.qunit_mobile_suite_tests': [
|
||||
'test_mail/static/tests/mobile/activity_tests.js',
|
||||
],
|
||||
'web.tests_assets': [
|
||||
'test_mail/static/tests/helpers/*',
|
||||
'web.assets_tests': [
|
||||
'test_mail/static/tests/tours/*',
|
||||
],
|
||||
},
|
||||
'installable': True,
|
||||
'author': 'Odoo S.A.',
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,5 +36,18 @@
|
|||
<field name="chaining_type">trigger</field>
|
||||
<field name="triggered_next_type_id" ref="test_mail.mail_act_test_chained_2"/>
|
||||
</record>
|
||||
<record id="mail_act_test_upload_document" model="mail.activity.type">
|
||||
<field name="name">Document</field>
|
||||
<field name="summary">Document</field>
|
||||
<field name="delay_count">5</field>
|
||||
<field name="category">upload_file</field>
|
||||
<field name="res_model">mail.test.activity</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_act_test_todo_generic" model="mail.activity.type">
|
||||
<field name="name">Do Stuff</field>
|
||||
<field name="summary">Hey Zoidberg! Get in here!</field>
|
||||
<field name="category">default</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
<field name="name">Mail Test Full: Tracking Template</field>
|
||||
<field name="subject">Test Template</field>
|
||||
<field name="partner_to">{{ object.customer_id.id }}</field>
|
||||
<field name="use_default_to" eval="False"/>
|
||||
<field name="body_html" type="html"><p>Hello <t t-out="object.name or ''"></t></p></field>
|
||||
<field name="model_id" ref="test_mail.model_mail_test_ticket"/>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
|
|
@ -13,6 +14,7 @@
|
|||
<field name="name">Mail Test: Template</field>
|
||||
<field name="subject">Post on {{ object.name }}</field>
|
||||
<field name="partner_to">{{ object.customer_id.id }}</field>
|
||||
<field name="use_default_to" eval="False"/>
|
||||
<field name="body_html" type="html"><p>Adding stuff on <t t-out="object.name or ''"></t></p></field>
|
||||
<field name="model_id" ref="test_mail.model_mail_test_container"/>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
|
|
@ -33,6 +35,30 @@
|
|||
</t>
|
||||
</template>
|
||||
|
||||
<template id="mail_test_ticket_test_template_2">
|
||||
<t t-call="web.html_container">
|
||||
<t t-set="o" t-value="res_company"/>
|
||||
<t t-call="web.external_layout">
|
||||
<div class="page">
|
||||
<p>This is another sample of an external report.</p>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="mail_test_ticket_test_variable_template">
|
||||
<t t-call="web.html_container">
|
||||
<t t-foreach="docs" t-as="ticket">
|
||||
<t t-call="web.external_layout">
|
||||
<div class="page">
|
||||
<p>This is a sample of an external report for a ticket for
|
||||
<span t-out="ticket.count"></span> people.</p>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="mail_template_simple_test">
|
||||
<p>Hello <t t-out="partner.name"/>, this comes from <t t-out="object.name"/>.</p>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -52,4 +52,13 @@
|
|||
<field name="internal" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- mail.test.ticket.partner -->
|
||||
<record id="st_mail_test_ticket_partner_new" model="mail.message.subtype">
|
||||
<field name="name">New ticket</field>
|
||||
<field name="description">New Ticket</field>
|
||||
<field name="res_model">mail.test.ticket.partner</field>
|
||||
<field name="default" eval="True"/>
|
||||
<field name="internal" eval="False"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ Subject: {subject}
|
|||
MIME-Version: 1.0
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="----=_Part_4200734_24778174.1344608186754"
|
||||
Date: Fri, 10 Aug 2012 14:16:26 +0000
|
||||
Date: {date}
|
||||
Message-ID: {msg_id}
|
||||
{extra}
|
||||
------=_Part_4200734_24778174.1344608186754
|
||||
|
|
@ -135,6 +135,38 @@ Message-ID: {msg_id}
|
|||
</html>
|
||||
"""
|
||||
|
||||
MAIL_TEMPLATE_SHORT = """Return-Path: {return_path}
|
||||
To: {to}
|
||||
cc: {cc}
|
||||
Received: by mail1.openerp.com (Postfix, from userid 10002)
|
||||
id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
|
||||
From: {email_from}
|
||||
Subject: {subject}
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="----=_Part_4200734_24778174.1344608186754"
|
||||
Date: Fri, 10 Aug 2012 14:16:26 +0000
|
||||
Message-ID: {msg_id}
|
||||
{extra}
|
||||
------=_Part_4200734_24778174.1344608186754
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
Eli alla à l'eau
|
||||
|
||||
--
|
||||
Signature
|
||||
------=_Part_4200734_24778174.1344608186754
|
||||
Content-Type: text/html; charset=utf-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
<div>Eli alla à l'eau<br/>
|
||||
--<br/>
|
||||
Sylvie
|
||||
</div>
|
||||
------=_Part_4200734_24778174.1344608186754--
|
||||
"""
|
||||
|
||||
|
||||
MAIL_MULTIPART_MIXED = """Return-Path: <ignasse.carambar@gmail.com>
|
||||
X-Original-To: raoul@grosbedon.fr
|
||||
Delivered-To: raoul@grosbedon.fr
|
||||
|
|
@ -249,7 +281,7 @@ Date: Sun, 26 Mar 2023 05:23:22 +0200
|
|||
Message-ID: {msg_id}
|
||||
Subject: {subject}
|
||||
From: "Sylvie Lelitre" <test.sylvie.lelitre@agrolait.com>
|
||||
To: groups@test.com
|
||||
To: groups@test.mycompany.com
|
||||
Content-Type: multipart/mixed; boundary="000000000000b951de05f7c47a9e"
|
||||
|
||||
--000000000000b951de05f7c47a9e
|
||||
|
|
@ -648,11 +680,11 @@ AAAAACwAAAAAAgACAAAEA3DJFQA7
|
|||
--001a11416b9e9b229a05272b7052--
|
||||
"""
|
||||
|
||||
MAIL_EML_ATTACHMENT = """Subject: Re: test attac
|
||||
MAIL_EML_ATTACHMENT = """Subject: {subject}
|
||||
From: {email_from}
|
||||
To: {to}
|
||||
References: <f3b9f8f8-28fa-2543-cab2-7aa68f679ebb@odoo.com>
|
||||
Message-ID: <cb7eaf62-58dc-2017-148c-305d0c78892f@odoo.com>
|
||||
References: {references}
|
||||
Message-ID: {msg_id}
|
||||
Date: Wed, 14 Mar 2018 14:26:58 +0100
|
||||
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101
|
||||
Thunderbird/52.6.0
|
||||
|
|
@ -1395,6 +1427,7 @@ Date: Fri, 10 Aug 2012 14:16:26 +0000
|
|||
|
||||
------=_Part_4200734_24778174.1344608186754
|
||||
Content-Type: {pdf_mime}; name="scan_soraya.lernout_1691652648.pdf"
|
||||
Content-Disposition: attachment; filename="scan_soraya.lernout_1691652648.pdf"
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
JVBERi0xLjEKJcKlwrHDqwoKMSAwIG9iagogIDw8IC9UeXBlIC9DYXRhbG9nCiAgICAgL1BhZ2VzIDIgMCBSCiAgPj4KZW5kb2JqCgoyIDAgb2JqCiAgPDwgL1R5cGUgL1BhZ2VzCiAgICAgL0tpZHMgWzMgMCBSXQogICAgIC9Db3VudCAxCiAgICAgL01lZGlhQm94IFswIDAgMzAwIDE0NF0KICA+PgplbmRvYmoKCjMgMCBvYmoKICA8PCAgL1R5cGUgL1BhZ2UKICAgICAgL1BhcmVudCAyIDAgUgogICAgICAvUmVzb3VyY2VzCiAgICAgICA8PCAvRm9udAogICAgICAgICAgIDw8IC9GMQogICAgICAgICAgICAgICA8PCAvVHlwZSAvRm9udAogICAgICAgICAgICAgICAgICAvU3VidHlwZSAvVHlwZTEKICAgICAgICAgICAgICAgICAgL0Jhc2VGb250IC9UaW1lcy1Sb21hbgogICAgICAgICAgICAgICA+PgogICAgICAgICAgID4+CiAgICAgICA+PgogICAgICAvQ29udGVudHMgNCAwIFIKICA+PgplbmRvYmoKCjQgMCBvYmoKICA8PCAvTGVuZ3RoIDU1ID4+CnN0cmVhbQogIEJUCiAgICAvRjEgMTggVGYKICAgIDAgMCBUZAogICAgKEhlbGxvIFdvcmxkKSBUagogIEVUCmVuZHN0cmVhbQplbmRvYmoKCnhyZWYKMCA1CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxOCAwMDAwMCBuIAowMDAwMDAwMDc3IDAwMDAwIG4gCjAwMDAwMDAxNzggMDAwMDAgbiAKMDAwMDAwMDQ1NyAwMDAwMCBuIAp0cmFpbGVyCiAgPDwgIC9Sb290IDEgMCBSCiAgICAgIC9TaXplIDUKICA+PgpzdGFydHhyZWYKNTY1CiUlRU9GCg==
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import mail_test_access
|
||||
from . import mail_test_lead
|
||||
from . import mail_test_ticket
|
||||
from . import test_mail_corner_case_models
|
||||
from . import test_mail_feature_models
|
||||
from . import test_mail_models
|
||||
from . import test_mail_thread_models
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
from odoo import exceptions, fields, models
|
||||
from odoo import fields, models, tools
|
||||
|
||||
|
||||
class MailTestAccess(models.Model):
|
||||
""" Test access on mail models without depending on real models like channel
|
||||
or partner which have their own set of ACLs. """
|
||||
or partner which have their own set of ACLs. Public, portal and internal
|
||||
have access to this model depending on 'access' field, allowing to check
|
||||
ir.rule usage. """
|
||||
_description = 'Mail Access Test'
|
||||
_name = 'mail.test.access'
|
||||
_inherit = ['mail.thread.blacklist']
|
||||
|
|
@ -27,7 +29,7 @@ class MailTestAccess(models.Model):
|
|||
],
|
||||
name='Access', default='public')
|
||||
|
||||
def _mail_get_partner_fields(self):
|
||||
def _mail_get_partner_fields(self, introspect_fields=False):
|
||||
return ['customer_id']
|
||||
|
||||
|
||||
|
|
@ -36,7 +38,7 @@ class MailTestAccessCusto(models.Model):
|
|||
or partner which have their own set of ACLs. """
|
||||
_description = 'Mail Access Test with Custo'
|
||||
_name = 'mail.test.access.custo'
|
||||
_inherit = ['mail.thread.blacklist']
|
||||
_inherit = ['mail.thread.blacklist', 'mail.activity.mixin']
|
||||
_mail_post_access = 'write' # default value but ease mock
|
||||
_order = 'id DESC'
|
||||
_primary_email = 'email_from'
|
||||
|
|
@ -46,15 +48,47 @@ class MailTestAccessCusto(models.Model):
|
|||
phone = fields.Char()
|
||||
customer_id = fields.Many2one('res.partner', 'Customer')
|
||||
is_locked = fields.Boolean()
|
||||
is_readonly = fields.Boolean()
|
||||
|
||||
def _mail_get_partner_fields(self):
|
||||
def _mail_get_partner_fields(self, introspect_fields=False):
|
||||
return ['customer_id']
|
||||
|
||||
def _get_mail_message_access(self, res_ids, operation, model_name=None):
|
||||
# customize message creation
|
||||
if operation == "create":
|
||||
if any(record.is_locked for record in self.browse(res_ids)):
|
||||
raise exceptions.AccessError('Cannot post on locked records')
|
||||
else:
|
||||
return "read"
|
||||
return super()._get_mail_message_access(res_ids, operation, model_name=model_name)
|
||||
def _mail_get_operation_for_mail_message_operation(self, message_operation):
|
||||
# customize message creation: only unlocked, except admins
|
||||
if message_operation == "create" and not self.env.user._is_admin():
|
||||
return dict.fromkeys(self.filtered(lambda r: not r.is_locked), 'read')
|
||||
# customize read: read access on unlocked, write access on locked
|
||||
elif message_operation == "read":
|
||||
return {
|
||||
record: 'write' if record.is_locked else 'read'
|
||||
for record in self
|
||||
}
|
||||
return super()._mail_get_operation_for_mail_message_operation(message_operation)
|
||||
|
||||
|
||||
class MailTestAccessPublic(models.Model):
|
||||
"""A model inheriting from mail.thread with public read and write access
|
||||
to test some public and guest interactions."""
|
||||
_description = "Access Test Public"
|
||||
_name = "mail.test.access.public"
|
||||
_inherit = ["mail.thread"]
|
||||
|
||||
name = fields.Char("Name")
|
||||
customer_id = fields.Many2one('res.partner', 'Customer')
|
||||
email = fields.Char('Email')
|
||||
mobile = fields.Char('Mobile')
|
||||
is_locked = fields.Boolean()
|
||||
|
||||
def _mail_get_partner_fields(self, introspect_fields=False):
|
||||
return ['customer_id']
|
||||
|
||||
def _get_customer_information(self):
|
||||
email_key_to_values = super()._get_customer_information()
|
||||
for record in self.filtered('email'):
|
||||
# do not fill Falsy with random data, unless monorecord (= always correct)
|
||||
if not tools.email_normalize(record.email) and len(self) > 1:
|
||||
continue
|
||||
values = email_key_to_values.setdefault(record.email, {})
|
||||
if not values.get('phone'):
|
||||
values['phone'] = record.mobile
|
||||
return email_key_to_values
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
from odoo import fields, models, _
|
||||
from odoo.tools.mail import parse_contact_from_email
|
||||
|
||||
|
||||
class MailTestTLead(models.Model):
|
||||
""" Lead-like model for business flows testing """
|
||||
_name = "mail.test.lead"
|
||||
_description = 'Lead-like model'
|
||||
_inherit = [
|
||||
'mail.thread.blacklist',
|
||||
'mail.thread.cc',
|
||||
'mail.activity.mixin',
|
||||
]
|
||||
_mail_defaults_to_email = True
|
||||
_primary_email = 'email_from'
|
||||
|
||||
name = fields.Char()
|
||||
company_id = fields.Many2one('res.company')
|
||||
user_id = fields.Many2one('res.users', tracking=1)
|
||||
email_from = fields.Char()
|
||||
customer_name = fields.Char()
|
||||
partner_id = fields.Many2one('res.partner', tracking=2)
|
||||
lang_code = fields.Char()
|
||||
phone = fields.Char()
|
||||
|
||||
def _creation_message(self):
|
||||
self.ensure_one()
|
||||
return _('A new lead has been created and is assigned to %(user_name)s.', user_name=self.user_id.name or _('nobody'))
|
||||
|
||||
def _get_customer_information(self):
|
||||
email_normalized_to_values = super()._get_customer_information()
|
||||
|
||||
for lead in self:
|
||||
email_key = lead.email_normalized or lead.email_from
|
||||
values = email_normalized_to_values.setdefault(email_key, {})
|
||||
values['lang'] = values.get('lang') or lead.lang_code
|
||||
values['name'] = values.get('name') or lead.customer_name or parse_contact_from_email(lead.email_from)[0] or lead.email_from
|
||||
values['phone'] = values.get('phone') or lead.phone
|
||||
return email_normalized_to_values
|
||||
|
||||
def _message_post_after_hook(self, message, msg_vals):
|
||||
if self.email_from and not self.partner_id:
|
||||
# we consider that posting a message with a specified recipient (not a follower, a specific one)
|
||||
# on a document without customer means that it was created through the chatter using
|
||||
# suggested recipients. This heuristic allows to avoid ugly hacks in JS.
|
||||
new_partner = message.partner_ids.filtered(
|
||||
lambda partner: partner.email == self.email_from or (self.email_normalized and partner.email_normalized == self.email_normalized)
|
||||
)
|
||||
if new_partner:
|
||||
if new_partner[0].email_normalized:
|
||||
email_domain = ('email_normalized', '=', new_partner[0].email_normalized)
|
||||
else:
|
||||
email_domain = ('email_from', '=', new_partner[0].email)
|
||||
self.search([('partner_id', '=', False), email_domain]).write({'partner_id': new_partner[0].id})
|
||||
return super()._message_post_after_hook(message, msg_vals)
|
||||
|
|
@ -0,0 +1,266 @@
|
|||
import ast
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.tools import email_normalize
|
||||
|
||||
|
||||
class MailTestTicket(models.Model):
|
||||
""" This model can be used in tests when complex chatter features are
|
||||
required like modeling tasks or tickets. """
|
||||
_description = 'Ticket-like model'
|
||||
_name = "mail.test.ticket"
|
||||
_inherit = ['mail.thread']
|
||||
_primary_email = 'email_from'
|
||||
|
||||
name = fields.Char()
|
||||
email_from = fields.Char(tracking=True)
|
||||
phone_number = fields.Char()
|
||||
count = fields.Integer(default=1)
|
||||
datetime = fields.Datetime(default=fields.Datetime.now)
|
||||
mail_template = fields.Many2one('mail.template', 'Template')
|
||||
customer_id = fields.Many2one('res.partner', 'Customer', tracking=2)
|
||||
user_id = fields.Many2one('res.users', 'Responsible', tracking=1)
|
||||
container_id = fields.Many2one('mail.test.container', tracking=True)
|
||||
|
||||
def _mail_get_partner_fields(self, introspect_fields=False):
|
||||
return ['customer_id']
|
||||
|
||||
def _message_compute_subject(self):
|
||||
self.ensure_one()
|
||||
return f"Ticket for {self.name} on {self.datetime.strftime('%m/%d/%Y, %H:%M:%S')}"
|
||||
|
||||
def _notify_get_recipients_groups(self, message, model_description, msg_vals=False):
|
||||
# Activate more groups to test query counters notably (and be backward compatible for tests)
|
||||
groups = super()._notify_get_recipients_groups(
|
||||
message, model_description, msg_vals=msg_vals
|
||||
)
|
||||
for group_name, _group_method, group_data in groups:
|
||||
if group_name == 'portal':
|
||||
group_data['active'] = True
|
||||
elif group_name == 'customer':
|
||||
group_data['active'] = True
|
||||
group_data['has_button_access'] = True
|
||||
|
||||
return groups
|
||||
|
||||
def _track_template(self, changes):
|
||||
res = super()._track_template(changes)
|
||||
record = self[0]
|
||||
if 'customer_id' in changes and record.mail_template:
|
||||
res['customer_id'] = (
|
||||
record.mail_template,
|
||||
{
|
||||
'composition_mode': 'mass_mail',
|
||||
'subtype_id': self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note'),
|
||||
}
|
||||
)
|
||||
elif 'datetime' in changes:
|
||||
res['datetime'] = (
|
||||
'test_mail.mail_test_ticket_tracking_view',
|
||||
{
|
||||
'composition_mode': 'mass_mail',
|
||||
'subtype_id': self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note'),
|
||||
}
|
||||
)
|
||||
return res
|
||||
|
||||
def _creation_subtype(self):
|
||||
if self.container_id:
|
||||
return self.env.ref('test_mail.st_mail_test_ticket_container_upd')
|
||||
return super(MailTestTicket, self)._creation_subtype()
|
||||
|
||||
def _track_subtype(self, init_values):
|
||||
self.ensure_one()
|
||||
if 'container_id' in init_values and self.container_id:
|
||||
return self.env.ref('test_mail.st_mail_test_ticket_container_upd')
|
||||
return super(MailTestTicket, self)._track_subtype(init_values)
|
||||
|
||||
def _get_customer_information(self):
|
||||
email_keys_to_values = super()._get_customer_information()
|
||||
|
||||
for ticket in self:
|
||||
email_key = email_normalize(ticket.email_from) or ticket.email_from
|
||||
# do not fill Falsy with random data, unless monorecord (= always correct)
|
||||
if not email_key and len(self) > 1:
|
||||
continue
|
||||
values = email_keys_to_values.setdefault(email_key, {})
|
||||
if not values.get('phone'):
|
||||
values['phone'] = ticket.phone_number
|
||||
return email_keys_to_values
|
||||
|
||||
|
||||
class MailTestTicketEl(models.Model):
|
||||
""" Just mail.test.ticket, but exclusion-list enabled. Kept as different
|
||||
model to avoid messing with existing tests, notably performance, and ease
|
||||
backward comparison. """
|
||||
_description = 'Ticket-like model with exclusion list'
|
||||
_name = "mail.test.ticket.el"
|
||||
_inherit = [
|
||||
'mail.test.ticket',
|
||||
'mail.thread.blacklist',
|
||||
]
|
||||
_primary_email = 'email_from'
|
||||
|
||||
email_from = fields.Char(
|
||||
'Email',
|
||||
compute='_compute_email_from', readonly=False, store=True)
|
||||
|
||||
@api.depends('customer_id')
|
||||
def _compute_email_from(self):
|
||||
for ticket in self.filtered(lambda r: r.customer_id and not r.email_from):
|
||||
ticket.email_from = ticket.customer_id.email_formatted
|
||||
|
||||
|
||||
class MailTestTicketMc(models.Model):
|
||||
""" Just mail.test.ticket, but multi company. Kept as different model to
|
||||
avoid messing with existing tests, notably performance, and ease backward
|
||||
comparison. """
|
||||
_description = 'Ticket-like model'
|
||||
_name = "mail.test.ticket.mc"
|
||||
_inherit = ['mail.test.ticket']
|
||||
_primary_email = 'email_from'
|
||||
|
||||
company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env.company)
|
||||
container_id = fields.Many2one('mail.test.container.mc', tracking=True)
|
||||
|
||||
def _get_customer_information(self):
|
||||
email_keys_to_values = super()._get_customer_information()
|
||||
|
||||
for ticket in self:
|
||||
email_key = email_normalize(ticket.email_from) or ticket.email_from
|
||||
# do not fill Falsy with random data, unless monorecord (= always correct)
|
||||
if not email_key and len(self) > 1:
|
||||
continue
|
||||
values = email_keys_to_values.setdefault(email_key, {})
|
||||
if not values.get('company_id'):
|
||||
values['company_id'] = ticket.company_id.id
|
||||
return email_keys_to_values
|
||||
|
||||
def _notify_get_reply_to(self, default=None, author_id=False):
|
||||
# Override to use alias of the parent container
|
||||
aliases = self.sudo().mapped('container_id')._notify_get_reply_to(default=default, author_id=author_id)
|
||||
res = {ticket.id: aliases.get(ticket.container_id.id) for ticket in self}
|
||||
leftover = self.filtered(lambda rec: not rec.container_id)
|
||||
if leftover:
|
||||
res.update(super()._notify_get_reply_to(default=default, author_id=author_id))
|
||||
return res
|
||||
|
||||
def _creation_subtype(self):
|
||||
if self.container_id:
|
||||
return self.env.ref('test_mail.st_mail_test_ticket_container_mc_upd')
|
||||
return super()._creation_subtype()
|
||||
|
||||
def _track_subtype(self, init_values):
|
||||
self.ensure_one()
|
||||
if 'container_id' in init_values and self.container_id:
|
||||
return self.env.ref('test_mail.st_mail_test_ticket_container_mc_upd')
|
||||
return super()._track_subtype(init_values)
|
||||
|
||||
|
||||
class MailTestTicketPartner(models.Model):
|
||||
""" Mail.test.ticket.mc, with complete partner support. More functional
|
||||
and therefore done in a separate model to avoid breaking other tests. """
|
||||
_description = 'MC ticket-like model with partner support'
|
||||
_name = "mail.test.ticket.partner"
|
||||
_inherit = [
|
||||
'mail.test.ticket.mc',
|
||||
'mail.thread.blacklist',
|
||||
]
|
||||
_primary_email = 'email_from'
|
||||
|
||||
# fields to mimic stage-based tracing
|
||||
state = fields.Selection(
|
||||
[('new', 'New'), ('open', 'Open'), ('close', 'Close'),],
|
||||
default='open', tracking=10)
|
||||
state_template_id = fields.Many2one('mail.template')
|
||||
|
||||
def _message_post_after_hook(self, message, msg_vals):
|
||||
if self.email_from and not self.customer_id:
|
||||
# we consider that posting a message with a specified recipient (not a follower, a specific one)
|
||||
# on a document without customer means that it was created through the chatter using
|
||||
# suggested recipients. This heuristic allows to avoid ugly hacks in JS.
|
||||
new_partner = message.partner_ids.filtered(
|
||||
lambda partner: partner.email == self.email_from or (self.email_normalized and partner.email_normalized == self.email_normalized)
|
||||
)
|
||||
if new_partner:
|
||||
if new_partner[0].email_normalized:
|
||||
email_domain = ('email_normalized', '=', new_partner[0].email_normalized)
|
||||
else:
|
||||
email_domain = ('email_from', '=', new_partner[0].email)
|
||||
self.search([
|
||||
('customer_id', '=', False), email_domain,
|
||||
]).write({'customer_id': new_partner[0].id})
|
||||
return super()._message_post_after_hook(message, msg_vals)
|
||||
|
||||
def _creation_subtype(self):
|
||||
if self.state == 'new':
|
||||
return self.env.ref('test_mail.st_mail_test_ticket_partner_new')
|
||||
return super(MailTestTicket, self)._creation_subtype()
|
||||
|
||||
def _track_template(self, changes):
|
||||
res = super()._track_template(changes)
|
||||
record = self[0]
|
||||
# acknowledgement-like email, like in project/helpdesk
|
||||
if 'state' in changes and record.state == 'new' and record.state_template_id:
|
||||
res['state'] = (
|
||||
record.state_template_id,
|
||||
{
|
||||
'auto_delete_keep_log': False,
|
||||
'subtype_id': self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note'),
|
||||
'email_layout_xmlid': 'mail.mail_notification_light'
|
||||
},
|
||||
)
|
||||
return res
|
||||
|
||||
|
||||
class MailTestContainer(models.Model):
|
||||
""" This model can be used in tests when container records like projects
|
||||
or teams are required. """
|
||||
_description = 'Project-like model with alias'
|
||||
_name = "mail.test.container"
|
||||
_mail_post_access = 'read'
|
||||
_inherit = ['mail.thread', 'mail.alias.mixin']
|
||||
|
||||
name = fields.Char()
|
||||
description = fields.Text()
|
||||
customer_id = fields.Many2one('res.partner', 'Customer')
|
||||
|
||||
def _mail_get_partner_fields(self, introspect_fields=False):
|
||||
return ['customer_id']
|
||||
|
||||
def _notify_get_recipients_groups(self, message, model_description, msg_vals=False):
|
||||
# Activate more groups to test query counters notably (and be backward compatible for tests)
|
||||
groups = super()._notify_get_recipients_groups(
|
||||
message, model_description, msg_vals=msg_vals
|
||||
)
|
||||
for group_name, _group_method, group_data in groups:
|
||||
if group_name == 'portal':
|
||||
group_data['active'] = True
|
||||
|
||||
return groups
|
||||
|
||||
def _alias_get_creation_values(self):
|
||||
values = super()._alias_get_creation_values()
|
||||
values['alias_model_id'] = self.env['ir.model']._get('mail.test.ticket').id
|
||||
values['alias_force_thread_id'] = False
|
||||
if self.id:
|
||||
values['alias_defaults'] = defaults = ast.literal_eval(self.alias_defaults or "{}")
|
||||
defaults['container_id'] = self.id
|
||||
return values
|
||||
|
||||
|
||||
class MailTestContainerMc(models.Model):
|
||||
""" Just mail.test.container, but multi company. Kept as different model to
|
||||
avoid messing with existing tests, notably performance, and ease backward
|
||||
comparison. """
|
||||
_description = 'Project-like model with alias (MC)'
|
||||
_name = "mail.test.container.mc"
|
||||
_mail_post_access = 'read'
|
||||
_inherit = ['mail.test.container']
|
||||
|
||||
company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env.company)
|
||||
|
||||
def _alias_get_creation_values(self):
|
||||
values = super()._alias_get_creation_values()
|
||||
values['alias_model_id'] = self.env['ir.model']._get('mail.test.ticket.mc').id
|
||||
return values
|
||||
|
|
@ -21,6 +21,19 @@ class MailPerformanceThread(models.Model):
|
|||
record.value_pc = float(record.value) / 100
|
||||
|
||||
|
||||
class MailPerformanceThreadRecipients(models.Model):
|
||||
_name = 'mail.performance.thread.recipients'
|
||||
_description = 'Performance: mail.thread, for recipients'
|
||||
_inherit = ['mail.thread']
|
||||
_primary_email = 'email_from'
|
||||
|
||||
name = fields.Char()
|
||||
value = fields.Integer()
|
||||
email_from = fields.Char('Email From')
|
||||
partner_id = fields.Many2one('res.partner', string='Customer')
|
||||
user_id = fields.Many2one('res.users', 'Responsible', tracking=1)
|
||||
|
||||
|
||||
class MailPerformanceTracking(models.Model):
|
||||
_name = 'mail.performance.tracking'
|
||||
_description = 'Performance: multi tracking'
|
||||
|
|
@ -36,7 +49,7 @@ class MailTestFieldType(models.Model):
|
|||
""" Test default values, notably type, messing through models during gateway
|
||||
processing (i.e. lead.type versus attachment.type). """
|
||||
_description = 'Test Field Type'
|
||||
_name = 'mail.test.field.type'
|
||||
_name = "mail.test.field.type"
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
name = fields.Char()
|
||||
|
|
@ -49,11 +62,11 @@ class MailTestFieldType(models.Model):
|
|||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
# Emulate an addon that alters the creation context, such as `crm`
|
||||
if not self._context.get('default_type'):
|
||||
if not self.env.context.get('default_type'):
|
||||
self = self.with_context(default_type='first')
|
||||
return super(MailTestFieldType, self).create(vals_list)
|
||||
|
||||
def _mail_get_partner_fields(self):
|
||||
def _mail_get_partner_fields(self, introspect_fields=False):
|
||||
return ['customer_id']
|
||||
|
||||
|
||||
|
|
@ -61,7 +74,7 @@ class MailTestLang(models.Model):
|
|||
""" A simple chatter model with lang-based capabilities, allowing to
|
||||
test translations. """
|
||||
_description = 'Lang Chatter Model'
|
||||
_name = 'mail.test.lang'
|
||||
_name = "mail.test.lang"
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
name = fields.Char()
|
||||
|
|
@ -69,30 +82,85 @@ class MailTestLang(models.Model):
|
|||
customer_id = fields.Many2one('res.partner')
|
||||
lang = fields.Char('Lang')
|
||||
|
||||
def _mail_get_partner_fields(self):
|
||||
def _mail_get_partner_fields(self, introspect_fields=False):
|
||||
return ['customer_id']
|
||||
|
||||
def _notify_get_recipients_groups(self, msg_vals=None):
|
||||
groups = super(MailTestLang, self)._notify_get_recipients_groups(msg_vals=msg_vals)
|
||||
|
||||
local_msg_vals = dict(msg_vals or {})
|
||||
|
||||
def _notify_get_recipients_groups(self, message, model_description, msg_vals=False):
|
||||
groups = super()._notify_get_recipients_groups(
|
||||
message, model_description, msg_vals=msg_vals
|
||||
)
|
||||
for group in [g for g in groups if g[0] in('follower', 'customer')]:
|
||||
group_options = group[2]
|
||||
group_options['has_button_access'] = True
|
||||
group_options['actions'] = [
|
||||
{'url': self._notify_get_action_link('controller', controller='/test_mail/do_stuff', **local_msg_vals),
|
||||
'title': _('NotificationButtonTitle')}
|
||||
]
|
||||
return groups
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# TRACKING MODELS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
class MailTestTrackAllM2m(models.Model):
|
||||
_description = 'Sub-model: pseudo tags for tracking'
|
||||
_name = "mail.test.track.all.m2m"
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
name = fields.Char('Name')
|
||||
|
||||
|
||||
class MailTestTrackAllO2m(models.Model):
|
||||
_description = 'Sub-model: pseudo tags for tracking'
|
||||
_name = "mail.test.track.all.o2m"
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
name = fields.Char('Name')
|
||||
mail_track_all_id = fields.Many2one('mail.test.track.all')
|
||||
|
||||
|
||||
class MailTestTrackAllPropertiesParent(models.Model):
|
||||
_description = 'Properties Parent'
|
||||
_name = "mail.test.track.all.properties.parent"
|
||||
|
||||
definition_properties = fields.PropertiesDefinition()
|
||||
|
||||
|
||||
class MailTestTrackAll(models.Model):
|
||||
_description = 'Test tracking on all field types'
|
||||
_name = "mail.test.track.all"
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
boolean_field = fields.Boolean('Boolean', tracking=1)
|
||||
char_field = fields.Char('Char', tracking=2)
|
||||
company_id = fields.Many2one('res.company')
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id')
|
||||
date_field = fields.Date('Date', tracking=3)
|
||||
datetime_field = fields.Datetime('Datetime', tracking=4)
|
||||
float_field = fields.Float('Float', tracking=5)
|
||||
float_field_with_digits = fields.Float('Precise Float', digits=(10, 8), tracking=5)
|
||||
html_field = fields.Html('Html', tracking=False)
|
||||
integer_field = fields.Integer('Integer', tracking=7)
|
||||
many2many_field = fields.Many2many(
|
||||
'mail.test.track.all.m2m', string='Many2Many',
|
||||
tracking=8)
|
||||
many2one_field_id = fields.Many2one('res.partner', string='Many2one', tracking=9)
|
||||
monetary_field = fields.Monetary('Monetary', tracking=10)
|
||||
one2many_field = fields.One2many(
|
||||
'mail.test.track.all.o2m', 'mail_track_all_id',
|
||||
string='One2Many',
|
||||
tracking=11)
|
||||
properties_parent_id = fields.Many2one('mail.test.track.all.properties.parent', tracking=True)
|
||||
properties = fields.Properties('Properties', definition='properties_parent_id.definition_properties')
|
||||
selection_field = fields.Selection(
|
||||
string='Selection',
|
||||
selection=[('first', 'FIRST'), ('second', 'SECOND')],
|
||||
tracking=12)
|
||||
text_field = fields.Text('Text', tracking=13)
|
||||
|
||||
name = fields.Char('Name')
|
||||
|
||||
|
||||
class MailTestTrackCompute(models.Model):
|
||||
_name = 'mail.test.track.compute'
|
||||
_description = "Test tracking with computed fields"
|
||||
_name = "mail.test.track.compute"
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
partner_id = fields.Many2one('res.partner', tracking=True)
|
||||
|
|
@ -101,63 +169,60 @@ class MailTestTrackCompute(models.Model):
|
|||
partner_phone = fields.Char(related='partner_id.phone', tracking=True)
|
||||
|
||||
|
||||
class MailTestTrackDurationMixin(models.Model):
|
||||
_description = 'Fake model to test the mixin mail.tracking.duration.mixin'
|
||||
_name = "mail.test.track.duration.mixin"
|
||||
_track_duration_field = 'customer_id'
|
||||
_inherit = ['mail.tracking.duration.mixin']
|
||||
|
||||
name = fields.Char()
|
||||
customer_id = fields.Many2one('res.partner', 'Customer', tracking=True)
|
||||
|
||||
def _mail_get_partner_fields(self, introspect_fields=False):
|
||||
return ['customer_id']
|
||||
|
||||
|
||||
class MailTestTrackGroups(models.Model):
|
||||
_description = "Test tracking with groups"
|
||||
_name = "mail.test.track.groups"
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
name = fields.Char(tracking=1)
|
||||
partner_id = fields.Many2one('res.partner', tracking=2, groups="base.group_user")
|
||||
secret = fields.Char(tracking=3, groups="base.group_user")
|
||||
|
||||
|
||||
class MailTestTrackMonetary(models.Model):
|
||||
_name = 'mail.test.track.monetary'
|
||||
_description = 'Test tracking monetary field'
|
||||
_name = "mail.test.track.monetary"
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
company_id = fields.Many2one('res.company')
|
||||
company_currency = fields.Many2one("res.currency", string='Currency', related='company_id.currency_id', readonly=True, tracking=True)
|
||||
revenue = fields.Monetary('Revenue', currency_field='company_currency', tracking=True)
|
||||
|
||||
class MailTestMultiCompanyWithActivity(models.Model):
|
||||
""" This model can be used in multi company tests with activity"""
|
||||
_name = "mail.test.multi.company.with.activity"
|
||||
_description = "Test Multi Company Mail With Activity"
|
||||
_inherit = ["mail.thread", "mail.activity.mixin"]
|
||||
|
||||
name = fields.Char()
|
||||
company_id = fields.Many2one("res.company")
|
||||
|
||||
|
||||
class MailTestSelectionTracking(models.Model):
|
||||
class MailTestTrackSelection(models.Model):
|
||||
""" Test tracking for selection fields """
|
||||
_description = 'Test Selection Tracking'
|
||||
_name = 'mail.test.track.selection'
|
||||
_name = "mail.test.track.selection"
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
name = fields.Char()
|
||||
selection_type = fields.Selection([('first', 'First'), ('second', 'Second')], tracking=True)
|
||||
|
||||
|
||||
class MailTestTrackAll(models.Model):
|
||||
_name = 'mail.test.track.all'
|
||||
_description = 'Test tracking on all field types'
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
boolean_field = fields.Boolean('Boolean', tracking=True)
|
||||
char_field = fields.Char('Char', tracking=True)
|
||||
company_id = fields.Many2one('res.company')
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id')
|
||||
date_field = fields.Date('Date', tracking=True)
|
||||
datetime_field = fields.Datetime('Datetime', tracking=True)
|
||||
float_field = fields.Float('Float', tracking=True)
|
||||
html_field = fields.Html('Html', tracking=True)
|
||||
integer_field = fields.Integer('Integer', tracking=True)
|
||||
many2one_field_id = fields.Many2one('res.partner', string='Many2one', tracking=True)
|
||||
monetary_field = fields.Monetary('Monetary', tracking=True)
|
||||
selection_field = fields.Selection(string='Selection', selection=[['first', 'FIRST']], tracking=True)
|
||||
text_field = fields.Text('Text', tracking=True)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# OTHER
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
class MailTestMultiCompany(models.Model):
|
||||
""" This model can be used in multi company tests"""
|
||||
_name = 'mail.test.multi.company'
|
||||
""" This model can be used in multi company tests, with attachments support
|
||||
for checking record update in MC """
|
||||
_description = "Test Multi Company Mail"
|
||||
_inherit = 'mail.thread'
|
||||
_name = "mail.test.multi.company"
|
||||
_inherit = ['mail.thread.main.attachment']
|
||||
|
||||
name = fields.Char()
|
||||
company_id = fields.Many2one('res.company')
|
||||
|
|
@ -168,16 +233,29 @@ class MailTestMultiCompanyRead(models.Model):
|
|||
even if the user has no write access. """
|
||||
_description = 'Simple Chatter Model '
|
||||
_name = 'mail.test.multi.company.read'
|
||||
_inherit = ['mail.test.multi.company']
|
||||
_inherit = ['mail.test.multi.company', 'mail.activity.mixin']
|
||||
_mail_post_access = 'read'
|
||||
|
||||
|
||||
class MailTestNotMailThread(models.Model):
|
||||
class MailTestMultiCompanyWithActivity(models.Model):
|
||||
""" This model can be used in multi company tests with activity"""
|
||||
_description = "Test Multi Company Mail With Activity"
|
||||
_name = "mail.test.multi.company.with.activity"
|
||||
_inherit = ["mail.thread", "mail.activity.mixin"]
|
||||
|
||||
name = fields.Char()
|
||||
company_id = fields.Many2one("res.company")
|
||||
|
||||
|
||||
class MailTestNothread(models.Model):
|
||||
""" Models not inheriting from mail.thread but using some cross models
|
||||
capabilities of mail. """
|
||||
_name = 'mail.test.nothread'
|
||||
_description = "NoThread Model"
|
||||
_name = "mail.test.nothread"
|
||||
|
||||
name = fields.Char()
|
||||
company_id = fields.Many2one('res.company')
|
||||
customer_id = fields.Many2one('res.partner')
|
||||
|
||||
def _mail_get_partner_fields(self, introspect_fields=False):
|
||||
return ['customer_id']
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
from odoo import api, fields, models
|
||||
from odoo.fields import Domain
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# RECIPIENTS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
class MailTestRecipients(models.Model):
|
||||
_name = 'mail.test.recipients'
|
||||
_description = "Test Recipients Computation"
|
||||
_inherit = ['mail.thread.cc']
|
||||
_primary_email = 'customer_email'
|
||||
|
||||
company_id = fields.Many2one('res.company')
|
||||
contact_ids = fields.Many2many('res.partner')
|
||||
customer_id = fields.Many2one('res.partner')
|
||||
customer_email = fields.Char('Customer Email', compute='_compute_customer_email', readonly=False, store=True)
|
||||
customer_phone = fields.Char('Customer Phone', compute='_compute_customer_phone', readonly=False, store=True)
|
||||
name = fields.Char()
|
||||
|
||||
@api.depends('customer_id')
|
||||
def _compute_customer_email(self):
|
||||
for source in self.filtered(lambda r: r.customer_id and not r.customer_email):
|
||||
source.customer_email = source.customer_id.email_formatted
|
||||
|
||||
@api.depends('customer_id')
|
||||
def _compute_customer_phone(self):
|
||||
for source in self.filtered(lambda r: r.customer_id and not r.customer_phone):
|
||||
source.customer_phone = source.customer_id.phone
|
||||
|
||||
def _mail_get_partner_fields(self, introspect_fields=False):
|
||||
return ['customer_id', 'contact_ids']
|
||||
|
||||
|
||||
class MailTestThreadCustomer(models.Model):
|
||||
_name = 'mail.test.thread.customer'
|
||||
_description = "Test Customer Thread Model"
|
||||
_inherit = ['mail.test.recipients']
|
||||
_mail_thread_customer = True
|
||||
_primary_email = 'customer_email'
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# PROPERTIES
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
class MailTestProperties(models.Model):
|
||||
_name = 'mail.test.properties'
|
||||
_description = 'Mail Test Properties'
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
name = fields.Char('Name')
|
||||
parent_id = fields.Many2one('mail.test.properties', string='Parent')
|
||||
properties = fields.Properties('Properties', definition='parent_id.definition_properties')
|
||||
definition_properties = fields.PropertiesDefinition('Definitions')
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# ROTTING RESOURCES
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
class MailTestStageField(models.Model):
|
||||
_description = 'Fake model to be a stage to help test rotting implementation'
|
||||
_name = 'mail.test.rotting.stage'
|
||||
|
||||
name = fields.Char()
|
||||
rotting_threshold_days = fields.Integer(default=3)
|
||||
no_rot = fields.Boolean(default=False)
|
||||
|
||||
|
||||
class MailTestRottingMixin(models.Model):
|
||||
_description = 'Fake model to test the rotting part of the mixin mail.thread.tracking.duration.mixin'
|
||||
_name = 'mail.test.rotting.resource'
|
||||
_track_duration_field = 'stage_id'
|
||||
_inherit = ['mail.tracking.duration.mixin']
|
||||
|
||||
name = fields.Char()
|
||||
date_last_stage_update = fields.Datetime(
|
||||
'Last Stage Update', compute='_compute_date_last_stage_update', index=True, readonly=True, store=True)
|
||||
stage_id = fields.Many2one('mail.test.rotting.stage', 'Stage')
|
||||
done = fields.Boolean(default=False)
|
||||
|
||||
def _get_rotting_depends_fields(self):
|
||||
return super()._get_rotting_depends_fields() + ['done', 'stage_id.no_rot']
|
||||
|
||||
def _get_rotting_domain(self):
|
||||
return super()._get_rotting_domain() & Domain([
|
||||
('done', '=', False),
|
||||
('stage_id.no_rot', '=', False),
|
||||
])
|
||||
|
||||
@api.depends('stage_id')
|
||||
def _compute_date_last_stage_update(self):
|
||||
self.date_last_stage_update = fields.Datetime.now()
|
||||
|
|
@ -1,6 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
|
||||
|
|
@ -8,29 +5,93 @@ class MailTestSimple(models.Model):
|
|||
""" A very simple model only inheriting from mail.thread when only
|
||||
communication history is necessary. """
|
||||
_description = 'Simple Chatter Model'
|
||||
_name = 'mail.test.simple'
|
||||
_name = "mail.test.simple"
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
name = fields.Char()
|
||||
email_from = fields.Char()
|
||||
|
||||
def _message_compute_subject(self):
|
||||
""" To ease mocks """
|
||||
_a = super()._message_compute_subject()
|
||||
return _a
|
||||
|
||||
def _notify_by_email_get_final_mail_values(self, *args, **kwargs):
|
||||
""" To ease mocks """
|
||||
_a = super()._notify_by_email_get_final_mail_values(*args, **kwargs)
|
||||
return _a
|
||||
|
||||
def _notify_by_email_get_headers(self, headers=None):
|
||||
headers = super()._notify_by_email_get_headers(headers=headers)
|
||||
headers['X-Custom'] = 'Done'
|
||||
return headers
|
||||
|
||||
class MailTestSimpleUnnamed(models.Model):
|
||||
""" A very simple model only inheriting from mail.thread when only
|
||||
communication history is necessary, and has no 'name' field """
|
||||
_description = 'Simple Chatter Model without "name" field'
|
||||
_name = 'mail.test.simple.unnamed'
|
||||
_inherit = ['mail.thread']
|
||||
_rec_name = "description"
|
||||
|
||||
description = fields.Char()
|
||||
|
||||
class MailTestSimpleMainAttachment(models.Model):
|
||||
_description = 'Simple Chatter Model With Main Attachment Management'
|
||||
_name = "mail.test.simple.main.attachment"
|
||||
_inherit = ['mail.test.simple', 'mail.thread.main.attachment']
|
||||
|
||||
|
||||
class MailTestSimpleUnfollow(models.Model):
|
||||
""" A very simple model only inheriting from mail.thread when only
|
||||
communication history is necessary with unfollow link enabled in
|
||||
notification emails even for non-internal user. """
|
||||
_description = 'Simple Chatter Model'
|
||||
_name = "mail.test.simple.unfollow"
|
||||
_inherit = ['mail.thread']
|
||||
_partner_unfollow_enabled = True
|
||||
|
||||
name = fields.Char()
|
||||
company_id = fields.Many2one('res.company')
|
||||
email_from = fields.Char()
|
||||
|
||||
|
||||
class MailTestAliasOptional(models.Model):
|
||||
""" A chatter model inheriting from the alias mixin using optional alias_id
|
||||
field, hence no inherits. """
|
||||
_description = 'Chatter Model using Optional Alias Mixin'
|
||||
_name = "mail.test.alias.optional"
|
||||
_inherit = ['mail.alias.mixin.optional']
|
||||
|
||||
name = fields.Char()
|
||||
company_id = fields.Many2one('res.company', default=lambda self: self.env.company)
|
||||
email_from = fields.Char()
|
||||
|
||||
def _alias_get_creation_values(self):
|
||||
""" Updates itself """
|
||||
values = super()._alias_get_creation_values()
|
||||
values['alias_model_id'] = self.env['ir.model']._get_id('mail.test.alias.optional')
|
||||
if self.id:
|
||||
values['alias_force_thread_id'] = self.id
|
||||
values['alias_defaults'] = {'company_id': self.company_id.id}
|
||||
return values
|
||||
|
||||
|
||||
class MailTestGateway(models.Model):
|
||||
""" A very simple model only inheriting from mail.thread to test pure mass
|
||||
mailing features and base performances. """
|
||||
_description = 'Simple Chatter Model for Mail Gateway'
|
||||
_name = 'mail.test.gateway'
|
||||
_name = "mail.test.gateway"
|
||||
_inherit = ['mail.thread.blacklist']
|
||||
_primary_email = 'email_from'
|
||||
|
||||
name = fields.Char()
|
||||
email_from = fields.Char()
|
||||
custom_field = fields.Char()
|
||||
user_id = fields.Many2one('res.users', 'Responsible')
|
||||
|
||||
@api.model
|
||||
def message_new(self, msg_dict, custom_values=None):
|
||||
""" Check override of 'message_new' allowing to update record values
|
||||
base on incoming email. """
|
||||
defaults = {
|
||||
'email_from': msg_dict.get('from'),
|
||||
}
|
||||
|
|
@ -38,11 +99,32 @@ class MailTestGateway(models.Model):
|
|||
return super().message_new(msg_dict, custom_values=defaults)
|
||||
|
||||
|
||||
class MailTestGatewayCompany(models.Model):
|
||||
""" A very simple model only inheriting from mail.thread to test pure mass
|
||||
mailing features and base performances, with a company field. """
|
||||
_description = 'Simple Chatter Model for Mail Gateway with company'
|
||||
_name = "mail.test.gateway.company"
|
||||
_inherit = ['mail.test.gateway']
|
||||
|
||||
company_id = fields.Many2one('res.company', 'Company')
|
||||
|
||||
|
||||
class MailTestGatewayMainAttachment(models.Model):
|
||||
""" A very simple model only inheriting from mail.thread to test pure mass
|
||||
mailing features and base performances, with a company field and main
|
||||
attachment management. """
|
||||
_description = 'Simple Chatter Model for Mail Gateway with company'
|
||||
_name = "mail.test.gateway.main.attachment"
|
||||
_inherit = ['mail.test.gateway', 'mail.thread.main.attachment']
|
||||
|
||||
company_id = fields.Many2one('res.company', 'Company')
|
||||
|
||||
|
||||
class MailTestGatewayGroups(models.Model):
|
||||
""" A model looking like discussion channels / groups (flat thread and
|
||||
alias). Used notably for advanced gatewxay tests. """
|
||||
_description = 'Channel/Group-like Chatter Model for Mail Gateway'
|
||||
_name = 'mail.test.gateway.groups'
|
||||
_name = "mail.test.gateway.groups"
|
||||
_inherit = ['mail.thread.blacklist', 'mail.alias.mixin']
|
||||
_mail_flat_thread = False
|
||||
_primary_email = 'email_from'
|
||||
|
|
@ -60,25 +142,15 @@ class MailTestGatewayGroups(models.Model):
|
|||
values['alias_parent_thread_id'] = self.id
|
||||
return values
|
||||
|
||||
def _mail_get_partner_fields(self):
|
||||
def _mail_get_partner_fields(self, introspect_fields=False):
|
||||
return ['customer_id']
|
||||
|
||||
def _message_get_default_recipients(self):
|
||||
return dict(
|
||||
(record.id, {
|
||||
'email_cc': False,
|
||||
'email_to': record.email_from if not record.customer_id.ids else False,
|
||||
'partner_ids': record.customer_id.ids,
|
||||
})
|
||||
for record in self
|
||||
)
|
||||
|
||||
|
||||
class MailTestStandard(models.Model):
|
||||
class MailTestTrack(models.Model):
|
||||
""" This model can be used in tests when automatic subscription and simple
|
||||
tracking is necessary. Most features are present in a simple way. """
|
||||
_description = 'Standard Chatter Model'
|
||||
_name = 'mail.test.track'
|
||||
_name = "mail.test.track"
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
name = fields.Char()
|
||||
|
|
@ -86,24 +158,40 @@ class MailTestStandard(models.Model):
|
|||
user_id = fields.Many2one('res.users', 'Responsible', tracking=True)
|
||||
container_id = fields.Many2one('mail.test.container', tracking=True)
|
||||
company_id = fields.Many2one('res.company')
|
||||
track_fields_tofilter = fields.Char() # comma-separated list of field names
|
||||
track_enable_default_log = fields.Boolean(default=False)
|
||||
parent_id = fields.Many2one('mail.test.track', string='Parent')
|
||||
|
||||
def _track_filter_for_display(self, tracking_values):
|
||||
values = super()._track_filter_for_display(tracking_values)
|
||||
filtered_fields = set(self.track_fields_tofilter.split(',') if self.track_fields_tofilter else '')
|
||||
return values.filtered(lambda val: val.field_id.name not in filtered_fields)
|
||||
|
||||
def _track_get_default_log_message(self, changes):
|
||||
filtered_fields = set(self.track_fields_tofilter.split(',') if self.track_fields_tofilter else '')
|
||||
if self.track_enable_default_log and not all(change in filtered_fields for change in changes):
|
||||
return f'There was a change on {self.name} for fields "{",".join(changes)}"'
|
||||
return super()._track_get_default_log_message(changes)
|
||||
|
||||
|
||||
class MailTestActivity(models.Model):
|
||||
""" This model can be used to test activities in addition to simple chatter
|
||||
features. """
|
||||
_description = 'Activity Model'
|
||||
_name = 'mail.test.activity'
|
||||
_name = "mail.test.activity"
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
|
||||
name = fields.Char()
|
||||
date = fields.Date()
|
||||
email_from = fields.Char()
|
||||
active = fields.Boolean(default=True)
|
||||
company_id = fields.Many2one('res.company')
|
||||
|
||||
def action_start(self, action_summary):
|
||||
return self.activity_schedule(
|
||||
'test_mail.mail_act_test_todo',
|
||||
summary=action_summary
|
||||
summary=action_summary,
|
||||
user_id=self.env.uid,
|
||||
)
|
||||
|
||||
def action_close(self, action_feedback, attachment_ids=None):
|
||||
|
|
@ -112,196 +200,18 @@ class MailTestActivity(models.Model):
|
|||
attachment_ids=attachment_ids)
|
||||
|
||||
|
||||
class MailTestTicket(models.Model):
|
||||
""" This model can be used in tests when complex chatter features are
|
||||
required like modeling tasks or tickets. """
|
||||
_description = 'Ticket-like model'
|
||||
_name = 'mail.test.ticket'
|
||||
_inherit = ['mail.thread']
|
||||
_primary_email = 'email_from'
|
||||
|
||||
name = fields.Char()
|
||||
email_from = fields.Char(tracking=True)
|
||||
count = fields.Integer(default=1)
|
||||
datetime = fields.Datetime(default=fields.Datetime.now)
|
||||
mail_template = fields.Many2one('mail.template', 'Template')
|
||||
customer_id = fields.Many2one('res.partner', 'Customer', tracking=2)
|
||||
user_id = fields.Many2one('res.users', 'Responsible', tracking=1)
|
||||
container_id = fields.Many2one('mail.test.container', tracking=True)
|
||||
|
||||
def _mail_get_partner_fields(self):
|
||||
return ['customer_id']
|
||||
|
||||
def _message_get_default_recipients(self):
|
||||
return dict(
|
||||
(record.id, {
|
||||
'email_cc': False,
|
||||
'email_to': record.email_from if not record.customer_id.ids else False,
|
||||
'partner_ids': record.customer_id.ids,
|
||||
})
|
||||
for record in self
|
||||
)
|
||||
|
||||
def _notify_get_recipients_groups(self, msg_vals=None):
|
||||
""" Activate more groups to test query counters notably (and be backward
|
||||
compatible for tests). """
|
||||
local_msg_vals = dict(msg_vals or {})
|
||||
groups = super()._notify_get_recipients_groups(msg_vals=msg_vals)
|
||||
for group_name, _group_method, group_data in groups:
|
||||
if group_name == 'portal':
|
||||
group_data['active'] = True
|
||||
elif group_name == 'customer':
|
||||
group_data['active'] = True
|
||||
group_data['has_button_access'] = True
|
||||
group_data['actions'] = [{
|
||||
'url': self._notify_get_action_link(
|
||||
'controller',
|
||||
controller='/test_mail/do_stuff',
|
||||
**local_msg_vals
|
||||
),
|
||||
'title': _('NotificationButtonTitle')
|
||||
}]
|
||||
|
||||
return groups
|
||||
|
||||
def _track_template(self, changes):
|
||||
res = super(MailTestTicket, self)._track_template(changes)
|
||||
record = self[0]
|
||||
if 'customer_id' in changes and record.mail_template:
|
||||
res['customer_id'] = (record.mail_template, {'composition_mode': 'mass_mail'})
|
||||
elif 'datetime' in changes:
|
||||
res['datetime'] = ('test_mail.mail_test_ticket_tracking_view', {'composition_mode': 'mass_mail'})
|
||||
return res
|
||||
|
||||
def _creation_subtype(self):
|
||||
if self.container_id:
|
||||
return self.env.ref('test_mail.st_mail_test_ticket_container_upd')
|
||||
return super(MailTestTicket, self)._creation_subtype()
|
||||
|
||||
def _track_subtype(self, init_values):
|
||||
self.ensure_one()
|
||||
if 'container_id' in init_values and self.container_id:
|
||||
return self.env.ref('test_mail.st_mail_test_ticket_container_upd')
|
||||
return super(MailTestTicket, self)._track_subtype(init_values)
|
||||
|
||||
|
||||
|
||||
class MailTestTicketEL(models.Model):
|
||||
""" Just mail.test.ticket, but exclusion-list enabled. Kept as different
|
||||
model to avoid messing with existing tests, notably performance, and ease
|
||||
backward comparison. """
|
||||
_description = 'Ticket-like model with exclusion list'
|
||||
_name = 'mail.test.ticket.el'
|
||||
_inherit = [
|
||||
'mail.test.ticket',
|
||||
'mail.thread.blacklist',
|
||||
]
|
||||
_primary_email = 'email_from'
|
||||
|
||||
email_from = fields.Char(
|
||||
'Email',
|
||||
compute='_compute_email_from', readonly=False, store=True)
|
||||
|
||||
@api.depends('customer_id')
|
||||
def _compute_email_from(self):
|
||||
for ticket in self.filtered(lambda r: r.customer_id and not r.email_from):
|
||||
ticket.email_from = ticket.customer_id.email_formatted
|
||||
|
||||
|
||||
class MailTestTicketMC(models.Model):
|
||||
""" Just mail.test.ticket, but multi company. Kept as different model to
|
||||
avoid messing with existing tests, notably performance, and ease backward
|
||||
comparison. """
|
||||
_description = 'Ticket-like model'
|
||||
_name = 'mail.test.ticket.mc'
|
||||
_inherit = ['mail.test.ticket']
|
||||
_primary_email = 'email_from'
|
||||
|
||||
company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env.company)
|
||||
container_id = fields.Many2one('mail.test.container.mc', tracking=True)
|
||||
|
||||
def _creation_subtype(self):
|
||||
if self.container_id:
|
||||
return self.env.ref('test_mail.st_mail_test_ticket_container_mc_upd')
|
||||
return super()._creation_subtype()
|
||||
|
||||
def _track_subtype(self, init_values):
|
||||
self.ensure_one()
|
||||
if 'container_id' in init_values and self.container_id:
|
||||
return self.env.ref('test_mail.st_mail_test_ticket_container_mc_upd')
|
||||
return super()._track_subtype(init_values)
|
||||
|
||||
|
||||
class MailTestContainer(models.Model):
|
||||
""" This model can be used in tests when container records like projects
|
||||
or teams are required. """
|
||||
_description = 'Project-like model with alias'
|
||||
_name = 'mail.test.container'
|
||||
_mail_post_access = 'read'
|
||||
_inherit = ['mail.thread', 'mail.alias.mixin']
|
||||
|
||||
name = fields.Char()
|
||||
description = fields.Text()
|
||||
customer_id = fields.Many2one('res.partner', 'Customer')
|
||||
alias_id = fields.Many2one(
|
||||
'mail.alias', 'Alias',
|
||||
delegate=True)
|
||||
|
||||
def _mail_get_partner_fields(self):
|
||||
return ['customer_id']
|
||||
|
||||
def _message_get_default_recipients(self):
|
||||
return dict(
|
||||
(record.id, {
|
||||
'email_cc': False,
|
||||
'email_to': False,
|
||||
'partner_ids': record.customer_id.ids,
|
||||
})
|
||||
for record in self
|
||||
)
|
||||
|
||||
def _notify_get_recipients_groups(self, msg_vals=None):
|
||||
""" Activate more groups to test query counters notably (and be backward
|
||||
compatible for tests). """
|
||||
groups = super(MailTestContainer, self)._notify_get_recipients_groups(msg_vals=msg_vals)
|
||||
for group_name, _group_method, group_data in groups:
|
||||
if group_name == 'portal':
|
||||
group_data['active'] = True
|
||||
|
||||
return groups
|
||||
|
||||
def _alias_get_creation_values(self):
|
||||
values = super(MailTestContainer, self)._alias_get_creation_values()
|
||||
values['alias_model_id'] = self.env['ir.model']._get('mail.test.container').id
|
||||
if self.id:
|
||||
values['alias_force_thread_id'] = self.id
|
||||
values['alias_parent_thread_id'] = self.id
|
||||
return values
|
||||
|
||||
class MailTestContainerMC(models.Model):
|
||||
""" Just mail.test.container, but multi company. Kept as different model to
|
||||
avoid messing with existing tests, notably performance, and ease backward
|
||||
comparison. """
|
||||
_description = 'Project-like model with alias (MC)'
|
||||
_name = 'mail.test.container.mc'
|
||||
_mail_post_access = 'read'
|
||||
_inherit = ['mail.test.container']
|
||||
|
||||
company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env.company)
|
||||
|
||||
|
||||
class MailTestComposerMixin(models.Model):
|
||||
""" A simple invite-like wizard using the composer mixin, rendering on
|
||||
composer source test model. Purpose is to have a minimal composer which
|
||||
runs on other records and check notably dynamic template support and
|
||||
translations. """
|
||||
_description = 'Invite-like Wizard'
|
||||
_name = 'mail.test.composer.mixin'
|
||||
_name = "mail.test.composer.mixin"
|
||||
_inherit = ['mail.composer.mixin']
|
||||
|
||||
name = fields.Char('Name')
|
||||
author_id = fields.Many2one('res.partner')
|
||||
description = fields.Html('Description', render_engine="qweb", render_options={"post_process": True}, sanitize=False)
|
||||
description = fields.Html('Description', render_engine="qweb", render_options={"post_process": True}, sanitize='email_outgoing')
|
||||
source_ids = fields.Many2many('mail.test.composer.source', string='Invite source')
|
||||
|
||||
def _compute_render_model(self):
|
||||
|
|
@ -310,8 +220,8 @@ class MailTestComposerMixin(models.Model):
|
|||
|
||||
class MailTestComposerSource(models.Model):
|
||||
""" A simple model on which invites are sent. """
|
||||
_description = 'Invite-like Wizard'
|
||||
_name = 'mail.test.composer.source'
|
||||
_description = 'Invite-like Source'
|
||||
_name = "mail.test.composer.source"
|
||||
_inherit = ['mail.thread.blacklist']
|
||||
_primary_email = 'email_from'
|
||||
|
||||
|
|
@ -326,5 +236,5 @@ class MailTestComposerSource(models.Model):
|
|||
for source in self.filtered(lambda r: r.customer_id and not r.email_from):
|
||||
source.email_from = source.customer_id.email_formatted
|
||||
|
||||
def _mail_get_partner_fields(self):
|
||||
def _mail_get_partner_fields(self, introspect_fields=False):
|
||||
return ['customer_id']
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class MailTestCC(models.Model):
|
||||
class MailTestCc(models.Model):
|
||||
_name = 'mail.test.cc'
|
||||
_description = "Test Email CC Thread"
|
||||
_inherit = ['mail.thread.cc']
|
||||
|
|
|
|||
|
|
@ -1,15 +1,29 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_mail_performance_thread,access_mail_performance_thread,model_mail_performance_thread,,1,1,1,1
|
||||
access_mail_performance_thread,access_mail_performance_thread,model_mail_performance_thread,base.group_user,1,1,1,1
|
||||
access_mail_performance_thread_recipients,access_mail_performance_thread_recipients,model_mail_performance_thread_recipients,base.group_user,1,1,1,1
|
||||
access_mail_performance_tracking_user,mail.performance.tracking,model_mail_performance_tracking,base.group_user,1,1,1,1
|
||||
access_mail_test_access_portal,mail.access.portal.portal,model_mail_test_access,base.group_portal,1,1,0,0
|
||||
access_mail_test_access_public,mail.access.portal.public,model_mail_test_access,base.group_public,1,0,0,0
|
||||
access_mail_test_access_user,mail.access.portal.user,model_mail_test_access,base.group_user,1,1,1,1
|
||||
access_mail_test_access_custo_portal,mail.access.portal.portal,model_mail_test_access_custo,base.group_portal,1,0,0,0
|
||||
access_mail_test_access_custo_user,mail.access.portal.user,model_mail_test_access_custo,base.group_user,1,1,1,1
|
||||
access_mail_test_access_public_public,mail.test.access.public.public,model_mail_test_access_public,base.group_public,1,1,0,0
|
||||
access_mail_test_access_public_portal,mail.test.access.public.portal,model_mail_test_access_public,base.group_portal,1,1,0,0
|
||||
access_mail_test_access_public_user,mail.test.access.public.user,model_mail_test_access_public,base.group_user,1,1,1,1
|
||||
access_mail_test_alias_optional_portal,mail.test.alias.optional.portal,model_mail_test_alias_optional,base.group_portal,1,0,0,0
|
||||
access_mail_test_alias_optional_user,mail.test.alias.optional.user,model_mail_test_alias_optional,base.group_user,1,1,1,1
|
||||
access_mail_test_simple_portal,mail.test.simple.portal,model_mail_test_simple,base.group_portal,1,0,0,0
|
||||
access_mail_test_simple_user,mail.test.simple.user,model_mail_test_simple,base.group_user,1,1,1,1
|
||||
access_mail_test_simple_unnamed_portal,mail.test.simple.unnamed.portal,model_mail_test_simple_unnamed,base.group_portal,1,0,0,0
|
||||
access_mail_test_simple_unnamed_user,mail.test.simple.unnamed.user,model_mail_test_simple_unnamed,base.group_user,1,1,1,1
|
||||
access_mail_test_simple_unfollow_portal,mail.test.simple.unfollow.portal,model_mail_test_simple_unfollow,base.group_portal,0,0,0,0
|
||||
access_mail_test_simple_unfollow_user,mail.test.simple.unfollow.user,model_mail_test_simple_unfollow,base.group_user,1,1,1,1
|
||||
access_mail_test_simple_main_attachment_portal,mail.test.simple.main.attachment.portal,model_mail_test_simple_main_attachment,base.group_portal,1,0,0,0
|
||||
access_mail_test_simple_main_attachment_user,mail.test.simple.main.attachment.user,model_mail_test_simple_main_attachment,base.group_user,1,1,1,1
|
||||
access_mail_test_gateway_portal,mail.test.gateway.portal,model_mail_test_gateway,base.group_portal,1,0,0,0
|
||||
access_mail_test_gateway_user,mail.test.gateway.user,model_mail_test_gateway,base.group_user,1,1,1,1
|
||||
access_mail_test_gateway_company_user,mail.test.gateway.company.user,model_mail_test_gateway_company,base.group_user,1,1,1,1
|
||||
access_mail_test_gateway_main_attachment_user,mail.test.gateway.main.attachment.user,model_mail_test_gateway_main_attachment,base.group_user,1,1,1,1
|
||||
access_mail_test_gateway_groups_portal,mail.test.gateway.groups.portal,model_mail_test_gateway_groups,base.group_portal,1,0,0,0
|
||||
access_mail_test_gateway_groups_user,mail.test.gateway.groups.user,model_mail_test_gateway_groups,base.group_user,1,1,1,1
|
||||
access_mail_test_track_portal,mail.test.track.portal,model_mail_test_track,base.group_portal,0,0,0,0
|
||||
|
|
@ -18,15 +32,18 @@ access_mail_test_activity_portal,mail.test.activity.portal,model_mail_test_activ
|
|||
access_mail_test_activity_user,mail.test.activity.user,model_mail_test_activity,base.group_user,1,1,1,1
|
||||
access_mail_test_field_type_portal,mail.test.field.type.portal,model_mail_test_field_type,base.group_portal,0,0,0,0
|
||||
access_mail_test_field_type_user,mail.test.field.type.user,model_mail_test_field_type,base.group_user,1,1,1,1
|
||||
access_mail_test_lead_user,mail.test.lead.user,model_mail_test_lead,base.group_user,1,1,1,1
|
||||
access_mail_test_ticket_portal,mail.test.ticket.portal,model_mail_test_ticket,base.group_portal,1,0,0,0
|
||||
access_mail_test_ticket_user,mail.test.ticket.user,model_mail_test_ticket,base.group_user,1,1,1,1
|
||||
access_mail_test_ticket_el_portal,mail.test.ticket.el.portal,model_mail_test_ticket_el,base.group_portal,1,0,0,0
|
||||
access_mail_test_ticket_el_user,mail.test.ticket.el.user,model_mail_test_ticket_el,base.group_user,1,1,1,1
|
||||
access_mail_test_ticket_mc_portal,mail.test.ticket.mc.portal,model_mail_test_ticket_mc,base.group_portal,1,0,0,0
|
||||
access_mail_test_ticket_mc_user,mail.test.ticket.mc.user,model_mail_test_ticket_mc,base.group_user,1,1,1,1
|
||||
access_mail_test_ticket_partner_portal,mail.test.ticket.partner.portal,model_mail_test_ticket_partner,base.group_portal,1,0,0,0
|
||||
access_mail_test_ticket_partner_user,mail.test.ticket.partner.user,model_mail_test_ticket_partner,base.group_user,1,1,1,1
|
||||
access_mail_test_composer_mixin_all,mail.test.composer.mixin.all,model_mail_test_composer_mixin,,0,0,0,0
|
||||
access_mail_test_composer_mixin_user,mail.test.composer.mixin.user,model_mail_test_composer_mixin,base.group_user,1,1,1,1
|
||||
access_mail_test_composer_source_all,mail.test.composer.source.all,model_mail_test_composer_source,,1,0,0,0
|
||||
access_mail_test_composer_source_all,mail.test.composer.source.all,model_mail_test_composer_source,base.group_user,1,0,0,0
|
||||
access_mail_test_composer_source_user,mail.test.composer.source.user,model_mail_test_composer_source,base.group_user,1,1,1,1
|
||||
access_mail_test_container_portal,mail.test.container_portal,model_mail_test_container,base.group_portal,1,0,0,0
|
||||
access_mail_test_container_user,mail.test.container.user,model_mail_test_container,base.group_user,1,1,1,1
|
||||
|
|
@ -44,8 +61,18 @@ access_mail_test_multi_company_with_activity_user,mail.test.multi.company.with.a
|
|||
access_mail_test_multi_company_with_activity_portal,mail.test.multi.company.with.activity.portal,model_mail_test_multi_company_with_activity,base.group_portal,1,0,0,0
|
||||
access_mail_test_nothread_user,mail.test.nothread.user,model_mail_test_nothread,base.group_user,1,1,1,1
|
||||
access_mail_test_nothread_portal,mail.test.nothread.portal,model_mail_test_nothread,base.group_portal,1,0,0,0
|
||||
access_mail_test_recipients_user,mail.test.recipients.user,model_mail_test_recipients,base.group_user,1,1,1,1
|
||||
access_mail_test_rotting_resource,mail.test.rotting.resource,model_mail_test_rotting_resource,base.group_user,1,1,1,1
|
||||
access_mail_test_rotting_stage,mail.test.rotting.stage,model_mail_test_rotting_stage,base.group_user,1,1,1,1
|
||||
access_mail_test_thread_customer_user,mail.test.thread.customer.user,model_mail_test_thread_customer,base.group_user,1,1,1,1
|
||||
access_mail_test_track_all,mail.test.track.all,model_mail_test_track_all,base.group_user,1,1,1,1
|
||||
access_mail_test_track_all_properties_parent,access_mail_test_track_all_properties_parent,model_mail_test_track_all_properties_parent,base.group_user,1,0,0,0
|
||||
access_mail_test_track_all_m2m,mail.test.track.all.m2m,model_mail_test_track_all_m2m,base.group_user,1,1,1,1
|
||||
access_mail_test_track_all_o2m,mail.test.track.all.o2m,model_mail_test_track_all_o2m,base.group_user,1,1,1,1
|
||||
access_mail_test_track_compute,mail.test.track.compute,model_mail_test_track_compute,base.group_user,1,1,1,1
|
||||
access_mail_test_track_groups,mail.test.track.groups,model_mail_test_track_groups,base.group_user,1,1,1,1
|
||||
access_mail_test_track_monetary,mail.test.track.monetary,model_mail_test_track_monetary,base.group_user,1,1,1,1
|
||||
access_mail_test_track_selection_portal,mail.test.track.selection.portal,model_mail_test_track_selection,base.group_portal,0,0,0,0
|
||||
access_mail_test_track_selection_user,mail.test.track.selection.user,model_mail_test_track_selection,base.group_user,1,1,1,1
|
||||
access_mail_test_properties_user,mail.test.properties.user,model_mail_test_properties,base.group_user,1,1,1,1
|
||||
access_mail_test_track_duration_mixin,mail.test.track.duration.mixin,model_mail_test_track_duration_mixin,base.group_user,1,1,1,1
|
||||
|
|
|
|||
|
|
|
@ -1,6 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo noupdate="1">
|
||||
|
||||
<!-- Having rules triggers call to check_access_rules and allow to spot crashes
|
||||
notably when records are unlinked. Without rule, method is not called and
|
||||
some crashes are not trigerred in tests. -->
|
||||
<record id="ir_rule_mail_test_simple_dummy" model="ir.rule">
|
||||
<field name="name">Dummy rule, just to enable rule evaluation, shows some specific errors</field>
|
||||
<field name="model_id" ref="test_mail.model_mail_test_simple"/>
|
||||
<field name="domain_force">[('email_from', '!=', 'donotsetmewiththisvalue')]</field>
|
||||
</record>
|
||||
|
||||
<!-- MAIL.TEST.ACCESS -->
|
||||
<record id="ir_rule_mail_test_access_public" model="ir.rule">
|
||||
<field name="name">Public: public only</field>
|
||||
<field name="model_id" ref="test_mail.model_mail_test_access"/>
|
||||
|
|
@ -55,21 +65,45 @@
|
|||
<field name="groups" eval="[(4, ref('base.group_system'))]"/>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="mail_test_multi_company_rule" model="ir.rule">
|
||||
<field name="name">Mail Test Multi Company</field>
|
||||
<field name="model_id" ref="test_mail.model_mail_test_multi_company"/>
|
||||
<field eval="True" name="global"/>
|
||||
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
|
||||
<!-- MAIL.TEST.ACCESS.CUSTO -->
|
||||
<record id="ir_rule_mail_test_access_custo_portal_read" model="ir.rule">
|
||||
<field name="name">Portal: read unlocked</field>
|
||||
<field name="model_id" ref="test_mail.model_mail_test_access_custo"/>
|
||||
<field name="domain_force">[('is_locked', '=', False)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_unlink" eval="False"/>
|
||||
</record>
|
||||
<record id="ir_rule_mail_test_access_custo_update" model="ir.rule">
|
||||
<field name="name">Internal: create/write/unlink unlocked and not readonly</field>
|
||||
<field name="model_id" ref="test_mail.model_mail_test_access_custo"/>
|
||||
<field name="domain_force">[('is_readonly', '=', False), ('is_locked', '=', False)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_user')), (4, ref('base.group_portal'))]"/>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_unlink" eval="True"/>
|
||||
</record>
|
||||
<record id="ir_rule_mail_test_access_custo_update_admin" model="ir.rule">
|
||||
<field name="name">Admin: all</field>
|
||||
<field name="model_id" ref="test_mail.model_mail_test_access_custo"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_system'))]"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_unlink" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- MAIL.TEST.MULTI.COMPANY(.*) -->
|
||||
<record id="mail_test_multi_company_rule" model="ir.rule">
|
||||
<field name="name">Mail Test Multi Company</field>
|
||||
<field name="model_id" ref="test_mail.model_mail_test_multi_company"/>
|
||||
<field eval="True" name="global"/>
|
||||
<field name="domain_force">[('company_id', 'in', company_ids + [False])]</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_test_multi_company_read_rule" model="ir.rule">
|
||||
<field name="name">MC Readonly Rule</field>
|
||||
<field name="model_id" ref="test_mail.model_mail_test_multi_company_read"/>
|
||||
|
|
@ -77,7 +111,6 @@
|
|||
<field name="domain_force">[('company_id', 'in', company_ids + [False])]</field>
|
||||
<field name="global" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="mail_test_multi_company_with_activity_rule" model="ir.rule">
|
||||
<field name="name">Mail Test Multi Company With Activity</field>
|
||||
<field name="model_id" ref="test_mail.model_mail_test_multi_company_with_activity"/>
|
||||
|
|
@ -85,15 +118,13 @@
|
|||
<field name="domain_force">[('company_id', 'in', company_ids + [False])]</field>
|
||||
</record>
|
||||
|
||||
<!-- TICKET-LIKE -->
|
||||
<!-- MAIL.TEST.TICKET(.*) (TICKET-LIKE) -->
|
||||
<record id="mail_test_ticket_rule_portal" model="ir.rule">
|
||||
<field name="name">Portal Mail Test Ticket</field>
|
||||
<field name="model_id" ref="test_mail.model_mail_test_ticket"/>
|
||||
<field name="domain_force">[('message_partner_ids', 'in', [user.partner_id.id])]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
|
||||
</record>
|
||||
|
||||
<!-- MULTI COMPANY TICKET LIKE -->
|
||||
<record id="mail_test_ticket_mc_rule" model="ir.rule">
|
||||
<field name="name">Mail Test Ticket Multi Company</field>
|
||||
<field name="model_id" ref="test_mail.model_mail_test_ticket_mc"/>
|
||||
|
|
@ -106,16 +137,26 @@
|
|||
<field name="domain_force">[('message_partner_ids', 'in', [user.partner_id.id])]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
|
||||
</record>
|
||||
<record id="mail_test_ticket_partner_rule" model="ir.rule">
|
||||
<field name="name">Mail Test Ticket Multi Company Partner</field>
|
||||
<field name="model_id" ref="test_mail.model_mail_test_ticket_partner"/>
|
||||
<field name="global" eval="True"/>
|
||||
<field name="domain_force">[('company_id', 'in', company_ids + [False])]</field>
|
||||
</record>
|
||||
<record id="mail_test_ticket_partner_rule_portal" model="ir.rule">
|
||||
<field name="name">Portal Mail Test Ticket Multi Company Partner</field>
|
||||
<field name="model_id" ref="test_mail.model_mail_test_ticket_partner"/>
|
||||
<field name="domain_force">[('message_partner_ids', 'in', [user.partner_id.id])]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
|
||||
</record>
|
||||
|
||||
<!-- PROJECT-LIKE -->
|
||||
<!-- MAIL.TEST.CONTAINER(.*) (PROJECT-LIKE) -->
|
||||
<record id="mail_test_container_rule_portal" model="ir.rule">
|
||||
<field name="name">Portal Mail Test Container</field>
|
||||
<field name="model_id" ref="test_mail.model_mail_test_container"/>
|
||||
<field name="domain_force">[('message_partner_ids', 'in', [user.partner_id.id])]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
|
||||
</record>
|
||||
|
||||
<!-- MULTI COMPANY PROJECT LIKE -->
|
||||
<record id="mail_test_container_mc_rule" model="ir.rule">
|
||||
<field name="name">Mail Test Container Multi Company</field>
|
||||
<field name="model_id" ref="test_mail.model_mail_test_container_mc"/>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,35 @@
|
|||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { defineTestMailModels } from "@test_mail/../tests/test_mail_test_helpers";
|
||||
import { openView, start, startServer } from "@mail/../tests/mail_test_helpers";
|
||||
|
||||
describe.current.tags("mobile");
|
||||
defineTestMailModels();
|
||||
|
||||
test("horizontal scroll applies only to the content, not to the whole controller", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["mail.activity.type"].create([
|
||||
{ name: "Email" },
|
||||
{ name: "Call" },
|
||||
{ name: "Upload document" },
|
||||
]);
|
||||
await start();
|
||||
await openView({
|
||||
res_model: "mail.test.activity",
|
||||
views: [[false, "activity"]],
|
||||
});
|
||||
const o_view_controller = document.querySelector(".o_view_controller");
|
||||
const o_content = o_view_controller.querySelector(".o_content");
|
||||
const o_cp_item = document.querySelector(".o_breadcrumb .active");
|
||||
const initialXCpItem = o_cp_item.getBoundingClientRect().x;
|
||||
const o_header_cell = o_content.querySelector(".o_activity_type_cell");
|
||||
const initialXHeaderCell = o_header_cell.getBoundingClientRect().x;
|
||||
expect(o_view_controller).toHaveClass("o_action_delegate_scroll");
|
||||
expect(o_view_controller).toHaveStyle({ overflow: "hidden" });
|
||||
expect(o_content).toHaveStyle({ overflow: "auto" });
|
||||
expect(o_content.scrollLeft).toBe(0);
|
||||
|
||||
o_content.scrollLeft = 100;
|
||||
expect(o_content.scrollLeft).toBe(100);
|
||||
expect(o_header_cell.getBoundingClientRect().x).toBeLessThan(initialXHeaderCell);
|
||||
expect(o_cp_item).toHaveRect({ x: initialXCpItem });
|
||||
});
|
||||
|
|
@ -1,676 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import ActivityRenderer from '@mail/js/views/activity/activity_renderer';
|
||||
import { start, startServer } from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
import testUtils from 'web.test_utils';
|
||||
import { click, insertText } from "@web/../tests/utils";
|
||||
import { legacyExtraNextTick, patchWithCleanup} from "@web/../tests/helpers/utils";
|
||||
import { doAction } from "@web/../tests/webclient/helpers";
|
||||
import { session } from '@web/session';
|
||||
|
||||
let serverData;
|
||||
let pyEnv;
|
||||
|
||||
QUnit.module('test_mail', {}, function () {
|
||||
QUnit.module('activity view', {
|
||||
async beforeEach() {
|
||||
pyEnv = await startServer();
|
||||
const mailTemplateIds = pyEnv['mail.template'].create([{ name: "Template1" }, { name: "Template2" }]);
|
||||
// reset incompatible setup
|
||||
pyEnv['mail.activity.type'].unlink(pyEnv['mail.activity.type'].search([]));
|
||||
const mailActivityTypeIds = pyEnv['mail.activity.type'].create([
|
||||
{ name: "Email", mail_template_ids: mailTemplateIds },
|
||||
{ name: "Call" },
|
||||
{ name: "Call for Demo" },
|
||||
{ name: "To Do" },
|
||||
]);
|
||||
const resUsersId1 = pyEnv['res.users'].create({ display_name: 'first user' });
|
||||
const mailActivityIds = pyEnv['mail.activity'].create([
|
||||
{
|
||||
display_name: "An activity",
|
||||
date_deadline: moment().add(3, "days").format("YYYY-MM-DD"), // now
|
||||
can_write: true,
|
||||
state: "planned",
|
||||
activity_type_id: mailActivityTypeIds[0],
|
||||
mail_template_ids: mailTemplateIds,
|
||||
user_id: resUsersId1,
|
||||
},
|
||||
{
|
||||
display_name: "An activity",
|
||||
date_deadline: moment().format("YYYY-MM-DD"), // now
|
||||
can_write: true,
|
||||
state: "today",
|
||||
activity_type_id: mailActivityTypeIds[0],
|
||||
mail_template_ids: mailTemplateIds,
|
||||
user_id: resUsersId1,
|
||||
},
|
||||
{
|
||||
res_model: 'mail.test.activity',
|
||||
display_name: "An activity",
|
||||
date_deadline: moment().subtract(2, "days").format("YYYY-MM-DD"), // now
|
||||
can_write: true,
|
||||
state: "overdue",
|
||||
activity_type_id: mailActivityTypeIds[1],
|
||||
user_id: resUsersId1,
|
||||
},
|
||||
]);
|
||||
pyEnv['mail.test.activity'].create([
|
||||
{ name: 'Meeting Room Furnitures', activity_ids: [mailActivityIds[0]] },
|
||||
{ name: 'Office planning', activity_ids: [mailActivityIds[1], mailActivityIds[2]] },
|
||||
]);
|
||||
serverData = {
|
||||
views: {
|
||||
'mail.test.activity,false,activity':
|
||||
'<activity string="MailTestActivity">' +
|
||||
'<templates>' +
|
||||
'<div t-name="activity-box">' +
|
||||
'<field name="name"/>' +
|
||||
'</div>' +
|
||||
'</templates>' +
|
||||
'</activity>',
|
||||
'mail.test.activity,false,form':
|
||||
'<form string="MailTestActivity">' +
|
||||
'<field name="name"/>' +
|
||||
'</form>',
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
var activityDateFormat = function (date) {
|
||||
return date.toLocaleDateString(moment().locale(), { day: 'numeric', month: 'short' });
|
||||
};
|
||||
|
||||
QUnit.test('activity view: simple activity rendering', async function (assert) {
|
||||
assert.expect(15);
|
||||
const mailTestActivityIds = pyEnv['mail.test.activity'].search([]);
|
||||
const mailActivityTypeIds = pyEnv['mail.activity.type'].search([]);
|
||||
|
||||
const { click , env, openView } = await start({
|
||||
serverData,
|
||||
});
|
||||
await openView({
|
||||
res_model: "mail.test.activity",
|
||||
views: [[false, "activity"], [false, "form"]],
|
||||
});
|
||||
patchWithCleanup(env.services.action, {
|
||||
doAction(action, options) {
|
||||
assert.deepEqual(action, {
|
||||
context: {
|
||||
default_res_id: mailTestActivityIds[1],
|
||||
default_res_model: "mail.test.activity",
|
||||
default_activity_type_id: mailActivityTypeIds[2],
|
||||
},
|
||||
res_id: false,
|
||||
res_model: "mail.activity",
|
||||
target: "new",
|
||||
type: "ir.actions.act_window",
|
||||
view_mode: "form",
|
||||
view_type: "form",
|
||||
views: [[false, "form"]]
|
||||
},
|
||||
"should do a do_action with correct parameters");
|
||||
options.onClose();
|
||||
return Promise.resolve();
|
||||
},
|
||||
});
|
||||
|
||||
const $activity = $(document.querySelector('.o_activity_view'));
|
||||
assert.containsOnce($activity, 'table',
|
||||
'should have a table');
|
||||
var $th1 = $activity.find('table thead tr:first th:nth-child(2)');
|
||||
assert.containsOnce($th1, 'span:first:contains(Email)', 'should contain "Email" in header of first column');
|
||||
assert.containsOnce($th1, '.o_legacy_kanban_counter', 'should contain a progressbar in header of first column');
|
||||
assert.hasAttrValue($th1.find('.o_kanban_counter_progress .progress-bar:first'), 'data-bs-original-title', '1 Planned',
|
||||
'the counter progressbars should be correctly displayed');
|
||||
assert.hasAttrValue($th1.find('.o_kanban_counter_progress .progress-bar:nth-child(2)'), 'data-bs-original-title', '1 Today',
|
||||
'the counter progressbars should be correctly displayed');
|
||||
var $th2 = $activity.find('table thead tr:first th:nth-child(3)');
|
||||
assert.containsOnce($th2, 'span:first:contains(Call)', 'should contain "Call" in header of second column');
|
||||
assert.hasAttrValue($th2.find('.o_kanban_counter_progress .progress-bar:nth-child(3)'), 'data-bs-original-title', '1 Overdue',
|
||||
'the counter progressbars should be correctly displayed');
|
||||
assert.containsNone($activity, 'table thead tr:first th:nth-child(4) .o_kanban_counter',
|
||||
'should not contain a progressbar in header of 3rd column');
|
||||
assert.ok($activity.find('table tbody tr:first td:first:contains(Office planning)').length,
|
||||
'should contain "Office planning" in first colum of first row');
|
||||
assert.ok($activity.find('table tbody tr:nth-child(2) td:first:contains(Meeting Room Furnitures)').length,
|
||||
'should contain "Meeting Room Furnitures" in first colum of second row');
|
||||
|
||||
var today = activityDateFormat(new Date());
|
||||
|
||||
assert.ok($activity.find('table tbody tr:first td:nth-child(2).today .o_closest_deadline:contains(' + today + ')').length,
|
||||
'should contain an activity for today in second cell of first line ' + today);
|
||||
var td = 'table tbody tr:nth-child(1) td.o_activity_empty_cell';
|
||||
assert.containsN($activity, td, 2, 'should contain an empty cell as no activity scheduled yet.');
|
||||
|
||||
// schedule an activity (this triggers a do_action)
|
||||
await testUtils.fields.editAndTrigger($activity.find(td + ':first'), null, ['mouseenter', 'click']);
|
||||
assert.containsOnce($activity, 'table tfoot tr .o_record_selector',
|
||||
'should contain search more selector to choose the record to schedule an activity for it');
|
||||
|
||||
// Ensure that the form view is opened in edit mode
|
||||
await click(document.querySelector(".o_activity_record"));
|
||||
const $form = $(document.querySelector('.o_form_view'));
|
||||
assert.containsOnce($form, '.o_form_editable',
|
||||
'Form view should be opened in edit mode');
|
||||
});
|
||||
|
||||
QUnit.test('activity view: no content rendering', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const { openView, pyEnv } = await start({
|
||||
serverData,
|
||||
});
|
||||
// reset incompatible setup
|
||||
pyEnv['mail.activity.type'].unlink(pyEnv['mail.activity.type'].search([]));
|
||||
await openView({
|
||||
res_model: "mail.test.activity",
|
||||
views: [[false, "activity"]],
|
||||
});
|
||||
const $activity = $(document);
|
||||
|
||||
assert.containsOnce($activity, '.o_view_nocontent',
|
||||
"should display the no content helper");
|
||||
assert.strictEqual($activity.find('.o_view_nocontent .o_view_nocontent_empty_folder').text().trim(),
|
||||
"No data to display",
|
||||
"should display the no content helper text");
|
||||
});
|
||||
|
||||
QUnit.test('activity view: batch send mail on activity', async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
const mailTestActivityIds = pyEnv['mail.test.activity'].search([]);
|
||||
const mailTemplateIds = pyEnv['mail.template'].search([]);
|
||||
const { openView } = await start({
|
||||
serverData,
|
||||
mockRPC: function(route, args) {
|
||||
if (args.method === 'activity_send_mail') {
|
||||
assert.step(JSON.stringify(args.args));
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
},
|
||||
});
|
||||
await openView({
|
||||
res_model: "mail.test.activity",
|
||||
views: [[false, "activity"]],
|
||||
});
|
||||
const $activity = $(document);
|
||||
assert.notOk($activity.find('table thead tr:first th:nth-child(2) span:nth-child(2) .dropdown-menu.show').length,
|
||||
'dropdown shouldn\'t be displayed');
|
||||
|
||||
testUtils.dom.click($activity.find('table thead tr:first th:nth-child(2) span:nth-child(2) i.fa-ellipsis-v'));
|
||||
assert.ok($activity.find('table thead tr:first th:nth-child(2) span:nth-child(2) .dropdown-menu.show').length,
|
||||
'dropdown should have appeared');
|
||||
|
||||
testUtils.dom.click($activity.find('table thead tr:first th:nth-child(2) span:nth-child(2) .dropdown-menu.show .o_send_mail_template:contains(Template2)'));
|
||||
assert.notOk($activity.find('table thead tr:first th:nth-child(2) span:nth-child(2) .dropdown-menu.show').length,
|
||||
'dropdown shouldn\'t be displayed');
|
||||
|
||||
testUtils.dom.click($activity.find('table thead tr:first th:nth-child(2) span:nth-child(2) i.fa-ellipsis-v'));
|
||||
testUtils.dom.click($activity.find('table thead tr:first th:nth-child(2) span:nth-child(2) .dropdown-menu.show .o_send_mail_template:contains(Template1)'));
|
||||
assert.verifySteps([
|
||||
`[[${mailTestActivityIds[0]},${mailTestActivityIds[1]}],${mailTemplateIds[1]}]`, // send mail template 1 on mail.test.activity 1 and 2
|
||||
`[[${mailTestActivityIds[0]},${mailTestActivityIds[1]}],${mailTemplateIds[0]}]`, // send mail template 2 on mail.test.activity 1 and 2
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test('activity view: activity widget', async function (assert) {
|
||||
assert.expect(16);
|
||||
|
||||
const mailActivityTypeIds = pyEnv['mail.activity.type'].search([]);
|
||||
const [mailTestActivityId2] = pyEnv['mail.test.activity'].search([['name', '=', 'Office planning']]);
|
||||
const [mailTemplateId1] = pyEnv['mail.template'].search([['name', '=', 'Template1']]);
|
||||
const { env, openView } = await start({
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'activity_send_mail') {
|
||||
assert.deepEqual([[mailTestActivityId2], mailTemplateId1], args.args, "Should send template related to mailTestActivity2");
|
||||
assert.step('activity_send_mail');
|
||||
// random value returned in order for the mock server to know that this route is implemented.
|
||||
return true;
|
||||
}
|
||||
if (args.method === 'action_feedback_schedule_next') {
|
||||
assert.deepEqual(
|
||||
[pyEnv['mail.activity'].search([['state', '=', 'overdue']])],
|
||||
args.args,
|
||||
"Should execute action_feedback_schedule_next only on the overude activity"
|
||||
);
|
||||
assert.equal(args.kwargs.feedback, "feedback2");
|
||||
assert.step('action_feedback_schedule_next');
|
||||
return Promise.resolve({ serverGeneratedAction: true });
|
||||
}
|
||||
},
|
||||
serverData,
|
||||
});
|
||||
await openView({
|
||||
res_model: 'mail.test.activity',
|
||||
views: [[false, 'activity']],
|
||||
});
|
||||
patchWithCleanup(env.services.action, {
|
||||
doAction(action) {
|
||||
if (action.serverGeneratedAction) {
|
||||
assert.step('serverGeneratedAction');
|
||||
} else if (action.res_model === 'mail.compose.message') {
|
||||
assert.deepEqual({
|
||||
default_model: 'mail.test.activity',
|
||||
default_res_id: mailTestActivityId2,
|
||||
default_template_id: mailTemplateId1,
|
||||
default_use_template: true,
|
||||
force_email: true
|
||||
}, action.context);
|
||||
assert.step("do_action_compose");
|
||||
} else if (action.res_model === 'mail.activity') {
|
||||
assert.deepEqual({
|
||||
"default_activity_type_id": mailActivityTypeIds[1],
|
||||
"default_res_id": mailTestActivityId2,
|
||||
"default_res_model": 'mail.test.activity',
|
||||
}, action.context);
|
||||
assert.step("do_action_activity");
|
||||
} else {
|
||||
assert.step("Unexpected action");
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
});
|
||||
|
||||
await testUtils.dom.click(document.querySelector('.today .o_closest_deadline'));
|
||||
assert.hasClass(document.querySelector('.today .dropdown-menu.o_activity'), 'show', "dropdown should be displayed");
|
||||
assert.ok(document.querySelector('.o_activity_color_today').textContent.includes('Today'), "Title should be today");
|
||||
assert.ok([...document.querySelectorAll('.today .o_activity_title_entry')].filter(el => el.textContent.includes('Template1')).length,
|
||||
"Template1 should be available");
|
||||
assert.ok([...document.querySelectorAll('.today .o_activity_title_entry')].filter(el => el.textContent.includes('Template2')).length,
|
||||
"Template2 should be available");
|
||||
|
||||
await testUtils.dom.click(document.querySelector('.o_activity_title_entry[data-activity-id="2"] .o_activity_template_preview'));
|
||||
await testUtils.dom.click(document.querySelector('.o_activity_title_entry[data-activity-id="2"] .o_activity_template_send'));
|
||||
await testUtils.dom.click(document.querySelector('.overdue .o_closest_deadline'));
|
||||
assert.notOk(document.querySelector('.overdue .o_activity_template_preview'),
|
||||
"No template should be available");
|
||||
|
||||
await testUtils.dom.click(document.querySelector('.overdue .o_schedule_activity'));
|
||||
await testUtils.dom.click(document.querySelector('.overdue .o_closest_deadline'));
|
||||
await testUtils.dom.click(document.querySelector('.overdue .o_mark_as_done'));
|
||||
document.querySelector('.overdue #activity_feedback').value = "feedback2";
|
||||
|
||||
await testUtils.dom.click(document.querySelector('.overdue .o_activity_popover_done_next'));
|
||||
assert.verifySteps([
|
||||
"do_action_compose",
|
||||
"activity_send_mail",
|
||||
"do_action_activity",
|
||||
"action_feedback_schedule_next",
|
||||
"serverGeneratedAction"
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
QUnit.test("activity view: no group_by_menu and no comparison_menu", async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
serverData.actions = {
|
||||
1: {
|
||||
id: 1,
|
||||
name: "MailTestActivity Action",
|
||||
res_model: "mail.test.activity",
|
||||
type: "ir.actions.act_window",
|
||||
views: [[false, "activity"]],
|
||||
},
|
||||
};
|
||||
|
||||
const mockRPC = (route, args) => {
|
||||
if (args.method === "get_activity_data") {
|
||||
assert.strictEqual(
|
||||
args.kwargs.context.lang,
|
||||
"zz_ZZ",
|
||||
"The context should have been passed"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
patchWithCleanup(session.user_context, { lang: "zz_ZZ" });
|
||||
|
||||
const { webClient } = await start({ serverData, mockRPC });
|
||||
|
||||
await doAction(webClient, 1);
|
||||
|
||||
assert.containsN(
|
||||
document.body,
|
||||
".o_search_options .dropdown button:visible",
|
||||
2,
|
||||
"only two elements should be available in view search"
|
||||
);
|
||||
assert.isVisible(
|
||||
document.querySelector(".o_search_options .dropdown.o_filter_menu > button"),
|
||||
"filter should be available in view search"
|
||||
);
|
||||
assert.isVisible(
|
||||
document.querySelector(".o_search_options .dropdown.o_favorite_menu > button"),
|
||||
"favorites should be available in view search"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('activity view: search more to schedule an activity for a record of a respecting model', async function (assert) {
|
||||
assert.expect(5);
|
||||
const mailTestActivityId1 = pyEnv['mail.test.activity'].create({ name: 'MailTestActivity 3' });
|
||||
Object.assign(serverData.views, {
|
||||
'mail.test.activity,false,list': '<tree string="MailTestActivity"><field name="name"/></tree>',
|
||||
});
|
||||
const { env, openView } = await start({
|
||||
mockRPC(route, args) {
|
||||
if (args.method === 'name_search') {
|
||||
args.kwargs.name = "MailTestActivity";
|
||||
}
|
||||
},
|
||||
serverData,
|
||||
});
|
||||
await openView({
|
||||
res_model: 'mail.test.activity',
|
||||
views: [[false, 'activity']],
|
||||
});
|
||||
patchWithCleanup(env.services.action, {
|
||||
doAction(action, options) {
|
||||
assert.step('doAction');
|
||||
var expectedAction = {
|
||||
context: {
|
||||
default_res_id: mailTestActivityId1,
|
||||
default_res_model: "mail.test.activity",
|
||||
},
|
||||
name: "Schedule Activity",
|
||||
res_id: false,
|
||||
res_model: "mail.activity",
|
||||
target: "new",
|
||||
type: "ir.actions.act_window",
|
||||
view_mode: "form",
|
||||
views: [[false, "form"]],
|
||||
};
|
||||
assert.deepEqual(action, expectedAction,
|
||||
"should execute an action with correct params");
|
||||
options.onClose();
|
||||
return Promise.resolve();
|
||||
},
|
||||
});
|
||||
|
||||
const activity = $(document);
|
||||
assert.containsOnce(activity, 'table tfoot tr .o_record_selector',
|
||||
'should contain search more selector to choose the record to schedule an activity for it');
|
||||
await testUtils.dom.click(activity.find('table tfoot tr .o_record_selector'));
|
||||
// search create dialog
|
||||
var $modal = $('.modal-lg');
|
||||
assert.strictEqual($modal.find('.o_data_row').length, 3, "all mail.test.activity should be available to select");
|
||||
// select a record to schedule an activity for it (this triggers a do_action)
|
||||
await testUtils.dom.click($modal.find('.o_data_row:last .o_data_cell'));
|
||||
assert.verifySteps(['doAction']);
|
||||
});
|
||||
|
||||
QUnit.test("Activity view: discard an activity creation dialog", async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
serverData.actions = {
|
||||
1: {
|
||||
id: 1,
|
||||
name: "MailTestActivity Action",
|
||||
res_model: "mail.test.activity",
|
||||
type: "ir.actions.act_window",
|
||||
views: [[false, "activity"]],
|
||||
},
|
||||
};
|
||||
|
||||
Object.assign(serverData.views, {
|
||||
'mail.activity,false,form':
|
||||
`<form>
|
||||
<field name="display_name"/>
|
||||
<footer>
|
||||
<button string="Discard" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
const mockRPC = (route, args) => {
|
||||
if (args.method === "check_access_rights") {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
const { webClient } = await start({ serverData, mockRPC });
|
||||
await doAction(webClient, 1);
|
||||
|
||||
await testUtils.dom.click(
|
||||
document.querySelector(".o_activity_view .o_data_row .o_activity_empty_cell")
|
||||
);
|
||||
await legacyExtraNextTick();
|
||||
assert.containsOnce($, ".modal.o_technical_modal", "Activity Modal should be opened");
|
||||
|
||||
await testUtils.dom.click($('.modal.o_technical_modal button[special="cancel"]'));
|
||||
await legacyExtraNextTick();
|
||||
assert.containsNone($, ".modal.o_technical_modal", "Activity Modal should be closed");
|
||||
});
|
||||
|
||||
QUnit.test('Activity view: many2one_avatar_user widget in activity view', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
const [mailTestActivityId1] = pyEnv['mail.test.activity'].search([['name', '=', 'Meeting Room Furnitures']]);
|
||||
const resUsersId1 = pyEnv['res.users'].create({
|
||||
display_name: "first user",
|
||||
avatar_128: "Atmaram Bhide",
|
||||
});
|
||||
pyEnv['mail.test.activity'].write([mailTestActivityId1], { activity_user_id: resUsersId1 });
|
||||
Object.assign(serverData.views, {
|
||||
'mail.test.activity,false,activity':
|
||||
`<activity string="MailTestActivity">
|
||||
<templates>
|
||||
<div t-name="activity-box">
|
||||
<field name="activity_user_id" widget="many2one_avatar_user"/>
|
||||
<field name="name"/>
|
||||
</div>
|
||||
</templates>
|
||||
</activity>`,
|
||||
});
|
||||
serverData.actions = {
|
||||
1: {
|
||||
id: 1,
|
||||
name: 'MailTestActivity Action',
|
||||
res_model: 'mail.test.activity',
|
||||
type: 'ir.actions.act_window',
|
||||
views: [[false, 'activity']],
|
||||
}
|
||||
};
|
||||
|
||||
const { webClient } = await start({ serverData });
|
||||
await doAction(webClient, 1);
|
||||
|
||||
await legacyExtraNextTick();
|
||||
assert.containsN(document.body, '.o_m2o_avatar', 2);
|
||||
assert.containsOnce(document.body, `tr[data-res-id=${mailTestActivityId1}] .o_m2o_avatar > img[data-src="/web/image/res.users/${resUsersId1}/avatar_128"]`,
|
||||
"should have m2o avatar image");
|
||||
assert.containsNone(document.body, '.o_m2o_avatar > span',
|
||||
"should not have text on many2one_avatar_user if onlyImage node option is passed");
|
||||
});
|
||||
|
||||
QUnit.test("Activity view: on_destroy_callback doesn't crash", async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
patchWithCleanup(ActivityRenderer.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
owl.onMounted(() => {
|
||||
assert.step('mounted');
|
||||
});
|
||||
owl.onWillUnmount(() => {
|
||||
assert.step('willUnmount');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const { openView } = await start({
|
||||
serverData,
|
||||
});
|
||||
await openView({
|
||||
res_model: 'mail.test.activity',
|
||||
views: [[false, 'activity']],
|
||||
});
|
||||
// force the unmounting of the activity view by opening another one
|
||||
await openView({
|
||||
res_model: 'mail.test.activity',
|
||||
views: [[false, 'form']],
|
||||
});
|
||||
|
||||
assert.verifySteps([
|
||||
'mounted',
|
||||
'willUnmount'
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("Schedule activity dialog uses the same search view as activity view", async function (assert) {
|
||||
assert.expect(8);
|
||||
pyEnv['mail.test.activity'].unlink(pyEnv['mail.test.activity'].search([]));
|
||||
Object.assign(serverData.views, {
|
||||
"mail.test.activity,false,list": `<list><field name="name"/></list>`,
|
||||
"mail.test.activity,false,search": `<search/>`,
|
||||
'mail.test.activity,1,search': `<search/>`,
|
||||
});
|
||||
|
||||
function mockRPC(route, args) {
|
||||
if (args.method === "get_views") {
|
||||
assert.step(JSON.stringify(args.kwargs.views));
|
||||
}
|
||||
}
|
||||
|
||||
const { webClient , click } = await start({ serverData, mockRPC });
|
||||
|
||||
// open an activity view (with default search arch)
|
||||
await doAction(webClient, {
|
||||
name: 'Dashboard',
|
||||
res_model: 'mail.test.activity',
|
||||
type: 'ir.actions.act_window',
|
||||
views: [[false, 'activity']],
|
||||
});
|
||||
|
||||
assert.verifySteps([
|
||||
'[[false,"activity"],[false,"search"]]',
|
||||
])
|
||||
|
||||
// click on "Schedule activity"
|
||||
await click(document.querySelector(".o_activity_view .o_record_selector"));
|
||||
|
||||
assert.verifySteps([
|
||||
'[[false,"list"],[false,"search"]]',
|
||||
])
|
||||
|
||||
// open an activity view (with search arch 1)
|
||||
await doAction(webClient, {
|
||||
name: 'Dashboard',
|
||||
res_model: 'mail.test.activity',
|
||||
type: 'ir.actions.act_window',
|
||||
views: [[false, 'activity']],
|
||||
search_view_id: [1,"search"],
|
||||
});
|
||||
|
||||
assert.verifySteps([
|
||||
'[[false,"activity"],[1,"search"]]',
|
||||
])
|
||||
|
||||
// click on "Schedule activity"
|
||||
await click(document.querySelector(".o_activity_view .o_record_selector"));
|
||||
|
||||
assert.verifySteps([
|
||||
'[[false,"list"],[1,"search"]]',
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test('Activity view: apply progressbar filter', async function (assert) {
|
||||
assert.expect(9);
|
||||
|
||||
serverData.actions = {
|
||||
1: {
|
||||
id: 1,
|
||||
name: 'MailTestActivity Action',
|
||||
res_model: 'mail.test.activity',
|
||||
type: 'ir.actions.act_window',
|
||||
views: [[false, 'activity']],
|
||||
}
|
||||
};
|
||||
|
||||
const { webClient } = await start({ serverData });
|
||||
|
||||
await doAction(webClient, 1);
|
||||
|
||||
assert.containsNone(document.querySelector('.o_activity_view thead'),
|
||||
'.o_activity_filter_planned,.o_activity_filter_today,.o_activity_filter_overdue,.o_activity_filter___false',
|
||||
"should not have active filter");
|
||||
assert.containsNone(document.querySelector('.o_activity_view tbody'),
|
||||
'.o_activity_filter_planned,.o_activity_filter_today,.o_activity_filter_overdue,.o_activity_filter___false',
|
||||
"should not have active filter");
|
||||
assert.strictEqual(document.querySelector('.o_activity_view tbody .o_activity_record').textContent,
|
||||
'Office planning', "'Office planning' should be first record");
|
||||
assert.containsOnce(document.querySelector('.o_activity_view tbody'), '.planned',
|
||||
"other records should be available");
|
||||
|
||||
await testUtils.dom.click(document.querySelector('.o_kanban_counter_progress .progress-bar[data-filter="planned"]'));
|
||||
assert.containsOnce(document.querySelector('.o_activity_view thead'), '.o_activity_filter_planned',
|
||||
"planned should be active filter");
|
||||
assert.containsN(document.querySelector('.o_activity_view tbody'), '.o_activity_filter_planned', 5,
|
||||
"planned should be active filter");
|
||||
assert.strictEqual(document.querySelector('.o_activity_view tbody .o_activity_record').textContent,
|
||||
'Meeting Room Furnitures', "'Office planning' should be first record");
|
||||
const tr = document.querySelectorAll('.o_activity_view tbody tr')[1];
|
||||
assert.hasClass(tr.querySelectorAll('td')[1], 'o_activity_empty_cell',
|
||||
"other records should be hidden");
|
||||
assert.containsNone(document.querySelector('.o_activity_view tbody'), 'planned',
|
||||
"other records should be hidden");
|
||||
});
|
||||
|
||||
QUnit.test("Activity view: luxon in renderingContext", async function (assert) {
|
||||
Object.assign(serverData.views, {
|
||||
"mail.test.activity,false,activity": `
|
||||
<activity string="MailTestActivity">
|
||||
<templates>
|
||||
<div t-name="activity-box">
|
||||
<t t-if="luxon">
|
||||
<span class="luxon">luxon</span>
|
||||
</t>
|
||||
</div>
|
||||
</templates>
|
||||
</activity>`,
|
||||
});
|
||||
const { openView } = await start({
|
||||
serverData,
|
||||
});
|
||||
await openView({
|
||||
res_model: "mail.test.activity",
|
||||
views: [[false, "activity"]],
|
||||
});
|
||||
assert.containsN(document.body, ".luxon", 2);
|
||||
});
|
||||
|
||||
QUnit.test('update activity view after creating multiple activities', async function (assert) {
|
||||
assert.expect(9);
|
||||
pyEnv['mail.test.activity'].create({ name: 'MailTestActivity 3' });
|
||||
Object.assign(serverData.views, {
|
||||
'mail.test.activity,false,list': '<tree string="MailTestActivity"><field name="name"/><field name="activity_ids" widget="list_activity"/></tree>',
|
||||
'mail.activity,false,form': '<form><field name="activity_type_id"/></form>'
|
||||
});
|
||||
|
||||
const { openView } = await start({
|
||||
mockRPC(route, args) {
|
||||
if (args.method === 'name_search') {
|
||||
args.kwargs.name = "MailTestActivity";
|
||||
}
|
||||
},
|
||||
serverData,
|
||||
});
|
||||
await openView({
|
||||
res_model: 'mail.test.activity',
|
||||
views: [[false, 'activity']],
|
||||
});
|
||||
|
||||
await click("table tfoot tr .o_record_selector");
|
||||
await click(".o_list_renderer table tbody tr:nth-child(2) td:nth-child(2) .o_ActivityButtonView")
|
||||
await click(".o-main-components-container .o_PopoverManager .o_ActivityListView .o_ActivityListView_addActivityButton");
|
||||
await insertText('.o_field_many2one_selection .o_input_dropdown .dropdown input[id=activity_type_id]', "test1");
|
||||
await click(".o_field_many2one_selection .o_input_dropdown .dropdown input[id=activity_type_id]");
|
||||
await click('.o-autocomplete--dropdown-menu li:nth-child(1) .dropdown-item');
|
||||
await click(".modal-footer .o_cp_buttons .o_form_buttons_edit .btn-primary");
|
||||
await click(".modal-footer .o_form_button_cancel");
|
||||
await click("table tbody tr:nth-child(1) td:nth-child(6) .o_mail_activity .o_activity_btn .o_closest_deadline");
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,252 @@
|
|||
import { defineTestMailModels } from "@test_mail/../tests/test_mail_test_helpers";
|
||||
import { beforeEach, describe, test, expect } from "@odoo/hoot";
|
||||
import { queryOne, waitUntil } from "@odoo/hoot-dom";
|
||||
import { animationFrame } from "@odoo/hoot-mock";
|
||||
import {
|
||||
click,
|
||||
contains,
|
||||
openFormView,
|
||||
registerArchs,
|
||||
start,
|
||||
startServer,
|
||||
patchUiSize,
|
||||
SIZES,
|
||||
dragenterFiles,
|
||||
dropFiles,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { patchWithCleanup } from "@web/../tests/web_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineTestMailModels();
|
||||
|
||||
let popoutIframe, popoutWindow;
|
||||
|
||||
beforeEach(() => {
|
||||
popoutIframe = document.createElement("iframe");
|
||||
popoutWindow = {
|
||||
closed: false,
|
||||
get document() {
|
||||
const doc = popoutIframe.contentDocument;
|
||||
if (!doc) {
|
||||
return undefined;
|
||||
}
|
||||
const originalWrite = doc.write;
|
||||
doc.write = (content) => {
|
||||
// This avoids duplicating the test script in the popoutWindow
|
||||
const sanitizedContent = content.replace(
|
||||
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
|
||||
""
|
||||
);
|
||||
originalWrite.call(doc, sanitizedContent);
|
||||
};
|
||||
return doc;
|
||||
},
|
||||
close: () => {
|
||||
popoutWindow.closed = true;
|
||||
popoutIframe.remove(popoutAttachmentViewBody());
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
patchWithCleanup(browser, {
|
||||
open: () => {
|
||||
popoutWindow.closed = false;
|
||||
queryOne(".o_popout_holder").append(popoutIframe);
|
||||
return popoutWindow;
|
||||
},
|
||||
});
|
||||
|
||||
function popoutAttachmentViewBody() {
|
||||
return popoutWindow.document.querySelector(".o-mail-PopoutAttachmentView");
|
||||
}
|
||||
async function popoutIsEmpty() {
|
||||
await animationFrame();
|
||||
expect(popoutAttachmentViewBody()).toBe(null);
|
||||
}
|
||||
async function popoutContains(selector) {
|
||||
await animationFrame();
|
||||
await waitUntil(() => popoutAttachmentViewBody());
|
||||
const target = popoutAttachmentViewBody().querySelector(selector);
|
||||
expect(target).toBeDisplayed();
|
||||
return target;
|
||||
}
|
||||
async function popoutClick(selector) {
|
||||
const target = await popoutContains(selector);
|
||||
click(target);
|
||||
}
|
||||
|
||||
test("Attachment view popout controls test", async () => {
|
||||
/*
|
||||
* This test makes sure that the attachment view controls are working in the following cases:
|
||||
* - Inside the popout window
|
||||
* - After closing the popout window
|
||||
*/
|
||||
const pyEnv = await startServer();
|
||||
const recordId = pyEnv["mail.test.simple.main.attachment"].create({
|
||||
display_name: "first partner",
|
||||
message_attachment_count: 2,
|
||||
});
|
||||
pyEnv["ir.attachment"].create([
|
||||
{
|
||||
mimetype: "image/jpeg",
|
||||
res_id: recordId,
|
||||
res_model: "mail.test.simple.main.attachment",
|
||||
},
|
||||
{
|
||||
mimetype: "application/pdf",
|
||||
res_id: recordId,
|
||||
res_model: "mail.test.simple.main.attachment",
|
||||
},
|
||||
]);
|
||||
registerArchs({
|
||||
"mail.test.simple.main.attachment,false,form": `
|
||||
<form string="Test document">
|
||||
<div class="o_popout_holder"/>
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
</sheet>
|
||||
<div class="o_attachment_preview"/>
|
||||
<chatter/>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
patchUiSize({ size: SIZES.XXL });
|
||||
await start();
|
||||
await openFormView("mail.test.simple.main.attachment", recordId);
|
||||
await click(".o_attachment_preview .o_attachment_control");
|
||||
await animationFrame();
|
||||
expect(".o_attachment_preview").not.toBeVisible();
|
||||
await popoutClick(".o_move_next");
|
||||
await popoutContains("img");
|
||||
await popoutClick(".o_move_previous");
|
||||
await popoutContains("iframe");
|
||||
popoutWindow.close();
|
||||
await contains(".o_attachment_preview:not(.d-none)");
|
||||
expect(".o_attachment_preview").toBeVisible();
|
||||
await click(".o_attachment_preview .o_move_next");
|
||||
await contains(".o_attachment_preview img");
|
||||
await click(".o_attachment_preview .o_move_previous");
|
||||
await contains(".o_attachment_preview iframe");
|
||||
await click(".o_attachment_preview .o_attachment_control");
|
||||
await animationFrame();
|
||||
expect(".o_attachment_preview").not.toBeVisible();
|
||||
});
|
||||
|
||||
test("Chatter main attachment: can change from non-viewable to viewable", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const recordId = pyEnv['mail.test.simple.main.attachment'].create({});
|
||||
const irAttachmentId = pyEnv['ir.attachment'].create({
|
||||
mimetype: 'text/plain',
|
||||
name: "Blah.txt",
|
||||
res_id: recordId,
|
||||
res_model: 'mail.test.simple.main.attachment',
|
||||
});
|
||||
pyEnv['mail.message'].create({
|
||||
attachment_ids: [irAttachmentId],
|
||||
model: 'mail.test.simple.main.attachment',
|
||||
res_id: recordId,
|
||||
});
|
||||
pyEnv['mail.test.simple.main.attachment'].write([recordId], {message_main_attachment_id : irAttachmentId});
|
||||
|
||||
registerArchs({
|
||||
"mail.test.simple.main.attachment,false,form": `
|
||||
<form string="Test document">
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
</sheet>
|
||||
<div class="o_attachment_preview"/>
|
||||
<chatter/>
|
||||
</form>`,
|
||||
});
|
||||
patchUiSize({ size: SIZES.XXL });
|
||||
await start();
|
||||
await openFormView("mail.test.simple.main.attachment", recordId);
|
||||
|
||||
// Add a PDF file
|
||||
const pdfFile = new File([new Uint8Array(1)], "text.pdf", { type: "application/pdf" });
|
||||
await dragenterFiles(".o-mail-Chatter", [pdfFile]);
|
||||
await dropFiles(".o-Dropzone", [pdfFile]);
|
||||
await contains(".o_attachment_preview");
|
||||
await contains(".o-mail-Attachment > iframe", { count: 0 }); // The viewer tries to display the text file not the PDF
|
||||
|
||||
// Switch to the PDF file in the viewer
|
||||
await click(".o_move_next");
|
||||
await contains(".o-mail-Attachment > iframe"); // There should be iframe for PDF viewer
|
||||
});
|
||||
|
||||
test.skip("Attachment view / chatter popout across multiple records test", async () => {
|
||||
// skip because test has race conditions: https://runbot.odoo.com/odoo/runbot.build.error/109795
|
||||
const pyEnv = await startServer();
|
||||
const recordIds = pyEnv["mail.test.simple.main.attachment"].create([
|
||||
{
|
||||
display_name: "first partner",
|
||||
message_attachment_count: 1,
|
||||
},
|
||||
{
|
||||
display_name: "second partner",
|
||||
message_attachment_count: 0,
|
||||
},
|
||||
{
|
||||
display_name: "third partner",
|
||||
message_attachment_count: 1,
|
||||
},
|
||||
]);
|
||||
pyEnv["ir.attachment"].create([
|
||||
{
|
||||
mimetype: "image/jpeg",
|
||||
res_id: recordIds[0],
|
||||
res_model: "mail.test.simple.main.attachment",
|
||||
},
|
||||
{
|
||||
mimetype: "application/pdf",
|
||||
res_id: recordIds[2],
|
||||
res_model: "mail.test.simple.main.attachment",
|
||||
},
|
||||
]);
|
||||
registerArchs({
|
||||
"mail.test.simple.main.attachment,false,form": `
|
||||
<form string="Test document">
|
||||
<div class="o_popout_holder"/>
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
</sheet>
|
||||
<div class="o_attachment_preview"/>
|
||||
<chatter/>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
async function navigateRecords() {
|
||||
/**
|
||||
* It should be called on the first record of recordIds
|
||||
* The popout window should be open
|
||||
* It navigates recordIds as 0 -> 1 -> 2 -> 0 -> 2
|
||||
*/
|
||||
await animationFrame();
|
||||
expect(".o_attachment_preview").not.toBeVisible();
|
||||
await popoutContains("img");
|
||||
await click(".o_pager_next");
|
||||
await popoutIsEmpty();
|
||||
await click(".o_pager_next");
|
||||
await popoutContains("iframe");
|
||||
await click(".o_pager_next");
|
||||
await popoutContains("img");
|
||||
await click(".o_pager_previous");
|
||||
await popoutContains("iframe");
|
||||
popoutWindow.close();
|
||||
await contains(".o_attachment_preview:not(.d-none)");
|
||||
}
|
||||
|
||||
patchUiSize({ size: SIZES.XXL });
|
||||
await start();
|
||||
await openFormView("mail.test.simple.main.attachment", recordIds[0], {
|
||||
resIds: recordIds,
|
||||
});
|
||||
await click(".o_attachment_preview .o_attachment_control");
|
||||
await navigateRecords();
|
||||
await openFormView("mail.test.simple.main.attachment", recordIds[0], {
|
||||
resIds: recordIds,
|
||||
});
|
||||
await click("button i[title='Pop out Attachments']");
|
||||
await navigateRecords();
|
||||
});
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
inputFiles,
|
||||
insertText,
|
||||
listenStoreFetch,
|
||||
openFormView,
|
||||
patchUiSize,
|
||||
registerArchs,
|
||||
SIZES,
|
||||
start,
|
||||
startServer,
|
||||
triggerHotkey,
|
||||
waitStoreFetch,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { defineTestMailModels } from "@test_mail/../tests/test_mail_test_helpers";
|
||||
import { MockServer, onRpc } from "@web/../tests/web_test_helpers";
|
||||
import { mail_data } from "@mail/../tests/mock_server/mail_mock_server";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineTestMailModels();
|
||||
|
||||
test("Send message button activation (access rights dependent)", async () => {
|
||||
const pyEnv = await startServer();
|
||||
registerArchs({
|
||||
"mail.test.multi.company,false,form": `
|
||||
<form string="Simple">
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
`,
|
||||
"mail.test.multi.company.read,false,form": `
|
||||
<form string="Simple">
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
let userAccess = {};
|
||||
listenStoreFetch("mail.thread", {
|
||||
async onRpc(request) {
|
||||
const { params } = await request.json();
|
||||
if (params.fetch_params.some((fetchParam) => fetchParam[0] === "mail.thread")) {
|
||||
const res = await mail_data.bind(MockServer.current)(request);
|
||||
res["mail.thread"][0].hasWriteAccess = userAccess.hasWriteAccess;
|
||||
res["mail.thread"][0].hasReadAccess = userAccess.hasReadAccess;
|
||||
return res;
|
||||
}
|
||||
},
|
||||
});
|
||||
await start();
|
||||
const simpleId = pyEnv["mail.test.multi.company"].create({ name: "Test MC Simple" });
|
||||
const simpleMcId = pyEnv["mail.test.multi.company.read"].create({
|
||||
name: "Test MC Readonly with Activities",
|
||||
});
|
||||
async function assertSendButton(
|
||||
enabled,
|
||||
activities,
|
||||
msg,
|
||||
model = null,
|
||||
resId = null,
|
||||
hasReadAccess = false,
|
||||
hasWriteAccess = false
|
||||
) {
|
||||
userAccess = { hasReadAccess, hasWriteAccess };
|
||||
await openFormView(model, resId);
|
||||
if (resId) {
|
||||
await waitStoreFetch("mail.thread");
|
||||
}
|
||||
if (enabled) {
|
||||
await contains(".o-mail-Chatter-topbar button:enabled", { text: "Send message" });
|
||||
await contains(".o-mail-Chatter-topbar button:enabled", { text: "Log note" });
|
||||
if (activities) {
|
||||
await contains(".o-mail-Chatter-topbar button:enabled", { text: "Activity" });
|
||||
|
||||
}
|
||||
} else {
|
||||
await contains(".o-mail-Chatter-topbar button:disabled", { text: "Send message" });
|
||||
await contains(".o-mail-Chatter-topbar button:disabled", { text: "Log note" });
|
||||
if (activities) {
|
||||
await contains(".o-mail-Chatter-topbar button:disabled", { text: "Activity" });
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
await assertSendButton(
|
||||
true,
|
||||
false,
|
||||
"Record, all rights",
|
||||
"mail.test.multi.company",
|
||||
simpleId,
|
||||
true,
|
||||
true
|
||||
);
|
||||
await assertSendButton(
|
||||
true,
|
||||
true,
|
||||
"Record, all rights",
|
||||
"mail.test.multi.company.read",
|
||||
simpleId,
|
||||
true,
|
||||
true
|
||||
);
|
||||
await assertSendButton(
|
||||
false,
|
||||
false,
|
||||
"Record, no write access",
|
||||
"mail.test.multi.company",
|
||||
simpleId,
|
||||
true
|
||||
);
|
||||
await assertSendButton(
|
||||
true,
|
||||
true,
|
||||
"Record, read access but model accept post with read only access",
|
||||
"mail.test.multi.company.read",
|
||||
simpleMcId,
|
||||
true
|
||||
);
|
||||
await assertSendButton(false, false, "Record, no rights", "mail.test.multi.company", simpleId);
|
||||
await assertSendButton(false, true, "Record, no rights", "mail.test.multi.company.read", simpleMcId);
|
||||
// Note that rights have no impact on send button for draft record (chatter.isTemporary=true)
|
||||
await assertSendButton(true, false, "Draft record", "mail.test.multi.company");
|
||||
await assertSendButton(true, true, "Draft record", "mail.test.multi.company.read");
|
||||
});
|
||||
|
||||
test("basic chatter rendering with a model without activities", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const recordId = pyEnv["mail.test.simple"].create({ name: "new record" });
|
||||
registerArchs({
|
||||
"mail.test.simple,false,form": `
|
||||
<form string="Records">
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
await start();
|
||||
await openFormView("mail.test.simple", recordId);
|
||||
await contains(".o-mail-Chatter");
|
||||
await contains(".o-mail-Chatter-topbar");
|
||||
await contains("button[aria-label='Attach files']");
|
||||
await contains("button", { count: 0, text: "Activities" });
|
||||
await contains(".o-mail-Followers");
|
||||
await contains(".o-mail-Thread");
|
||||
});
|
||||
|
||||
test("opened attachment box should remain open after adding a new attachment", async (assert) => {
|
||||
const pyEnv = await startServer();
|
||||
const recordId = pyEnv["mail.test.simple.main.attachment"].create({});
|
||||
const attachmentId = pyEnv["ir.attachment"].create({
|
||||
mimetype: "image/jpeg",
|
||||
res_id: recordId,
|
||||
res_model: "mail.test.simple.main.attachment",
|
||||
});
|
||||
pyEnv["mail.message"].create({
|
||||
attachment_ids: [attachmentId],
|
||||
model: "mail.test.simple.main.attachment",
|
||||
res_id: recordId,
|
||||
});
|
||||
onRpc("/mail/thread/data", async (request) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1)); // need extra time for useEffect
|
||||
});
|
||||
patchUiSize({ size: SIZES.XXL });
|
||||
await start();
|
||||
await openFormView("mail.test.simple.main.attachment", recordId, {
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
</sheet>
|
||||
<div class="o_attachment_preview" />
|
||||
<chatter reload_on_post="True" reload_on_attachment="True"/>
|
||||
</form>`,
|
||||
});
|
||||
await contains(".o_attachment_preview");
|
||||
await click(".o-mail-Chatter-attachFiles");
|
||||
await contains(".o-mail-AttachmentBox");
|
||||
await click("button", { text: "Send message" });
|
||||
await inputFiles(".o-mail-Composer .o_input_file", [
|
||||
new File(["image"], "testing.jpeg", { type: "image/jpeg" }),
|
||||
]);
|
||||
await click(".o-mail-Composer-send:enabled");
|
||||
await contains(".o_move_next");
|
||||
await click("button", { text: "Send message" });
|
||||
await insertText(".o-mail-Composer-input", "test");
|
||||
triggerHotkey("control+Enter");
|
||||
await contains(".o-mail-Message-body", { text: "test" });
|
||||
await contains(".o-mail-AttachmentBox .o-mail-AttachmentImage", { count: 2 });
|
||||
});
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import {
|
||||
start,
|
||||
startServer,
|
||||
} from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
|
||||
QUnit.module('mail', {}, function () {
|
||||
QUnit.module('Chatter');
|
||||
|
||||
QUnit.test('Send message button activation (access rights dependent)', async function (assert) {
|
||||
const pyEnv = await startServer();
|
||||
const view = `<form string="Simple">
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids"/>
|
||||
</div>
|
||||
</form>`;
|
||||
|
||||
let userAccess = {};
|
||||
const { openView } = await start({
|
||||
serverData: {
|
||||
views: {
|
||||
'mail.test.multi.company,false,form': view,
|
||||
'mail.test.multi.company.read,false,form': view,
|
||||
}
|
||||
},
|
||||
async mockRPC(route, args, performRPC) {
|
||||
const res = await performRPC(route, args);
|
||||
if (route === '/mail/thread/data') {
|
||||
// mimic user with custom access defined in userAccess variable
|
||||
const { thread_model } = args;
|
||||
Object.assign(res, userAccess);
|
||||
res['canPostOnReadonly'] = thread_model === 'mail.test.multi.company.read';
|
||||
}
|
||||
return res;
|
||||
},
|
||||
});
|
||||
const resSimpleId1 = pyEnv['mail.test.multi.company'].create({ name: 'Test MC Simple' });
|
||||
const resSimpleMCId1 = pyEnv['mail.test.multi.company.read'].create({ name: 'Test MC Readonly' });
|
||||
async function assertSendButton(enabled, msg,
|
||||
model = null, resId = null,
|
||||
hasReadAccess = false, hasWriteAccess = false) {
|
||||
userAccess = { hasReadAccess, hasWriteAccess };
|
||||
await openView({
|
||||
res_id: resId,
|
||||
res_model: model,
|
||||
views: [[false, 'form']],
|
||||
});
|
||||
const details = `hasReadAccess: ${hasReadAccess}, hasWriteAccess: ${hasWriteAccess}, model: ${model}, resId: ${resId}`;
|
||||
if (enabled) {
|
||||
assert.containsNone(document.body, '.o_ChatterTopbar_buttonSendMessage:disabled',
|
||||
`${msg}: send message button must not be disabled (${details}`);
|
||||
} else {
|
||||
assert.containsOnce(document.body, '.o_ChatterTopbar_buttonSendMessage:disabled',
|
||||
`${msg}: send message button must be disabled (${details})`);
|
||||
}
|
||||
}
|
||||
const enabled = true, disabled = false;
|
||||
|
||||
await assertSendButton(enabled, 'Record, all rights', 'mail.test.multi.company', resSimpleId1, true, true);
|
||||
await assertSendButton(enabled, 'Record, all rights', 'mail.test.multi.company.read', resSimpleId1, true, true);
|
||||
await assertSendButton(disabled, 'Record, no write access', 'mail.test.multi.company', resSimpleId1, true);
|
||||
await assertSendButton(enabled, 'Record, read access but model accept post with read only access',
|
||||
'mail.test.multi.company.read', resSimpleMCId1, true);
|
||||
await assertSendButton(disabled, 'Record, no rights', 'mail.test.multi.company', resSimpleId1);
|
||||
await assertSendButton(disabled, 'Record, no rights', 'mail.test.multi.company.read', resSimpleMCId1);
|
||||
|
||||
// Note that rights have no impact on send button for draft record (chatter.isTemporary=true)
|
||||
await assertSendButton(enabled, 'Draft record', 'mail.test.multi.company');
|
||||
await assertSendButton(enabled, 'Draft record', 'mail.test.multi.company.read');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { addModelNamesToFetch } from '@bus/../tests/helpers/model_definitions_helpers';
|
||||
|
||||
addModelNamesToFetch(['mail.test.track.all', 'mail.test.activity', 'mail.test.multi.company', 'mail.test.multi.company.read']);
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { start } from "@mail/../tests/helpers/test_utils";
|
||||
import { prepareTarget } from "web.test_utils";
|
||||
|
||||
QUnit.module("test_mail", () => {
|
||||
QUnit.module("activity view mobile");
|
||||
|
||||
QUnit.test('horizontal scroll applies only to the content, not to the whole controller', async (assert) => {
|
||||
const viewPort = prepareTarget();
|
||||
viewPort.style.position = "initial";
|
||||
viewPort.style.width = "initial";
|
||||
|
||||
const { openView } = await start();
|
||||
await openView({
|
||||
res_model: "mail.test.activity",
|
||||
views: [[false, "activity"]],
|
||||
});
|
||||
const o_view_controller = document.querySelector(".o_view_controller");
|
||||
const o_content = o_view_controller.querySelector(".o_content");
|
||||
|
||||
const o_cp_buttons = o_view_controller.querySelector(".o_control_panel .o_cp_buttons");
|
||||
const initialXCpBtn = o_cp_buttons.getBoundingClientRect().x;
|
||||
|
||||
const o_header_cell = o_content.querySelector(".o_activity_type_cell");
|
||||
const initialXHeaderCell = o_header_cell.getBoundingClientRect().x;
|
||||
|
||||
assert.hasClass(o_view_controller, "o_action_delegate_scroll",
|
||||
"the 'o_view_controller' should be have the 'o_action_delegate_scroll'.");
|
||||
assert.strictEqual(window.getComputedStyle(o_view_controller).overflow,"hidden",
|
||||
"the view controller should have overflow hidden");
|
||||
assert.strictEqual(window.getComputedStyle(o_content).overflow,"auto",
|
||||
"the view content should have the overflow auto");
|
||||
assert.strictEqual(o_content.scrollLeft, 0, "the o_content should not have scroll value");
|
||||
|
||||
// Horizontal scroll
|
||||
o_content.scrollLeft = 100;
|
||||
|
||||
assert.strictEqual(o_content.scrollLeft, 100, "the o_content should be 100 due to the overflow auto");
|
||||
assert.ok(o_header_cell.getBoundingClientRect().x < initialXHeaderCell,
|
||||
"the gantt header cell x position value should be lower after the scroll");
|
||||
assert.strictEqual(o_cp_buttons.getBoundingClientRect().x, initialXCpBtn,
|
||||
"the btn x position of the control panel button should be the same after the scroll");
|
||||
viewPort.style.position = "";
|
||||
viewPort.style.width = "";
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class MailTestActivity extends models.ServerModel {
|
||||
_name = "mail.test.activity";
|
||||
_inherit = ["mail.thread"];
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class MailTestMultiCompany extends models.ServerModel {
|
||||
_name = "mail.test.multi.company";
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class MailTestMultiCompanyRead extends models.ServerModel {
|
||||
_name = "mail.test.multi.company.read";
|
||||
_mail_post_access = "read";
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class MailTestProperties extends models.ServerModel {
|
||||
_name = "mail.test.properties";
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class MailTestSimple extends models.ServerModel {
|
||||
_name = "mail.test.simple";
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class MailTestSimpleMainAttachment extends models.ServerModel {
|
||||
_name = "mail.test.simple.main.attachment";
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import { fields, models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class MailTestTrackAll extends models.ServerModel {
|
||||
_name = "mail.test.track.all";
|
||||
_inherit = ["mail.thread"];
|
||||
|
||||
float_field_with_digits = fields.Float({
|
||||
digits: [10, 8],
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class ResCurrency extends models.ServerModel {
|
||||
_name = "res.currency";
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
openFormView,
|
||||
registerArchs,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { defineTestMailModels } from "@test_mail/../tests/test_mail_test_helpers";
|
||||
import { asyncStep, onRpc, waitForSteps } from "@web/../tests/web_test_helpers";
|
||||
|
||||
/**
|
||||
* Open a chat window when clicking on an avatar many2one / many2many properties.
|
||||
*/
|
||||
async function testPropertyFieldAvatarOpenChat(propertyType) {
|
||||
const pyEnv = await startServer();
|
||||
registerArchs({
|
||||
"mail.test.properties,false,form": `
|
||||
<form string="Form With Avatar Users">
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
<field name="parent_id"/>
|
||||
<field name="properties"/>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
onRpc("mail.test.properties", "has_access", () => true);
|
||||
onRpc("res.users", "read", () => {
|
||||
asyncStep("read res.users");
|
||||
return [{ id: userId, partner_id: [partnerId, "Partner Test"] }];
|
||||
});
|
||||
onRpc("res.users", "search_read", () => [{ id: userId, name: "User Test" }]);
|
||||
await start();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Partner Test" });
|
||||
const userId = pyEnv["res.users"].create({ partner_id: partnerId });
|
||||
const propertyDefinition = {
|
||||
type: propertyType,
|
||||
comodel: "res.users",
|
||||
name: "user",
|
||||
string: "user",
|
||||
};
|
||||
const parentId = pyEnv["mail.test.properties"].create({
|
||||
name: "Parent",
|
||||
definition_properties: [propertyDefinition],
|
||||
});
|
||||
const childId = pyEnv["mail.test.properties"].create({
|
||||
name: "Test",
|
||||
parent_id: parentId,
|
||||
properties: [{ ...propertyDefinition, value: [userId] }],
|
||||
});
|
||||
|
||||
await openFormView("mail.test.properties", childId);
|
||||
await waitForSteps([]);
|
||||
await click(
|
||||
propertyType === "many2one" ? ".o_field_property_many2one_value img" : ".o_m2m_avatar"
|
||||
);
|
||||
await waitForSteps(["read res.users"]);
|
||||
await contains(".o-mail-ChatWindow", { text: "Partner Test" });
|
||||
}
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineTestMailModels();
|
||||
|
||||
test("Properties fields: many2one avatar open chat on click", async () => {
|
||||
await testPropertyFieldAvatarOpenChat("many2one");
|
||||
});
|
||||
|
||||
test("Properties fields: m2m avatar list open chat on click", async () => {
|
||||
await testPropertyFieldAvatarOpenChat("many2many");
|
||||
});
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
import { start, startServer } from "@mail/../tests/mail_test_helpers";
|
||||
import { click, contains } from "@mail/../tests/mail_test_helpers_contains";
|
||||
import { beforeEach, describe, expect, test } from "@odoo/hoot";
|
||||
import { mockDate } from "@odoo/hoot-mock";
|
||||
import { defineTestMailModels } from "@test_mail/../tests/test_mail_test_helpers";
|
||||
import { asyncStep, mockService, waitForSteps } from "@web/../tests/web_test_helpers";
|
||||
import { serializeDate, today } from "@web/core/l10n/dates";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineTestMailModels();
|
||||
// Avoid problem around midnight (Ex.: tomorrow activities become today activities when reaching midnight)
|
||||
beforeEach(() => mockDate("2023-04-08 10:00:00", 0));
|
||||
|
||||
test("menu with no records", async () => {
|
||||
await start();
|
||||
await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Activities'])");
|
||||
await contains(".o-mail-ActivityMenu", {
|
||||
text: "Congratulations, you're done with your activities.",
|
||||
});
|
||||
});
|
||||
|
||||
test("do not show empty text when at least some future activities", async () => {
|
||||
const tomorrow = today().plus({ days: 1 });
|
||||
const pyEnv = await startServer();
|
||||
const activityId = pyEnv["mail.test.activity"].create({});
|
||||
pyEnv["mail.activity"].create([
|
||||
{
|
||||
date_deadline: serializeDate(tomorrow),
|
||||
res_id: activityId,
|
||||
res_model: "mail.test.activity",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Activities'])");
|
||||
await contains(".o-mail-ActivityMenu", {
|
||||
count: 0,
|
||||
text: "Congratulations, you're done with your activities.",
|
||||
});
|
||||
});
|
||||
|
||||
test("activity menu widget: activity menu with 2 models", async () => {
|
||||
const tomorrow = today().plus({ days: 1 });
|
||||
const yesterday = today().plus({ days: -1 });
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
const activityIds = pyEnv["mail.test.activity"].create([{}, {}, {}, {}]);
|
||||
pyEnv["mail.activity"].create([
|
||||
{ res_id: partnerId, res_model: "res.partner", date_deadline: serializeDate(today()) },
|
||||
{
|
||||
res_id: activityIds[0],
|
||||
res_model: "mail.test.activity",
|
||||
date_deadline: serializeDate(today()),
|
||||
},
|
||||
{
|
||||
date_deadline: serializeDate(tomorrow),
|
||||
res_id: activityIds[1],
|
||||
res_model: "mail.test.activity",
|
||||
},
|
||||
{
|
||||
date_deadline: serializeDate(tomorrow),
|
||||
res_id: activityIds[2],
|
||||
res_model: "mail.test.activity",
|
||||
},
|
||||
{
|
||||
date_deadline: serializeDate(yesterday),
|
||||
res_id: activityIds[3],
|
||||
res_model: "mail.test.activity",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await contains(".o_menu_systray i[aria-label='Activities']");
|
||||
await contains(".o-mail-ActivityMenu-counter");
|
||||
await contains(".o-mail-ActivityMenu-counter", { text: "5" });
|
||||
const actionChecks = {
|
||||
context: {
|
||||
force_search_count: 1,
|
||||
search_default_filter_activities_my: 1,
|
||||
search_default_activities_overdue: 1,
|
||||
search_default_activities_today: 1,
|
||||
},
|
||||
domain: [["active", "in", [true, false]]],
|
||||
};
|
||||
mockService("action", {
|
||||
doAction(action) {
|
||||
Object.entries(actionChecks).forEach(([key, value]) => {
|
||||
if (Array.isArray(value) || typeof value === "object") {
|
||||
expect(action[key]).toEqual(value);
|
||||
} else {
|
||||
expect(action[key]).toBe(value);
|
||||
}
|
||||
});
|
||||
asyncStep("do_action:" + action.name);
|
||||
},
|
||||
});
|
||||
await click(".o_menu_systray i[aria-label='Activities']");
|
||||
await contains(".o-mail-ActivityMenu");
|
||||
await contains(".o-mail-ActivityMenu .o-mail-ActivityGroup", { count: 2 });
|
||||
await contains(".o-mail-ActivityMenu .o-mail-ActivityGroup", {
|
||||
contains: [
|
||||
["div[name='activityTitle']", { text: "res.partner" }],
|
||||
["span", { text: "0 Late" }],
|
||||
["span", { text: "1 Today" }],
|
||||
["span", { text: "0 Future" }],
|
||||
],
|
||||
});
|
||||
await contains(".o-mail-ActivityMenu .o-mail-ActivityGroup", {
|
||||
contains: [
|
||||
["div[name='activityTitle']", { text: "mail.test.activity" }],
|
||||
["span", { text: "1 Late" }],
|
||||
["span", { text: "1 Today" }],
|
||||
["span", { text: "2 Future" }],
|
||||
],
|
||||
});
|
||||
actionChecks.res_model = "res.partner";
|
||||
await click(".o-mail-ActivityMenu .o-mail-ActivityGroup", { text: "res.partner" });
|
||||
await contains(".o-mail-ActivityMenu", { count: 0 });
|
||||
await click(".o_menu_systray i[aria-label='Activities']");
|
||||
actionChecks.res_model = "mail.test.activity";
|
||||
await click(".o-mail-ActivityMenu .o-mail-ActivityGroup", { text: "mail.test.activity" });
|
||||
await waitForSteps(["do_action:res.partner", "do_action:mail.test.activity"]);
|
||||
});
|
||||
|
||||
test("activity menu widget: close on messaging menu click", async () => {
|
||||
await start();
|
||||
await click(".o_menu_systray i[aria-label='Activities']");
|
||||
await contains(".o-mail-ActivityMenu");
|
||||
await click(".o_menu_systray i[aria-label='Messages']");
|
||||
await contains(".o-mail-ActivityMenu", { count: 0 });
|
||||
});
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import {
|
||||
start,
|
||||
startServer,
|
||||
} from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
import session from 'web.session';
|
||||
import { date_to_str } from 'web.time';
|
||||
import { patchWithCleanup } from '@web/../tests/helpers/utils';
|
||||
|
||||
|
||||
QUnit.module('test_mail', {}, function () {
|
||||
QUnit.module('systray_activity_menu_tests.js', {
|
||||
async beforeEach() {
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
const yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
const pyEnv = await startServer();
|
||||
const resPartnerId1 = pyEnv['res.partner'].create({});
|
||||
const mailTestActivityIds = pyEnv['mail.test.activity'].create([{}, {}, {}, {}]);
|
||||
pyEnv['mail.activity'].create([
|
||||
{ res_id: resPartnerId1, res_model: 'res.partner' },
|
||||
{ res_id: mailTestActivityIds[0], res_model: 'mail.test.activity' },
|
||||
{ date_deadline: date_to_str(tomorrow), res_id: mailTestActivityIds[1], res_model: 'mail.test.activity' },
|
||||
{ date_deadline: date_to_str(tomorrow), res_id: mailTestActivityIds[2], res_model: 'mail.test.activity' },
|
||||
{ date_deadline: date_to_str(yesterday), res_id: mailTestActivityIds[3], res_model: 'mail.test.activity' },
|
||||
]);
|
||||
},
|
||||
});
|
||||
|
||||
QUnit.test('activity menu widget: menu with no records', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const { click } = await start({
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'systray_get_activities') {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
},
|
||||
});
|
||||
await click('.o_ActivityMenuView_dropdownToggle');
|
||||
assert.containsOnce(document.body, '.o_ActivityMenuView_noActivity');
|
||||
});
|
||||
|
||||
QUnit.test('activity menu widget: activity menu with 2 models', async function (assert) {
|
||||
assert.expect(10);
|
||||
|
||||
const { click, env } = await start();
|
||||
|
||||
await click('.o_ActivityMenuView_dropdownToggle');
|
||||
assert.containsOnce(document.body, '.o_ActivityMenuView', 'should contain an instance of widget');
|
||||
assert.ok(document.querySelectorAll('.o_ActivityMenuView_activityGroup').length);
|
||||
assert.containsOnce(document.body, '.o_ActivityMenuView_counter', "widget should have notification counter");
|
||||
assert.strictEqual(parseInt(document.querySelector('.o_ActivityMenuView_counter').innerText), 5, "widget should have 5 notification counter");
|
||||
|
||||
var context = {};
|
||||
patchWithCleanup(env.services.action, {
|
||||
doAction(action) {
|
||||
assert.deepEqual(action.context, context, "wrong context value");
|
||||
},
|
||||
});
|
||||
|
||||
// case 1: click on "late"
|
||||
context = {
|
||||
force_search_count: 1,
|
||||
search_default_activities_overdue: 1,
|
||||
};
|
||||
assert.containsOnce(document.body, '.o_ActivityMenuView_dropdownMenu.show', 'ActivityMenu should be open');
|
||||
await click('.o_ActivityMenuView_activityGroupFilterButton[data-model_name="mail.test.activity"][data-filter="overdue"]');
|
||||
assert.containsNone(document.body, '.show', 'ActivityMenu should be closed');
|
||||
// case 2: click on "today"
|
||||
context = {
|
||||
force_search_count: 1,
|
||||
search_default_activities_today: 1,
|
||||
};
|
||||
await click('.dropdown-toggle[title="Activities"]');
|
||||
await click('.o_ActivityMenuView_activityGroupFilterButton[data-model_name="mail.test.activity"][data-filter="today"]');
|
||||
// case 3: click on "future"
|
||||
context = {
|
||||
force_search_count: 1,
|
||||
search_default_activities_upcoming_all: 1,
|
||||
};
|
||||
await click('.dropdown-toggle[title="Activities"]');
|
||||
await click('.o_ActivityMenuView_activityGroupFilterButton[data-model_name="mail.test.activity"][data-filter="upcoming_all"]');
|
||||
// case 4: click anywere else
|
||||
context = {
|
||||
force_search_count: 1,
|
||||
search_default_activities_overdue: 1,
|
||||
search_default_activities_today: 1,
|
||||
};
|
||||
await click('.dropdown-toggle[title="Activities"]');
|
||||
await click('.o_ActivityMenuView_activityGroups > div[data-model_name="mail.test.activity"]');
|
||||
});
|
||||
|
||||
QUnit.test('activity menu widget: activity view icon', async function (assert) {
|
||||
assert.expect(14);
|
||||
|
||||
patchWithCleanup(session, { uid: 10 });
|
||||
const { click, env } = await start();
|
||||
|
||||
await click('.o_ActivityMenuView_dropdownToggle');
|
||||
assert.containsN(document.body, '.o_ActivityMenuView_activityGroupActionButton', 2,
|
||||
"widget should have 2 activity view icons");
|
||||
|
||||
var first = document.querySelector('.o_ActivityMenuView_activityGroupActionButton[data-model_name="res.partner"]');
|
||||
var second = document.querySelector('.o_ActivityMenuView_activityGroupActionButton[data-model_name="mail.test.activity"]');
|
||||
assert.ok(first, "should have activity action linked to 'res.partner'");
|
||||
assert.hasClass(first, 'fa-clock-o', "should display the activity action icon");
|
||||
|
||||
assert.ok(second, "should have activity action linked to 'mail.test.activity'");
|
||||
assert.hasClass(second, 'fa-clock-o', "should display the activity action icon");
|
||||
|
||||
patchWithCleanup(env.services.action, {
|
||||
doAction(action) {
|
||||
if (action.name) {
|
||||
assert.ok(action.domain, "should define a domain on the action");
|
||||
assert.deepEqual(action.domain, [["activity_ids.user_id", "=", 10]],
|
||||
"should set domain to user's activity only");
|
||||
assert.step('do_action:' + action.name);
|
||||
} else {
|
||||
assert.step('do_action:' + action);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
assert.hasClass(document.querySelector('.o-dropdown-menu'), 'show',
|
||||
"dropdown should be expanded");
|
||||
|
||||
await click('.o_ActivityMenuView_activityGroupActionButton[data-model_name="mail.test.activity"]');
|
||||
assert.containsNone(document.body, '.o-dropdown-menu',
|
||||
"dropdown should be collapsed");
|
||||
|
||||
// click on the "res.partner" activity icon
|
||||
await click('.dropdown-toggle[title="Activities"]');
|
||||
await click('.o_ActivityMenuView_activityGroupActionButton[data-model_name="res.partner"]');
|
||||
|
||||
assert.verifySteps([
|
||||
'do_action:mail.test.activity',
|
||||
'do_action:res.partner'
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test('activity menu widget: close on messaging menu click', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const { click } = await start();
|
||||
|
||||
await click('.dropdown-toggle[title="Activities"]');
|
||||
assert.hasClass(
|
||||
document.querySelector('.o_ActivityMenuView_dropdownMenu'),
|
||||
'show',
|
||||
"activity menu should be shown after click on itself"
|
||||
);
|
||||
|
||||
await click(`.o_MessagingMenu_toggler`);
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_ActivityMenuView_dropdownMenu',
|
||||
"activity menu should be hidden after click on messaging menu"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import { contains, mailModels } from "@mail/../tests/mail_test_helpers";
|
||||
import { MailTestActivity } from "@test_mail/../tests/mock_server/models/mail_test_activity";
|
||||
import { MailTestMultiCompany } from "@test_mail/../tests/mock_server/models/mail_test_multi_company";
|
||||
import { MailTestMultiCompanyRead } from "@test_mail/../tests/mock_server/models/mail_test_multi_company_read";
|
||||
import { MailTestProperties } from "@test_mail/../tests/mock_server/models/mail_test_properties";
|
||||
import { MailTestSimpleMainAttachment } from "./mock_server/models/mail_test_simple_main_attachment";
|
||||
import { MailTestSimple } from "@test_mail/../tests/mock_server/models/mail_test_simple";
|
||||
import { MailTestTrackAll } from "@test_mail/../tests/mock_server/models/mail_test_track_all";
|
||||
import { defineModels, defineParams } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export const testMailModels = {
|
||||
...mailModels,
|
||||
MailTestActivity,
|
||||
MailTestMultiCompany,
|
||||
MailTestMultiCompanyRead,
|
||||
MailTestProperties,
|
||||
MailTestSimpleMainAttachment,
|
||||
MailTestSimple,
|
||||
MailTestTrackAll,
|
||||
};
|
||||
|
||||
export function defineTestMailModels() {
|
||||
defineParams({ suite: "test_mail" }, "replace");
|
||||
defineModels(testMailModels);
|
||||
}
|
||||
|
||||
export async function editSelect(selector, value) {
|
||||
await contains(selector);
|
||||
const el = document.querySelector(selector);
|
||||
el.value = value;
|
||||
el.dispatchEvent(new Event("change"));
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
|
||||
const setPager = value => [
|
||||
{
|
||||
content: "Click Pager",
|
||||
trigger: ".o_pager_value:first()",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Change pager to display lines " + value,
|
||||
trigger: "input.o_pager_value",
|
||||
run: `edit ${value} && click body`,
|
||||
},
|
||||
{
|
||||
trigger: `.o_pager_value:contains('${value}')`,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
const checkRows = values => {
|
||||
return {
|
||||
trigger: '.o_activity_view',
|
||||
run: () => {
|
||||
const dataRow = document.querySelectorAll('.o_activity_view tbody .o_data_row .o_activity_record');
|
||||
if (dataRow.length !== values.length) {
|
||||
throw Error(`There should be ${values.length} activities`);
|
||||
}
|
||||
values.forEach((value, index) => {
|
||||
if (dataRow[index].textContent !== value) {
|
||||
throw Error(`Record does not match ${value} != ${dataRow[index]}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("web_tour.tours").add("mail_activity_view", {
|
||||
steps: () => [
|
||||
{
|
||||
content: "Open the debug menu",
|
||||
trigger: ".o_debug_manager button",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Click the Set Defaults menu",
|
||||
trigger: ".o-dropdown-item:contains(Open View)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_searchview_input",
|
||||
run: "edit Test Activity View"
|
||||
},
|
||||
{
|
||||
trigger: ".o_searchview_autocomplete .o-dropdown-item.focus",
|
||||
content: "Validate search",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Select Test Activity View",
|
||||
trigger: `.o_data_row td:contains("Test Activity View")`,
|
||||
run: "click",
|
||||
},
|
||||
checkRows(["Task 1", "Task 2", "Task 3"]),
|
||||
...setPager("1-2"),
|
||||
checkRows(["Task 2", "Task 3"]),
|
||||
...setPager("3"),
|
||||
checkRows(["Task 1"]),
|
||||
],
|
||||
})
|
||||
|
|
@ -0,0 +1,377 @@
|
|||
import {
|
||||
contains,
|
||||
click,
|
||||
insertText,
|
||||
openFormView,
|
||||
registerArchs,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { beforeEach, describe, expect, test } from "@odoo/hoot";
|
||||
import { mockDate, mockTimeZone } from "@odoo/hoot-mock";
|
||||
import { defineTestMailModels } from "@test_mail/../tests/test_mail_test_helpers";
|
||||
import { editSelectMenu, patchWithCleanup } from "@web/../tests/web_test_helpers";
|
||||
import { currencies } from "@web/core/currency";
|
||||
|
||||
const archs = {
|
||||
"mail.test.track.all,false,form": `
|
||||
<form>
|
||||
<sheet>
|
||||
<field name="boolean_field"/>
|
||||
<field name="char_field"/>
|
||||
<field name="date_field"/>
|
||||
<field name="datetime_field"/>
|
||||
<field name="float_field"/>
|
||||
<field name="float_field_with_digits"/>
|
||||
<field name="integer_field"/>
|
||||
<field name="monetary_field"/>
|
||||
<field name="many2one_field_id"/>
|
||||
<field name="selection_field"/>
|
||||
<field name="text_field"/>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
`,
|
||||
};
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineTestMailModels();
|
||||
beforeEach(() => mockTimeZone(0));
|
||||
|
||||
test("basic rendering of tracking value (float type)", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({ float_field: 12.3 });
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId1);
|
||||
await insertText("div[name=float_field] input", "45.67", { replace: true });
|
||||
await click(".o_form_button_save");
|
||||
await contains(".o-mail-Message-tracking");
|
||||
await contains(".o-mail-Message-trackingField");
|
||||
await contains(".o-mail-Message-trackingField", { text: "(Float)" });
|
||||
await contains(".o-mail-Message-trackingOld");
|
||||
await contains(".o-mail-Message-trackingOld", { text: "12.30" });
|
||||
await contains(".o-mail-Message-trackingSeparator");
|
||||
await contains(".o-mail-Message-trackingNew");
|
||||
await contains(".o-mail-Message-trackingNew", { text: "45.67" });
|
||||
});
|
||||
|
||||
test("rendering of tracked field of type float: from non-0 to 0", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
|
||||
float_field: 1,
|
||||
});
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId1);
|
||||
await insertText("div[name=float_field] input", "0", { replace: true });
|
||||
await click(".o_form_button_save");
|
||||
await contains(".o-mail-Message-tracking", { text: "1.000.00(Float)" });
|
||||
});
|
||||
|
||||
test("rendering of tracked field of type float: from 0 to non-0", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
|
||||
float_field: 0,
|
||||
float_field_with_digits: 0,
|
||||
});
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId1);
|
||||
await insertText("div[name=float_field] input", "1.01", { replace: true });
|
||||
await insertText("div[name=float_field_with_digits] input", "1.0001", { replace: true });
|
||||
await click(".o_form_button_save");
|
||||
await contains(".o-mail-Message-tracking", { count: 2 });
|
||||
const [increasedPrecisionLine, defaultPrecisionLine] =
|
||||
document.getElementsByClassName("o-mail-Message-tracking");
|
||||
const expectedText = [
|
||||
[defaultPrecisionLine, ["0.00", "1.01", "(Float)"]],
|
||||
[increasedPrecisionLine, ["0.00000000", "1.00010000", "(Float)"]],
|
||||
];
|
||||
for (const [targetLine, [oldText, newText, fieldName]] of expectedText) {
|
||||
await contains(".o-mail-Message-trackingOld", { target: targetLine, text: oldText });
|
||||
await contains(".o-mail-Message-trackingNew", { target: targetLine, text: newText });
|
||||
await contains(".o-mail-Message-trackingField", { target: targetLine, text: fieldName });
|
||||
}
|
||||
});
|
||||
|
||||
test("rendering of tracked field of type integer: from non-0 to 0", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
|
||||
integer_field: 1,
|
||||
});
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId1);
|
||||
await insertText("div[name=integer_field] input", "0", { replace: true });
|
||||
await click(".o_form_button_save");
|
||||
await contains(".o-mail-Message-tracking", { text: "10(Integer)" });
|
||||
});
|
||||
|
||||
test("rendering of tracked field of type integer: from 0 to non-0", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
|
||||
integer_field: 0,
|
||||
});
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId1);
|
||||
await insertText("div[name=integer_field] input", "1", { replace: true });
|
||||
await click(".o_form_button_save");
|
||||
await contains(".o-mail-Message-tracking", { text: "01(Integer)" });
|
||||
});
|
||||
|
||||
test("rendering of tracked field of type monetary: from non-0 to 0", async () => {
|
||||
const pyEnv = await startServer();
|
||||
|
||||
const testCurrencyId = pyEnv["res.currency"].create({ name: "ECU", symbol: "§" });
|
||||
// need to patch currencies as they're passed via cookies, not through the orm
|
||||
patchWithCleanup(currencies, {
|
||||
[testCurrencyId]: { digits: [69, 2], position: "after", symbol: "§" },
|
||||
});
|
||||
|
||||
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
|
||||
currency_id: testCurrencyId,
|
||||
monetary_field: 1,
|
||||
});
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId1);
|
||||
await insertText("div[name=monetary_field] input", "0", { replace: true });
|
||||
await click(".o_form_button_save");
|
||||
await contains(".o-mail-Message-tracking", { text: "1.00 §0.00 §(Monetary)" });
|
||||
});
|
||||
|
||||
test("rendering of tracked field of type monetary: from 0 to non-0", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
|
||||
monetary_field: 0,
|
||||
});
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId1);
|
||||
await insertText("div[name=monetary_field] input", "1", { replace: true });
|
||||
await click(".o_form_button_save");
|
||||
await contains(".o-mail-Message-tracking", { text: "0.001.00(Monetary)" });
|
||||
});
|
||||
|
||||
test("rendering of tracked field of type boolean: from true to false", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
|
||||
boolean_field: true,
|
||||
});
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId1);
|
||||
await click(".o_field_boolean input");
|
||||
await click(".o_form_button_save");
|
||||
await contains(".o-mail-Message-tracking", { text: "YesNo(Boolean)" });
|
||||
});
|
||||
|
||||
test("rendering of tracked field of type boolean: from false to true", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({});
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId1);
|
||||
await click(".o_field_boolean input");
|
||||
await click(".o_form_button_save");
|
||||
await contains(".o-mail-Message-tracking", { text: "NoYes(Boolean)" });
|
||||
});
|
||||
|
||||
test("rendering of tracked field of type char: from a string to empty string", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
|
||||
char_field: "Marc",
|
||||
});
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId1);
|
||||
await insertText("div[name=char_field] input", "", { replace: true });
|
||||
await click(".o_form_button_save");
|
||||
await contains(".o-mail-Message-tracking", { text: "MarcNone(Char)" });
|
||||
});
|
||||
|
||||
test("rendering of tracked field of type char: from empty string to a string", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
|
||||
char_field: "",
|
||||
});
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId1);
|
||||
await insertText("div[name=char_field] input", "Marc", { replace: true });
|
||||
await click(".o_form_button_save");
|
||||
await contains(".o-mail-Message-tracking", { text: "NoneMarc(Char)" });
|
||||
});
|
||||
|
||||
test("rendering of tracked field of type date: from no date to a set date", async () => {
|
||||
mockDate("2018-12-01");
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
|
||||
date_field: false,
|
||||
});
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId1);
|
||||
await click("div[name=date_field] input");
|
||||
await click(".o_datetime_button", { text: "14" });
|
||||
await click(".o_form_button_save");
|
||||
await contains(".o-mail-Message-tracking", { text: "None12/14/2018(Date)" });
|
||||
});
|
||||
|
||||
test("rendering of tracked field of type date: from a set date to no date", async () => {
|
||||
mockDate("2018-12-01");
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
|
||||
date_field: "2018-12-14",
|
||||
});
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId1);
|
||||
await click("div[name=date_field] button");
|
||||
await insertText("div[name=date_field] input", "", { replace: true });
|
||||
await click(".o_form_button_save");
|
||||
await contains(".o-mail-Message-tracking", { text: "12/14/2018None(Date)" });
|
||||
});
|
||||
|
||||
test("rendering of tracked field of type datetime: from no date and time to a set date and time", async function () {
|
||||
mockDate("2018-12-01", 3);
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
|
||||
datetime_field: false,
|
||||
});
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId1);
|
||||
await click("div[name=datetime_field] input");
|
||||
await click(".o_datetime_button", { text: "14" });
|
||||
await click(".o_form_button_save");
|
||||
await contains(".o-mail-Message-tracking", { text: "None12/14/2018 12:00:00(Datetime)" });
|
||||
const [savedRecord] = pyEnv["mail.test.track.all"].search_read([
|
||||
["id", "=", mailTestTrackAllId1],
|
||||
]);
|
||||
expect(savedRecord.datetime_field).toBe("2018-12-14 09:00:00");
|
||||
});
|
||||
|
||||
test("rendering of tracked field of type datetime: from a set date and time to no date and time", async () => {
|
||||
mockTimeZone(3);
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
|
||||
datetime_field: "2018-12-14 13:42:28 ",
|
||||
});
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId1);
|
||||
await click("div[name=datetime_field] button");
|
||||
await insertText("div[name=datetime_field] input", "", { replace: true });
|
||||
await click(".o_form_button_save");
|
||||
await contains(".o-mail-Message-tracking", { text: "12/14/2018 16:42:28None(Datetime)" });
|
||||
});
|
||||
|
||||
test("rendering of tracked field of type text: from some text to empty", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
|
||||
text_field: "Marc",
|
||||
});
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId1);
|
||||
await insertText("div[name=text_field] textarea", "", { replace: true });
|
||||
await click(".o_form_button_save");
|
||||
await contains(".o-mail-Message-tracking", { text: "MarcNone(Text)" });
|
||||
});
|
||||
|
||||
test("rendering of tracked field of type text: from empty to some text", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
|
||||
text_field: "",
|
||||
});
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId1);
|
||||
await insertText("div[name=text_field] textarea", "Marc", { replace: true });
|
||||
await click(".o_form_button_save");
|
||||
await contains(".o-mail-Message-tracking", { text: "NoneMarc(Text)" });
|
||||
});
|
||||
|
||||
test("rendering of tracked field of type selection: from a selection to no selection", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
|
||||
selection_field: "first",
|
||||
});
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId1);
|
||||
await editSelectMenu("div[name=selection_field] input", { value: "" });
|
||||
await click(".o_form_button_save");
|
||||
await contains(".o-mail-Message-tracking", { text: "firstNone(Selection)" });
|
||||
});
|
||||
|
||||
test("rendering of tracked field of type selection: from no selection to a selection", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({});
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId1);
|
||||
await editSelectMenu("div[name=selection_field] input", { value: "First" });
|
||||
await click(".o_form_button_save");
|
||||
await contains(".o-mail-Message-tracking", { text: "Nonefirst(Selection)" });
|
||||
});
|
||||
|
||||
test("rendering of tracked field of type many2one: from having a related record to no related record", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const resPartnerId1 = pyEnv["res.partner"].create({ name: "Marc" });
|
||||
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
|
||||
many2one_field_id: resPartnerId1,
|
||||
});
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId1);
|
||||
await insertText(".o_field_many2one_selection input", "", { replace: true });
|
||||
await click(".o_form_button_save");
|
||||
await contains(".o-mail-Message-tracking", { text: "MarcNone(Many2one)" });
|
||||
});
|
||||
|
||||
test("rendering of tracked field of type many2one: from no related record to having a related record", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.partner"].create({ name: "Marc" });
|
||||
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({});
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId1);
|
||||
await click("[name=many2one_field_id] input");
|
||||
await click("[name=many2one_field_id] .o-autocomplete--dropdown-item", { text: "Marc" });
|
||||
await click(".o_form_button_save");
|
||||
await contains(".o-mail-Message-tracking", { text: "NoneMarc(Many2one)" });
|
||||
});
|
||||
|
||||
test("Search message with filter in chatter", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId = pyEnv["mail.test.track.all"].create({});
|
||||
pyEnv["mail.message"].create({
|
||||
body: "Hermit",
|
||||
model: "mail.test.track.all",
|
||||
res_id: mailTestTrackAllId,
|
||||
});
|
||||
await start();
|
||||
registerArchs(archs);
|
||||
await openFormView("mail.test.track.all", mailTestTrackAllId);
|
||||
await click("[name=many2one_field_id] input");
|
||||
await click("[name=many2one_field_id] .o-autocomplete--dropdown-item", { text: "Hermit" });
|
||||
await click(".o_form_button_save");
|
||||
// Search message with filter
|
||||
await click("[title='Search Messages']");
|
||||
await insertText(".o_searchview_input", "Hermit");
|
||||
await click("button[title='Filter Messages']");
|
||||
await click("span", { text: "Conversations" });
|
||||
await contains(".o-mail-SearchMessageResult .o-mail-Message", { text: "Hermit" });
|
||||
|
||||
await click("button[title='Filter Messages']");
|
||||
await click("span", { text: "Tracked Changes" });
|
||||
await contains(".o-mail-SearchMessageResult .o-mail-Message", { text: "Hermit" });
|
||||
|
||||
await click("button[title='Filter Messages']");
|
||||
await click("span", { text: "All" });
|
||||
await contains(".o-mail-SearchMessageResult .o-mail-Message", { count: 2 });
|
||||
});
|
||||
|
|
@ -1,438 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import {
|
||||
start,
|
||||
startServer,
|
||||
} from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
import { editInput, editSelect, selectDropdownItem, patchWithCleanup, patchTimeZone } from "@web/../tests/helpers/utils";
|
||||
|
||||
import session from 'web.session';
|
||||
import testUtils from 'web.test_utils';
|
||||
|
||||
QUnit.module('test_mail', {}, function () {
|
||||
QUnit.module('tracking_value_tests.js', {
|
||||
beforeEach() {
|
||||
const views = {
|
||||
'mail.test.track.all,false,form':
|
||||
`<form>
|
||||
<sheet>
|
||||
<field name="boolean_field"/>
|
||||
<field name="char_field"/>
|
||||
<field name="date_field"/>
|
||||
<field name="datetime_field"/>
|
||||
<field name="float_field"/>
|
||||
<field name="integer_field"/>
|
||||
<field name="monetary_field"/>
|
||||
<field name="many2one_field_id"/>
|
||||
<field name="selection_field"/>
|
||||
<field name="text_field"/>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids"/>
|
||||
</div>
|
||||
</form>`,
|
||||
};
|
||||
this.start = async ({ res_id }) => {
|
||||
const { openFormView, ...remainder } = await start({
|
||||
serverData: { views },
|
||||
});
|
||||
await openFormView(
|
||||
{
|
||||
res_model: 'mail.test.track.all',
|
||||
res_id,
|
||||
},
|
||||
{
|
||||
props: { mode: 'edit' },
|
||||
},
|
||||
);
|
||||
return remainder;
|
||||
};
|
||||
|
||||
patchWithCleanup(session, {
|
||||
getTZOffset() {
|
||||
return 0;
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
QUnit.test('basic rendering of tracking value (float type)', async function (assert) {
|
||||
assert.expect(8);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ float_field: 12.30 });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await editInput(document.body, 'div[name=float_field] input', 45.67);
|
||||
await click('.o_form_button_save');
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_TrackingValue',
|
||||
"should display a tracking value"
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_TrackingValue_fieldName',
|
||||
"should display the name of the tracked field"
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue_fieldName').textContent,
|
||||
"(Float)",
|
||||
"should display the correct tracked field name (Float)",
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_TrackingValue_oldValue',
|
||||
"should display the old value"
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue_oldValue').textContent,
|
||||
"12.30",
|
||||
"should display the correct old value (12.30)",
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_TrackingValue_separator',
|
||||
"should display the separator"
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_TrackingValue_newValue',
|
||||
"should display the new value"
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue_newValue').textContent,
|
||||
"45.67",
|
||||
"should display the correct new value (45.67)",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('rendering of tracked field of type float: from non-0 to 0', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ float_field: 1 });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await editInput(document.body, 'div[name=float_field] input', 0);
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"1.000.00(Float)",
|
||||
"should display the correct content of tracked field of type float: from non-0 to 0 (1.00 -> 0.00 (Float))"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('rendering of tracked field of type float: from 0 to non-0', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ float_field: 0 });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await editInput(document.body, 'div[name=float_field] input', 1);
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"0.001.00(Float)",
|
||||
"should display the correct content of tracked field of type float: from 0 to non-0 (0.00 -> 1.00 (Float))"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('rendering of tracked field of type integer: from non-0 to 0', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ integer_field: 1 });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await editInput(document.body, 'div[name=integer_field] input', 0);
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"10(Integer)",
|
||||
"should display the correct content of tracked field of type integer: from non-0 to 0 (1 -> 0 (Integer))"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('rendering of tracked field of type integer: from 0 to non-0', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ integer_field: 0 });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await editInput(document.body, 'div[name=integer_field] input', 1);
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"01(Integer)",
|
||||
"should display the correct content of tracked field of type integer: from 0 to non-0 (0 -> 1 (Integer))"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('rendering of tracked field of type monetary: from non-0 to 0', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ monetary_field: 1 });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await editInput(document.body, 'div[name=monetary_field] input', 0);
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"1.000.00(Monetary)",
|
||||
"should display the correct content of tracked field of type monetary: from non-0 to 0 (1.00 -> 0.00 (Monetary))"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('rendering of tracked field of type monetary: from 0 to non-0', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ monetary_field: 0 });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await editInput(document.body, 'div[name=monetary_field] input', 1);
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"0.001.00(Monetary)",
|
||||
"should display the correct content of tracked field of type monetary: from 0 to non-0 (0.00 -> 1.00 (Monetary))"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('rendering of tracked field of type boolean: from true to false', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ boolean_field: true });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
document.querySelector('.o_field_boolean input').click();
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"YesNo(Boolean)",
|
||||
"should display the correct content of tracked field of type boolean: from true to false (True -> False (Boolean))"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('rendering of tracked field of type boolean: from false to true', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({});
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
document.querySelector('.o_field_boolean input').click();
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"NoYes(Boolean)",
|
||||
"should display the correct content of tracked field of type boolean: from false to true (False -> True (Boolean))"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('rendering of tracked field of type char: from a string to empty string', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ char_field: 'Marc' });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await editInput(document.body, 'div[name=char_field] input', '');
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"MarcNone(Char)",
|
||||
"should display the correct content of tracked field of type char: from a string to empty string (Marc -> None (Char))"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('rendering of tracked field of type char: from empty string to a string', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ char_field: '' });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await editInput(document.body, 'div[name=char_field] input', 'Marc');
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"NoneMarc(Char)",
|
||||
"should display the correct content of tracked field of type char: from empty string to a string (None -> Marc (Char))"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('rendering of tracked field of type date: from no date to a set date', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ date_field: false });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editAndTrigger(document.querySelector('div[name=date_field] .o_datepicker .o_datepicker_input'), '12/14/2018', ['change']);
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"None12/14/2018(Date)",
|
||||
"should display the correct content of tracked field of type date: from no date to a set date (None -> 12/14/2018 (Date))"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('rendering of tracked field of type date: from a set date to no date', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ date_field: '2018-12-14' });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editAndTrigger(document.querySelector('div[name=date_field] .o_datepicker .o_datepicker_input'), '', ['change']);
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"12/14/2018None(Date)",
|
||||
"should display the correct content of tracked field of type date: from a set date to no date (12/14/2018 -> None (Date))"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('rendering of tracked field of type datetime: from no date and time to a set date and time', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
patchTimeZone(180);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ datetime_field: false });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editAndTrigger(document.querySelector('div[name=datetime_field] .o_datepicker .o_datepicker_input'), '12/14/2018 13:42:28', ['change']);
|
||||
await click('.o_form_button_save');
|
||||
const savedRecord = pyEnv.getData()["mail.test.track.all"].records.find(({id}) => id === mailTestTrackAllId1);
|
||||
assert.strictEqual(savedRecord.datetime_field, '2018-12-14 10:42:28');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"None12/14/2018 13:42:28(Datetime)",
|
||||
"should display the correct content of tracked field of type datetime: from no date and time to a set date and time (None -> 12/14/2018 13:42:28 (Datetime))"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('rendering of tracked field of type datetime: from a set date and time to no date and time', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
patchTimeZone(180)
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ datetime_field: '2018-12-14 13:42:28 ' });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editAndTrigger(document.querySelector('div[name=datetime_field] .o_datepicker .o_datepicker_input'), '', ['change']);
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"12/14/2018 16:42:28None(Datetime)",
|
||||
"should display the correct content of tracked field of type datetime: from a set date and time to no date and time (12/14/2018 13:42:28 -> None (Datetime))"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('rendering of tracked field of type text: from some text to empty', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ text_field: 'Marc' });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await editInput(document.body, 'div[name=text_field] textarea', '');
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"MarcNone(Text)",
|
||||
"should display the correct content of tracked field of type text: from some text to empty (Marc -> None (Text))"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('rendering of tracked field of type text: from empty to some text', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ text_field: '' });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await editInput(document.body, 'div[name=text_field] textarea', 'Marc');
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"NoneMarc(Text)",
|
||||
"should display the correct content of tracked field of type text: from empty to some text (None -> Marc (Text))"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('rendering of tracked field of type selection: from a selection to no selection', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ selection_field: 'first' });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await editSelect(document.body, 'div[name=selection_field] select', false);
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"firstNone(Selection)",
|
||||
"should display the correct content of tracked field of type selection: from a selection to no selection (first -> None (Selection))"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('rendering of tracked field of type selection: from no selection to a selection', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({});
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await editSelect(document.body, 'div[name=selection_field] select', '"first"');
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"Nonefirst(Selection)",
|
||||
"should display the correct content of tracked field of type selection: from no selection to a selection (None -> first (Selection))"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('rendering of tracked field of type many2one: from having a related record to no related record', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const resPartnerId1 = pyEnv['res.partner'].create({ display_name: 'Marc' });
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ many2one_field_id: resPartnerId1 });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await editInput(document.body, ".o_field_many2one_selection input", '')
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"MarcNone(Many2one)",
|
||||
"should display the correct content of tracked field of type many2one: from having a related record to no related record (Marc -> None (Many2one))"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('rendering of tracked field of type many2one: from no related record to having a related record', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
pyEnv['res.partner'].create({ display_name: 'Marc' });
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({});
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await selectDropdownItem(document.body, "many2one_field_id", "Marc")
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"NoneMarc(Many2one)",
|
||||
"should display the correct content of tracked field of type many2one: from no related record to having a related record (None -> Marc (Many2one))"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,24 +1,32 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_controller_attachment
|
||||
from . import test_controller_binary
|
||||
from . import test_controller_thread
|
||||
from . import test_invite
|
||||
from . import test_ir_actions
|
||||
from . import test_ir_attachment
|
||||
from . import test_mail_activity
|
||||
from . import test_mail_activity_mixin
|
||||
from . import test_mail_activity_plan
|
||||
from . import test_mail_alias
|
||||
from . import test_mail_composer
|
||||
from . import test_mail_composer_mixin
|
||||
from . import test_mail_followers
|
||||
from . import test_mail_gateway
|
||||
from . import test_mail_flow
|
||||
from . import test_mail_mail
|
||||
from . import test_mail_management
|
||||
from . import test_mail_message
|
||||
from . import test_mail_message_security
|
||||
from . import test_mail_mail
|
||||
from . import test_mail_gateway
|
||||
from . import test_mail_multicompany
|
||||
from . import test_mail_push
|
||||
from . import test_mail_scheduled_message
|
||||
from . import test_mail_security
|
||||
from . import test_mail_thread_internals
|
||||
from . import test_mail_thread_mixins
|
||||
from . import test_mail_template
|
||||
from . import test_mail_template_preview
|
||||
from . import test_message_management
|
||||
from . import test_message_post
|
||||
from . import test_message_track
|
||||
from . import test_performance
|
||||
from . import test_ui
|
||||
from . import test_mail_management
|
||||
from . import test_mail_security
|
||||
|
|
|
|||
|
|
@ -1,15 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestMailCommon(MailCommon):
|
||||
""" Main entry point for functional tests. Kept to ease backward
|
||||
compatibility. """
|
||||
|
||||
|
||||
class TestRecipients(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
|
|
@ -25,13 +19,11 @@ class TestRecipients(TransactionCase):
|
|||
'name': 'Valid Lelitre',
|
||||
'email': 'valid.lelitre@agrolait.com',
|
||||
'country_id': cls.env.ref('base.be').id,
|
||||
'mobile': '0456001122',
|
||||
'phone': False,
|
||||
'phone': '0456001122',
|
||||
})
|
||||
cls.partner_2 = Partner.create({
|
||||
'name': 'Valid Poilvache',
|
||||
'email': 'valid.other@gmail.com',
|
||||
'country_id': cls.env.ref('base.be').id,
|
||||
'mobile': '+32 456 22 11 00',
|
||||
'phone': False,
|
||||
'phone': '+32 456 22 11 00',
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import odoo
|
||||
from odoo.addons.mail.tests.common_controllers import MailControllerAttachmentCommon
|
||||
|
||||
|
||||
@odoo.tests.tagged("-at_install", "post_install", "mail_controller")
|
||||
class TestAttachmentController(MailControllerAttachmentCommon):
|
||||
def test_independent_attachment_delete(self):
|
||||
"""Test access to delete an attachment whether or not limited `ownership_token` is sent"""
|
||||
self._execute_subtests_delete(self.all_users, token=True, allowed=True)
|
||||
self._execute_subtests_delete(self.user_admin, token=False, allowed=True)
|
||||
self._execute_subtests_delete(
|
||||
(self.guest, self.user_employee, self.user_portal, self.user_public),
|
||||
token=False,
|
||||
allowed=False,
|
||||
)
|
||||
|
||||
def test_attachment_delete_linked_to_thread(self):
|
||||
"""Test access to delete an attachment associated with a thread
|
||||
whether or not limited `ownership_token` is sent"""
|
||||
thread = self.env["mail.test.simple"].create({"name": "Test"})
|
||||
self._execute_subtests_delete(self.all_users, token=True, allowed=True, thread=thread)
|
||||
self._execute_subtests_delete(
|
||||
(self.user_admin, self.user_employee),
|
||||
token=False,
|
||||
allowed=True,
|
||||
thread=thread,
|
||||
)
|
||||
self._execute_subtests_delete(
|
||||
(self.guest, self.user_portal, self.user_public),
|
||||
token=False,
|
||||
allowed=False,
|
||||
thread=thread,
|
||||
)
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
from odoo.addons.mail.tests.common_controllers import MailControllerBinaryCommon
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged("-at_install", "post_install", "mail_controller")
|
||||
class TestPublicBinaryController(MailControllerBinaryCommon):
|
||||
|
||||
def test_avatar_no_public(self):
|
||||
"""Test access to open a guest / partner avatar who hasn't sent a message on a
|
||||
public record."""
|
||||
for source in (self.guest_2, self.user_employee_nopartner.partner_id):
|
||||
self._execute_subtests(
|
||||
source, (
|
||||
(self.user_public, False),
|
||||
(self.guest, False),
|
||||
(self.user_portal, False),
|
||||
(self.user_employee, True),
|
||||
)
|
||||
)
|
||||
|
||||
def test_avatar_private(self):
|
||||
"""Test access to open a partner avatar who has sent a message on a private record."""
|
||||
document = self.env["mail.test.simple.unfollow"].create({"name": "Test"})
|
||||
self._post_message(document, self.user_employee_nopartner)
|
||||
self._execute_subtests(
|
||||
self.user_employee_nopartner.partner_id, (
|
||||
(self.user_public, False),
|
||||
(self.guest, False),
|
||||
(self.user_portal, False),
|
||||
(self.user_employee, True),
|
||||
)
|
||||
)
|
||||
|
||||
def test_avatar_public(self):
|
||||
"""Test access to open a guest avatar who has sent a message on a public record."""
|
||||
document = self.env["mail.test.access.public"].create({"name": "Test"})
|
||||
for author, source in ((self.guest_2, self.guest_2), (self.user_employee_nopartner, self.user_employee_nopartner.partner_id)):
|
||||
self._post_message(document, author)
|
||||
self._execute_subtests(
|
||||
source,
|
||||
(
|
||||
(self.user_public, False),
|
||||
(self.guest, False),
|
||||
(self.user_portal, False),
|
||||
(self.user_employee, True),
|
||||
),
|
||||
)
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
import json
|
||||
|
||||
from odoo import http
|
||||
from odoo.addons.mail.tests.common_controllers import MailControllerThreadCommon
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged("-at_install", "post_install", "mail_controller")
|
||||
class TestMessageController(MailControllerThreadCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.test_public_record = cls.env["mail.test.access.public"].create({"name": "Public Channel", "email": "john@test.be", "mobile": "+32455001122"})
|
||||
|
||||
@mute_logger("odoo.http")
|
||||
def test_thread_attachment_hijack(self):
|
||||
att = self.env["ir.attachment"].create({
|
||||
"name": "arguments_for_firing_marc_demo",
|
||||
"res_id": 0,
|
||||
"res_model": "mail.compose.message",
|
||||
})
|
||||
self.authenticate(self.user_employee.login, self.user_employee.login)
|
||||
record = self.env["mail.test.access.public"].create({"name": "Public Channel"})
|
||||
record.with_user(self.user_employee).write({'name': 'updated'}) # can access, update, ...
|
||||
# if this test breaks, it might be due to a change in /web/content, or the default rules for accessing an attachment. This is not an issue but it makes this test irrelevant.
|
||||
self.assertFalse(self.url_open(f"/web/content/{att.id}").ok)
|
||||
response = self.url_open(
|
||||
url="/mail/message/post",
|
||||
headers={"Content-Type": "application/json"}, # route called as demo
|
||||
data=json.dumps(
|
||||
{
|
||||
"params": {
|
||||
"post_data": {
|
||||
"attachment_ids": [att.id], # demo does not have access to this attachment id
|
||||
"body": "",
|
||||
"message_type": "comment",
|
||||
"partner_ids": [],
|
||||
"subtype_xmlid": "mail.mt_comment",
|
||||
},
|
||||
"thread_id": record.id,
|
||||
"thread_model": record._name,
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
self.assertNotIn(
|
||||
"arguments_for_firing_marc_demo", response.text
|
||||
) # demo should not be able to see the name of the document
|
||||
|
||||
def test_thread_partner_from_email_authenticated(self):
|
||||
self.authenticate(self.user_employee.login, self.user_employee.login)
|
||||
res3 = self.url_open(
|
||||
url="/mail/partner/from_email",
|
||||
data=json.dumps(
|
||||
{
|
||||
"params": {
|
||||
"thread_model": self.test_public_record._name,
|
||||
"thread_id": self.test_public_record.id,
|
||||
"emails": ["john@test.be"],
|
||||
},
|
||||
}
|
||||
),
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
self.assertEqual(res3.status_code, 200)
|
||||
self.assertEqual(
|
||||
1,
|
||||
self.env["res.partner"].search_count([('email', '=', "john@test.be"), ('phone', '=', "+32455001122")]),
|
||||
"authenticated users can create a partner from an email",
|
||||
)
|
||||
# should not create another partner with same email
|
||||
res4 = self.url_open(
|
||||
url="/mail/partner/from_email",
|
||||
data=json.dumps(
|
||||
{
|
||||
"params": {
|
||||
"thread_model": self.test_public_record._name,
|
||||
"thread_id": self.test_public_record.id,
|
||||
"emails": ["john@test.be"],
|
||||
},
|
||||
}
|
||||
),
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
self.assertEqual(res4.status_code, 200)
|
||||
self.assertEqual(
|
||||
1,
|
||||
self.env["res.partner"].search_count([('email', '=', "john@test.be")]),
|
||||
"'mail/partner/from_email' does not create another user if there's already a user with matching email",
|
||||
)
|
||||
|
||||
self.test_public_record.write({'email': 'john2@test.be'})
|
||||
res5 = self.url_open(
|
||||
url="/mail/message/post",
|
||||
data=json.dumps(
|
||||
{
|
||||
"params": {
|
||||
"thread_model": self.test_public_record._name,
|
||||
"thread_id": self.test_public_record.id,
|
||||
"post_data": {
|
||||
"body": "test",
|
||||
"partner_emails": ["john2@test.be"],
|
||||
},
|
||||
},
|
||||
}
|
||||
),
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
self.assertEqual(res5.status_code, 200)
|
||||
self.assertEqual(
|
||||
1,
|
||||
self.env["res.partner"].search_count([('email', '=', "john2@test.be"), ('phone', '=', "+32455001122")]),
|
||||
"authenticated users can create a partner from an email from message_post",
|
||||
)
|
||||
# should not create another partner with same email
|
||||
res6 = self.url_open(
|
||||
url="/mail/message/post",
|
||||
data=json.dumps(
|
||||
{
|
||||
"params": {
|
||||
"thread_model": self.test_public_record._name,
|
||||
"thread_id": self.test_public_record.id,
|
||||
"post_data": {
|
||||
"body": "test",
|
||||
"partner_emails": ["john2@test.be"],
|
||||
},
|
||||
},
|
||||
}
|
||||
),
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
self.assertEqual(res6.status_code, 200)
|
||||
self.assertEqual(
|
||||
1,
|
||||
self.env["res.partner"].search_count([('email', '=', "john2@test.be")]),
|
||||
"'mail/message/post' does not create another user if there's already a user with matching email",
|
||||
)
|
||||
|
||||
def test_thread_post_archived_record(self):
|
||||
self.authenticate(self.user_employee.login, self.user_employee.login)
|
||||
archived_partner = self.env["res.partner"].create({"name": "partner", "active": False})
|
||||
|
||||
# 1. posting a message
|
||||
data = self.make_jsonrpc_request("/mail/message/post", {
|
||||
"thread_model": "res.partner",
|
||||
"thread_id": archived_partner.id,
|
||||
"post_data": {
|
||||
"body": "A great message",
|
||||
}
|
||||
})
|
||||
message = next(filter(lambda m: m["id"] == data["message_id"], data["store_data"]["mail.message"]))
|
||||
self.assertEqual(["markup", "<p>A great message</p>"], message["body"])
|
||||
|
||||
# 2. attach a file
|
||||
response = self.url_open(
|
||||
"/mail/attachment/upload",
|
||||
{
|
||||
"csrf_token": http.Request.csrf_token(self),
|
||||
"thread_id": archived_partner.id,
|
||||
"thread_model": "res.partner",
|
||||
},
|
||||
files={"ufile": b""},
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.test_mail.tests.common import TestMailCommon
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('mail_followers')
|
||||
class TestInvite(TestMailCommon):
|
||||
class TestInvite(MailCommon):
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_invite_email(self):
|
||||
|
|
@ -16,18 +16,38 @@ class TestInvite(TestMailCommon):
|
|||
'name': 'Valid Lelitre',
|
||||
'email': 'valid.lelitre@agrolait.com'})
|
||||
|
||||
mail_invite = self.env['mail.wizard.invite'].with_context({
|
||||
mail_invite = self.env['mail.followers.edit'].with_context({
|
||||
'default_res_model': 'mail.test.simple',
|
||||
'default_res_id': test_record.id
|
||||
}).with_user(self.user_employee).create({
|
||||
'partner_ids': [(4, test_partner.id), (4, self.user_admin.partner_id.id)],
|
||||
'send_mail': True})
|
||||
with self.mock_mail_gateway():
|
||||
mail_invite.add_followers()
|
||||
'default_res_ids': [test_record.id],
|
||||
}).with_user(self.user_employee).create({'partner_ids': [(4, test_partner.id), (4, self.user_admin.partner_id.id)],
|
||||
'notify': True})
|
||||
with self.mock_mail_app(), self.mock_mail_gateway():
|
||||
mail_invite.edit_followers()
|
||||
|
||||
# check added followers and that emails were sent
|
||||
# Check added followers and that notifications are sent.
|
||||
# Admin notification preference is inbox so the notification must be of inbox type
|
||||
# while partner_employee must receive it by email.
|
||||
self.assertEqual(test_record.message_partner_ids,
|
||||
test_partner | self.user_admin.partner_id)
|
||||
self.assertEqual(len(self._new_msgs), 1)
|
||||
self.assertEqual(len(self._mails), 1)
|
||||
self.assertSentEmail(self.partner_employee, [test_partner])
|
||||
self.assertSentEmail(self.partner_employee, [self.partner_admin])
|
||||
self.assertEqual(len(self._mails), 2)
|
||||
self.assertNotSentEmail([self.partner_admin])
|
||||
self.assertNotified(
|
||||
self._new_msgs[0],
|
||||
[{'partner': self.partner_admin, 'type': 'inbox', 'is_read': False}]
|
||||
)
|
||||
|
||||
# Remove followers
|
||||
mail_remove = self.env['mail.followers.edit'].with_context({
|
||||
'default_res_model': 'mail.test.simple',
|
||||
'default_res_ids': [test_record.id],
|
||||
}).with_user(self.user_employee).create({
|
||||
"operation": "remove",
|
||||
'partner_ids': [(4, test_partner.id), (4, self.user_admin.partner_id.id)]})
|
||||
|
||||
with self.mock_mail_app(), self.mock_mail_gateway():
|
||||
mail_remove.edit_followers()
|
||||
|
||||
# Check removed followers and that notifications are sent.
|
||||
self.assertEqual(test_record.message_partner_ids, self.env["res.partner"])
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.base.tests.test_ir_actions import TestServerActionsBase
|
||||
from odoo.addons.test_mail.tests.common import TestMailCommon
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('ir_actions')
|
||||
class TestServerActionsEmail(TestMailCommon, TestServerActionsBase):
|
||||
class TestServerActionsEmail(MailCommon, TestServerActionsBase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestServerActionsEmail, self).setUp()
|
||||
|
|
@ -61,6 +61,17 @@ class TestServerActionsEmail(TestMailCommon, TestServerActionsBase):
|
|||
self.action.with_context(self.context).run()
|
||||
self.assertEqual(self.test_partner.message_partner_ids, self.env.ref('base.partner_admin') | random_partner)
|
||||
|
||||
def test_action_followers_warning(self):
|
||||
self.test_partner.message_unsubscribe(self.test_partner.message_partner_ids.ids)
|
||||
self.action.write({
|
||||
'state': 'followers',
|
||||
"followers_type": "generic",
|
||||
"followers_partner_field_name": "user_id.name"
|
||||
})
|
||||
self.assertEqual(self.action.warning, "The field 'Salesperson > Name' is not a partner field.")
|
||||
self.action.write({"followers_partner_field_name": "parent_id.child_ids"})
|
||||
self.assertEqual(self.action.warning, False)
|
||||
|
||||
def test_action_message_post(self):
|
||||
# initial state
|
||||
self.assertEqual(len(self.test_partner.message_ids), 1,
|
||||
|
|
@ -78,7 +89,10 @@ class TestServerActionsEmail(TestMailCommon, TestServerActionsBase):
|
|||
with self.assertSinglePostNotifications(
|
||||
[{'partner': self.test_partner, 'type': 'email', 'status': 'ready'}],
|
||||
message_info={'content': 'Hello %s' % self.test_partner.name,
|
||||
'message_type': 'notification',
|
||||
'mail_mail_values': {
|
||||
'author_id': self.env.user.partner_id,
|
||||
},
|
||||
'message_type': 'auto_comment',
|
||||
'subtype': 'mail.mt_comment',
|
||||
}
|
||||
):
|
||||
|
|
@ -95,7 +109,7 @@ class TestServerActionsEmail(TestMailCommon, TestServerActionsBase):
|
|||
with self.assertSinglePostNotifications(
|
||||
[{'partner': self.test_partner, 'type': 'email', 'status': 'ready'}],
|
||||
message_info={'content': 'Hello %s' % self.test_partner.name,
|
||||
'message_type': 'notification',
|
||||
'message_type': 'auto_comment',
|
||||
'subtype': 'mail.mt_note',
|
||||
}
|
||||
):
|
||||
|
|
@ -117,6 +131,18 @@ class TestServerActionsEmail(TestMailCommon, TestServerActionsBase):
|
|||
self.assertEqual(self.env['mail.activity'].search_count([]), before_count + 1)
|
||||
self.assertEqual(self.env['mail.activity'].search_count([('summary', '=', 'TestNew')]), 1)
|
||||
|
||||
def test_action_next_activity_warning(self):
|
||||
self.action.write({
|
||||
'state': 'next_activity',
|
||||
'activity_user_type': 'generic',
|
||||
"activity_user_field_name": "user_id.name",
|
||||
'activity_type_id': self.env.ref('mail.mail_activity_data_meeting').id,
|
||||
'activity_summary': 'TestNew',
|
||||
})
|
||||
self.assertEqual(self.action.warning, "The field 'Salesperson > Name' is not a user field.")
|
||||
self.action.write({"activity_user_field_name": "parent_id.user_id"})
|
||||
self.assertEqual(self.action.warning, False)
|
||||
|
||||
def test_action_next_activity_due_date(self):
|
||||
""" Make sure we don't crash if a due date is set without a type. """
|
||||
self.action.write({
|
||||
|
|
@ -132,3 +158,66 @@ class TestServerActionsEmail(TestMailCommon, TestServerActionsBase):
|
|||
self.assertFalse(run_res, 'ir_actions_server: create next activity action correctly finished should return False')
|
||||
self.assertEqual(self.env['mail.activity'].search_count([]), before_count + 1)
|
||||
self.assertEqual(self.env['mail.activity'].search_count([('summary', '=', 'TestNew')]), 1)
|
||||
|
||||
def test_action_next_activity_from_x2m_user(self):
|
||||
self.test_partner.user_ids = self.user_demo | self.user_admin
|
||||
self.action.write({
|
||||
'state': 'next_activity',
|
||||
'activity_user_type': 'generic',
|
||||
'activity_user_field_name': 'user_ids',
|
||||
'activity_type_id': self.env.ref('mail.mail_activity_data_meeting').id,
|
||||
'activity_summary': 'TestNew',
|
||||
})
|
||||
before_count = self.env['mail.activity'].search_count([])
|
||||
run_res = self.action.with_context(self.context).run()
|
||||
self.assertFalse(run_res, 'ir_actions_server: create next activity action correctly finished should return False')
|
||||
self.assertEqual(self.env['mail.activity'].search_count([]), before_count + 1)
|
||||
self.assertRecordValues(
|
||||
self.env['mail.activity'].search([('res_model', '=', 'res.partner'), ('res_id', '=', self.test_partner.id)]),
|
||||
[{
|
||||
'summary': 'TestNew',
|
||||
'user_id': self.user_demo.id, # the first user found
|
||||
}],
|
||||
)
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink')
|
||||
def test_action_send_mail_without_mail_thread(self):
|
||||
""" Check running a server action to send an email with custom layout on a non mail.thread model """
|
||||
no_thread_record = self.env['mail.test.nothread'].create({'name': 'Test NoMailThread', 'customer_id': self.test_partner.id})
|
||||
no_thread_template = self._create_template(
|
||||
'mail.test.nothread',
|
||||
{
|
||||
'email_from': 'someone@example.com',
|
||||
'partner_to': '{{ object.customer_id.id }}',
|
||||
'subject': 'About {{ object.name }}',
|
||||
'body_html': '<p>Hello <t t-out="object.name"/></p>',
|
||||
'email_layout_xmlid': 'mail.mail_notification_layout',
|
||||
}
|
||||
)
|
||||
|
||||
# update action: send an email
|
||||
self.action.write({
|
||||
'mail_post_method': 'email',
|
||||
'state': 'mail_post',
|
||||
'model_id': self.env['ir.model'].search([('model', '=', 'mail.test.nothread')], limit=1).id,
|
||||
'model_name': 'mail.test.nothread',
|
||||
'template_id': no_thread_template.id,
|
||||
})
|
||||
|
||||
with self.mock_mail_gateway(), self.mock_mail_app():
|
||||
action_ctx = {
|
||||
'active_model': 'mail.test.nothread',
|
||||
'active_id': no_thread_record.id,
|
||||
}
|
||||
self.action.with_context(action_ctx).run()
|
||||
|
||||
mail = self.assertMailMail(
|
||||
self.test_partner,
|
||||
None,
|
||||
content='Hello Test NoMailThread',
|
||||
fields_values={
|
||||
'email_from': 'someone@example.com',
|
||||
'subject': 'About Test NoMailThread',
|
||||
}
|
||||
)
|
||||
self.assertNotIn('Powered by', mail.body_html, 'Body should contain the notification layout')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
import base64
|
||||
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.tests import tagged, users
|
||||
|
||||
|
||||
@tagged("ir_attachment")
|
||||
class TestAttachment(MailCommon):
|
||||
|
||||
@users("employee")
|
||||
def test_register_as_main_attachment(self):
|
||||
""" Test 'register_as_main_attachment', especially the multi support """
|
||||
records_model1 = self.env["mail.test.simple.main.attachment"].create([
|
||||
{
|
||||
"name": f"First model {idx}",
|
||||
}
|
||||
for idx in range(5)
|
||||
])
|
||||
records_model2 = self.env["mail.test.gateway.main.attachment"].create([
|
||||
{
|
||||
"name": f"Second model {idx}",
|
||||
}
|
||||
for idx in range(5)
|
||||
])
|
||||
record_nomain = self.env["mail.test.simple"].create({"name": "No Main Attachment"})
|
||||
attachments = self.env["ir.attachment"].create([
|
||||
{
|
||||
"datas": base64.b64encode(b'AttContent'),
|
||||
"name": f"AttachName_{record.name}.pdf",
|
||||
"mimetype": "application/pdf",
|
||||
"res_id": record.id,
|
||||
"res_model": record._name,
|
||||
}
|
||||
for record in records_model1
|
||||
] + [
|
||||
{
|
||||
"datas": base64.b64encode(b'AttContent'),
|
||||
"name": f"AttachName_{record.name}.pdf",
|
||||
"mimetype": "application/pdf",
|
||||
"res_id": record.id,
|
||||
"res_model": record._name,
|
||||
}
|
||||
for record in records_model2
|
||||
] + [
|
||||
{
|
||||
"datas": base64.b64encode(b'AttContent'),
|
||||
"name": "AttachName_free.pdf",
|
||||
"mimetype": "application/pdf",
|
||||
}, {
|
||||
"datas": base64.b64encode(b'AttContent'),
|
||||
"name": f"AttachName_{record_nomain.name}.pdf",
|
||||
"mimetype": "application/pdf",
|
||||
"res_id": record_nomain.id,
|
||||
"res_model": record_nomain._name,
|
||||
}
|
||||
])
|
||||
attachments.register_as_main_attachment()
|
||||
for record, attachment in zip(records_model1, attachments[:5]):
|
||||
self.assertEqual(record.message_main_attachment_id, attachment)
|
||||
for record, attachment in zip(records_model2, attachments[5:10]):
|
||||
self.assertEqual(record.message_main_attachment_id, attachment)
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,730 @@
|
|||
from datetime import date, datetime, timedelta, timezone
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from freezegun import freeze_time
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytz
|
||||
import random
|
||||
|
||||
from odoo import fields, tests
|
||||
from odoo.addons.mail.models.mail_activity import MailActivity
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.test_mail.tests.test_mail_activity import TestActivityCommon
|
||||
from odoo.tests import tagged, users
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('mail_activity', 'mail_activity_mixin')
|
||||
class TestActivityMixin(TestActivityCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.user_utc = mail_new_test_user(
|
||||
cls.env,
|
||||
name='User UTC',
|
||||
login='User UTC',
|
||||
)
|
||||
cls.user_utc.tz = 'UTC'
|
||||
|
||||
cls.user_australia = mail_new_test_user(
|
||||
cls.env,
|
||||
name='user Australia',
|
||||
login='user Australia',
|
||||
)
|
||||
cls.user_australia.tz = 'Australia/Sydney'
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_activity_mixin(self):
|
||||
self.user_employee.tz = self.user_admin.tz
|
||||
with self.with_user('employee'):
|
||||
self.test_record = self.env['mail.test.activity'].browse(self.test_record.id)
|
||||
self.assertEqual(len(self.test_record.message_ids), 1)
|
||||
self.assertEqual(self.test_record.env.user, self.user_employee)
|
||||
|
||||
now_utc = datetime.now(pytz.UTC)
|
||||
now_user = now_utc.astimezone(pytz.timezone(self.env.user.tz or 'UTC'))
|
||||
today_user = now_user.date()
|
||||
|
||||
# Test various scheduling of activities
|
||||
act1 = self.test_record.activity_schedule(
|
||||
'test_mail.mail_act_test_todo',
|
||||
today_user + relativedelta(days=1),
|
||||
user_id=self.user_admin.id)
|
||||
self.assertEqual(act1.automated, True)
|
||||
|
||||
act_type = self.env.ref('test_mail.mail_act_test_todo')
|
||||
self.assertEqual(self.test_record.activity_summary, act_type.summary)
|
||||
self.assertEqual(self.test_record.activity_state, 'planned')
|
||||
self.assertEqual(self.test_record.activity_user_id, self.user_admin)
|
||||
|
||||
act2 = self.test_record.activity_schedule(
|
||||
'test_mail.mail_act_test_meeting',
|
||||
today_user + relativedelta(days=-1),
|
||||
user_id=self.user_employee.id,
|
||||
)
|
||||
self.assertEqual(self.test_record.activity_state, 'overdue')
|
||||
# `activity_user_id` is defined as `fields.Many2one('res.users', 'Responsible User', related='activity_ids.user_id')`
|
||||
# it therefore relies on the natural order of `activity_ids`, according to which activity comes first.
|
||||
# As we just created the activity, its not yet in the right order.
|
||||
# We force it by invalidating it so it gets fetched from database, in the right order.
|
||||
self.test_record.invalidate_recordset(['activity_ids'])
|
||||
self.assertEqual(self.test_record.activity_user_id, self.user_employee)
|
||||
|
||||
act3 = self.test_record.activity_schedule(
|
||||
'test_mail.mail_act_test_todo',
|
||||
today_user + relativedelta(days=3),
|
||||
user_id=self.user_employee.id,
|
||||
)
|
||||
self.assertEqual(self.test_record.activity_state, 'overdue')
|
||||
# `activity_user_id` is defined as `fields.Many2one('res.users', 'Responsible User', related='activity_ids.user_id')`
|
||||
# it therefore relies on the natural order of `activity_ids`, according to which activity comes first.
|
||||
# As we just created the activity, its not yet in the right order.
|
||||
# We force it by invalidating it so it gets fetched from database, in the right order.
|
||||
self.test_record.invalidate_recordset(['activity_ids'])
|
||||
self.assertEqual(self.test_record.activity_user_id, self.user_employee)
|
||||
|
||||
self.test_record.invalidate_recordset()
|
||||
self.assertEqual(self.test_record.activity_ids, act1 | act2 | act3)
|
||||
|
||||
# Perform todo activities for admin
|
||||
self.test_record.activity_feedback(
|
||||
['test_mail.mail_act_test_todo'],
|
||||
user_id=self.user_admin.id,
|
||||
feedback='Test feedback 1',
|
||||
)
|
||||
self.assertEqual(self.test_record.activity_ids, act2 | act3)
|
||||
self.assertFalse(act1.active)
|
||||
|
||||
# Reschedule all activities, should update the record state
|
||||
self.assertEqual(self.test_record.activity_state, 'overdue')
|
||||
self.test_record.activity_reschedule(
|
||||
['test_mail.mail_act_test_meeting', 'test_mail.mail_act_test_todo'],
|
||||
date_deadline=today_user + relativedelta(days=3)
|
||||
)
|
||||
self.assertEqual(self.test_record.activity_state, 'planned')
|
||||
|
||||
# Perform todo activities for remaining people
|
||||
self.test_record.activity_feedback(
|
||||
['test_mail.mail_act_test_todo'],
|
||||
feedback='Test feedback 2')
|
||||
self.assertFalse(act3.active)
|
||||
|
||||
# Setting activities as done should delete them and post messages
|
||||
self.assertEqual(self.test_record.activity_ids, act2)
|
||||
self.assertEqual(len(self.test_record.message_ids), 3)
|
||||
self.assertEqual(len(self.test_record.message_ids), 3)
|
||||
feedback2, feedback1, _create_log = self.test_record.message_ids
|
||||
self.assertEqual((feedback2 + feedback1).subtype_id, self.env.ref('mail.mt_activities'))
|
||||
|
||||
# Unlink meeting activities
|
||||
self.test_record.activity_unlink(['test_mail.mail_act_test_meeting'])
|
||||
|
||||
# Canceling activities should simply remove them
|
||||
self.assertEqual(self.test_record.activity_ids, self.env['mail.activity'])
|
||||
self.assertEqual(len(self.test_record.message_ids), 3, 'Should not produce additional message')
|
||||
self.assertFalse(self.test_record.activity_state)
|
||||
self.assertFalse(act2.exists())
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_activity_mixin_not_only_automated(self):
|
||||
|
||||
# Schedule activity and create manual activity
|
||||
act_type_todo = self.env.ref('test_mail.mail_act_test_todo')
|
||||
auto_act = self.test_record.activity_schedule(
|
||||
'test_mail.mail_act_test_todo',
|
||||
date_deadline=date.today() + relativedelta(days=1),
|
||||
)
|
||||
man_act = self.env['mail.activity'].create({
|
||||
'activity_type_id': act_type_todo.id,
|
||||
'res_id': self.test_record.id,
|
||||
'res_model_id': self.env['ir.model']._get_id(self.test_record._name),
|
||||
'date_deadline': date.today() + relativedelta(days=1)
|
||||
})
|
||||
self.assertEqual(auto_act.automated, True)
|
||||
self.assertEqual(man_act.automated, False)
|
||||
|
||||
# Test activity reschedule on not only automated activities
|
||||
self.test_record.activity_reschedule(
|
||||
['test_mail.mail_act_test_todo'],
|
||||
date_deadline=date.today() + relativedelta(days=2),
|
||||
only_automated=False
|
||||
)
|
||||
self.assertEqual(auto_act.date_deadline, date.today() + relativedelta(days=2))
|
||||
self.assertEqual(man_act.date_deadline, date.today() + relativedelta(days=2))
|
||||
|
||||
# Test activity feedback on not only automated activities
|
||||
self.test_record.activity_feedback(
|
||||
['test_mail.mail_act_test_todo'],
|
||||
feedback='Test feedback',
|
||||
only_automated=False
|
||||
)
|
||||
self.assertEqual(self.test_record.activity_ids, self.env['mail.activity'])
|
||||
self.assertFalse(auto_act.active)
|
||||
self.assertFalse(man_act.active)
|
||||
|
||||
# Test activity unlink on not only automated activities
|
||||
auto_act = self.test_record.activity_schedule(
|
||||
'test_mail.mail_act_test_todo',
|
||||
)
|
||||
man_act = self.env['mail.activity'].create({
|
||||
'activity_type_id': act_type_todo.id,
|
||||
'res_id': self.test_record.id,
|
||||
'res_model_id': self.env['ir.model']._get_id(self.test_record._name)
|
||||
})
|
||||
self.test_record.activity_unlink(['test_mail.mail_act_test_todo'], only_automated=False)
|
||||
self.assertEqual(self.test_record.activity_ids, self.env['mail.activity'])
|
||||
self.assertFalse(auto_act.exists())
|
||||
self.assertFalse(man_act.exists())
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_activity_mixin_archive(self):
|
||||
rec = self.test_record.with_user(self.user_employee)
|
||||
new_act = rec.activity_schedule(
|
||||
'test_mail.mail_act_test_todo',
|
||||
user_id=self.user_admin.id,
|
||||
)
|
||||
self.assertEqual(rec.activity_ids, new_act)
|
||||
rec.action_archive()
|
||||
self.assertEqual(rec.active, False)
|
||||
self.assertEqual(rec.activity_ids, new_act)
|
||||
rec.action_unarchive()
|
||||
self.assertEqual(rec.active, True)
|
||||
self.assertEqual(rec.activity_ids, new_act)
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_activity_mixin_archive_user(self):
|
||||
"""
|
||||
Test when archiving an user, we unlink all his related activities
|
||||
"""
|
||||
test_users = self.env['res.users']
|
||||
for i in range(5):
|
||||
test_users += mail_new_test_user(self.env, name=f'test_user_{i}', login=f'test_password_{i}')
|
||||
for user in test_users:
|
||||
self.test_record.activity_schedule(user_id=user.id)
|
||||
archived_users = self.env['res.users'].browse(x.id for x in random.sample(test_users, 2)) # pick 2 users to archive
|
||||
archived_users.action_archive()
|
||||
active_users = test_users - archived_users
|
||||
|
||||
# archive user with company disabled
|
||||
user_admin = self.user_admin
|
||||
user_employee_c2 = self.user_employee_c2
|
||||
self.assertIn(self.company_2, user_admin.company_ids)
|
||||
self.test_record.env['ir.rule'].create({
|
||||
'model_id': self.env.ref('test_mail.model_mail_test_activity').id,
|
||||
'domain_force': "[('company_id', 'in', company_ids)]"
|
||||
})
|
||||
self.test_record.activity_schedule(user_id=user_employee_c2.id)
|
||||
user_employee_c2.with_user(user_admin).with_context(
|
||||
allowed_company_ids=(user_admin.company_ids - self.company_2).ids
|
||||
).action_archive()
|
||||
archived_users += user_employee_c2
|
||||
|
||||
self.assertFalse(any(archived_users.mapped('active')), "Users should be archived.")
|
||||
|
||||
# activities of active users shouldn't be touched, each has exactly 1 activity present
|
||||
activities = self.env['mail.activity'].search([('user_id', 'in', active_users.ids)])
|
||||
self.assertEqual(len(activities), 3, "We should have only 3 activities in total linked to our active users")
|
||||
self.assertEqual(activities.mapped('user_id'), active_users,
|
||||
"We should have 3 different users linked to the activities of the active users")
|
||||
|
||||
# ensure the user's activities are removed
|
||||
activities = self.env['mail.activity'].search([('user_id', 'in', archived_users.ids)])
|
||||
self.assertFalse(activities, "Activities of archived users should be deleted.")
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_activity_mixin_reschedule_user(self):
|
||||
rec = self.test_record.with_user(self.user_employee)
|
||||
rec.activity_schedule(
|
||||
'test_mail.mail_act_test_todo',
|
||||
user_id=self.user_admin.id)
|
||||
self.assertEqual(rec.activity_ids[0].user_id, self.user_admin)
|
||||
|
||||
# reschedule its own should not alter other's activities
|
||||
rec.activity_reschedule(
|
||||
['test_mail.mail_act_test_todo'],
|
||||
user_id=self.user_employee.id,
|
||||
new_user_id=self.user_employee.id)
|
||||
self.assertEqual(rec.activity_ids[0].user_id, self.user_admin)
|
||||
|
||||
rec.activity_reschedule(
|
||||
['test_mail.mail_act_test_todo'],
|
||||
user_id=self.user_admin.id,
|
||||
new_user_id=self.user_employee.id)
|
||||
self.assertEqual(rec.activity_ids[0].user_id, self.user_employee)
|
||||
|
||||
@users('employee')
|
||||
def test_feedback_w_attachments(self):
|
||||
test_record = self.env['mail.test.activity'].browse(self.test_record.ids)
|
||||
|
||||
activity = self.env['mail.activity'].create({
|
||||
'activity_type_id': 1,
|
||||
'res_id': test_record.id,
|
||||
'res_model_id': self.env['ir.model']._get_id('mail.test.activity'),
|
||||
'summary': 'Test',
|
||||
})
|
||||
attachments = self.env['ir.attachment'].create([{
|
||||
'name': 'test',
|
||||
'res_name': 'test',
|
||||
'res_model': 'mail.activity',
|
||||
'res_id': activity.id,
|
||||
'datas': 'test',
|
||||
}, {
|
||||
'name': 'test2',
|
||||
'res_name': 'test',
|
||||
'res_model': 'mail.activity',
|
||||
'res_id': activity.id,
|
||||
'datas': 'testtest',
|
||||
}])
|
||||
|
||||
# Checking if the attachment has been forwarded to the message
|
||||
# when marking an activity as "Done"
|
||||
activity.action_feedback()
|
||||
activity_message = test_record.message_ids[0]
|
||||
self.assertEqual(set(activity_message.attachment_ids.ids), set(attachments.ids))
|
||||
for attachment in attachments:
|
||||
self.assertEqual(attachment.res_id, activity_message.id)
|
||||
self.assertEqual(attachment.res_model, activity_message._name)
|
||||
|
||||
@users('employee')
|
||||
def test_feedback_chained_current_date(self):
|
||||
frozen_now = datetime(2021, 10, 10, 14, 30, 15)
|
||||
|
||||
test_record = self.env['mail.test.activity'].browse(self.test_record.ids)
|
||||
first_activity = self.env['mail.activity'].create({
|
||||
'activity_type_id': self.env.ref('test_mail.mail_act_test_chained_1').id,
|
||||
'date_deadline': frozen_now + relativedelta(days=-2),
|
||||
'res_id': test_record.id,
|
||||
'res_model_id': self.env['ir.model']._get_id('mail.test.activity'),
|
||||
'summary': 'Test',
|
||||
})
|
||||
first_activity_id = first_activity.id
|
||||
|
||||
with freeze_time(frozen_now):
|
||||
first_activity.action_feedback(feedback='Done')
|
||||
self.assertFalse(first_activity.active)
|
||||
|
||||
# check chained activity
|
||||
new_activity = test_record.activity_ids
|
||||
self.assertNotEqual(new_activity.id, first_activity_id)
|
||||
self.assertEqual(new_activity.summary, 'Take the second step.')
|
||||
self.assertEqual(new_activity.date_deadline, frozen_now.date() + relativedelta(days=10))
|
||||
|
||||
@users('employee')
|
||||
def test_feedback_chained_previous(self):
|
||||
self.env.ref('test_mail.mail_act_test_chained_2').sudo().write({'delay_from': 'previous_activity'})
|
||||
frozen_now = datetime(2021, 10, 10, 14, 30, 15)
|
||||
|
||||
test_record = self.env['mail.test.activity'].browse(self.test_record.ids)
|
||||
first_activity = self.env['mail.activity'].create({
|
||||
'activity_type_id': self.env.ref('test_mail.mail_act_test_chained_1').id,
|
||||
'date_deadline': frozen_now + relativedelta(days=-2),
|
||||
'res_id': test_record.id,
|
||||
'res_model_id': self.env['ir.model']._get_id('mail.test.activity'),
|
||||
'summary': 'Test',
|
||||
})
|
||||
first_activity_id = first_activity.id
|
||||
|
||||
with freeze_time(frozen_now):
|
||||
first_activity.action_feedback(feedback='Done')
|
||||
self.assertFalse(first_activity.active)
|
||||
|
||||
# check chained activity
|
||||
new_activity = test_record.activity_ids
|
||||
self.assertNotEqual(new_activity.id, first_activity_id)
|
||||
self.assertEqual(new_activity.summary, 'Take the second step.')
|
||||
self.assertEqual(new_activity.date_deadline, frozen_now.date() + relativedelta(days=8),
|
||||
'New deadline should take into account original activity deadline, not current date')
|
||||
|
||||
def test_mail_activity_state(self):
|
||||
"""Create 3 activity for 2 different users in 2 different timezones.
|
||||
|
||||
User UTC (+0h)
|
||||
User Australia (+11h)
|
||||
Today datetime: 1/1/2020 16h
|
||||
|
||||
Activity 1 & User UTC
|
||||
1/1/2020 - 16h UTC -> The state is today
|
||||
|
||||
Activity 2 & User Australia
|
||||
1/1/2020 - 16h UTC
|
||||
2/1/2020 - 1h Australia -> State is overdue
|
||||
|
||||
Activity 3 & User UTC
|
||||
1/1/2020 - 23h UTC -> The state is today
|
||||
"""
|
||||
record = self.env['mail.test.activity'].create({'name': 'Record'})
|
||||
|
||||
with freeze_time(datetime(2020, 1, 1, 16)):
|
||||
today_utc = datetime.today()
|
||||
activity_1 = self.env['mail.activity'].create({
|
||||
'summary': 'Test',
|
||||
'activity_type_id': 1,
|
||||
'res_model_id': self.env.ref('test_mail.model_mail_test_activity').id,
|
||||
'res_id': record.id,
|
||||
'date_deadline': today_utc,
|
||||
'user_id': self.user_utc.id,
|
||||
})
|
||||
|
||||
activity_2 = activity_1.copy()
|
||||
activity_2.user_id = self.user_australia
|
||||
activity_3 = activity_1.copy()
|
||||
activity_3.date_deadline += relativedelta(hours=7)
|
||||
|
||||
self.assertEqual(activity_1.state, 'today')
|
||||
self.assertEqual(activity_2.state, 'overdue')
|
||||
self.assertEqual(activity_3.state, 'today')
|
||||
|
||||
@users('employee')
|
||||
def test_mail_activity_mixin_search_activity_user_id_false(self):
|
||||
"""Test the search method on the "activity_user_id" when searching for non-set user"""
|
||||
MailTestActivity = self.env['mail.test.activity']
|
||||
test_records = self.test_record | self.test_record_2
|
||||
self.assertFalse(test_records.activity_ids)
|
||||
self.assertEqual(MailTestActivity.search([('activity_user_id', '=', False)]), test_records)
|
||||
|
||||
self.env['mail.activity'].create({
|
||||
'summary': 'Test',
|
||||
'activity_type_id': self.env.ref('test_mail.mail_act_test_todo').id,
|
||||
'res_model_id': self.env.ref('test_mail.model_mail_test_activity').id,
|
||||
'res_id': self.test_record.id,
|
||||
})
|
||||
self.assertEqual(MailTestActivity.search([('activity_user_id', '!=', True)]), self.test_record_2)
|
||||
|
||||
def test_mail_activity_mixin_search_exception_decoration(self):
|
||||
"""Test the search on "activity_exception_decoration".
|
||||
|
||||
Domain ('activity_exception_decoration', '!=', False) should only return
|
||||
records that have at least one warning/danger activity.
|
||||
"""
|
||||
record_warning, record_normal, _ = self.test_record, self.test_record_2, self.env['mail.test.activity'].create({'name': 'No activities'})
|
||||
record_warning.activity_schedule('mail.mail_activity_data_warning', user_id=self.env.user.id)
|
||||
record_normal.activity_schedule('test_mail.mail_act_test_todo', user_id=self.env.user.id)
|
||||
|
||||
records = self.env['mail.test.activity'].search([('activity_exception_decoration', '!=', False)])
|
||||
self.assertEqual(records, record_warning)
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.tests')
|
||||
def test_mail_activity_mixin_search_state_basic(self):
|
||||
"""Test the search method on the "activity_state".
|
||||
|
||||
Test all the operators and also test the case where the "activity_state" is
|
||||
different because of the timezone. There's also a tricky case for which we
|
||||
"reverse" the domain for performance purpose.
|
||||
"""
|
||||
|
||||
# Create some records without activity schedule on it for testing
|
||||
self.env['mail.test.activity'].create([
|
||||
{'name': 'Record %i' % record_i}
|
||||
for record_i in range(5)
|
||||
])
|
||||
|
||||
origin_1, origin_2 = self.env['mail.test.activity'].search([], limit=2)
|
||||
activity_type = self.env.ref('test_mail.mail_act_test_todo')
|
||||
|
||||
with freeze_time(datetime(2020, 1, 1, 16)):
|
||||
today_utc = datetime.today()
|
||||
origin_1_activity_1 = self.env['mail.activity'].create({
|
||||
'summary': 'Test',
|
||||
'activity_type_id': activity_type.id,
|
||||
'res_model_id': self.env.ref('test_mail.model_mail_test_activity').id,
|
||||
'res_id': origin_1.id,
|
||||
'date_deadline': today_utc,
|
||||
'user_id': self.user_utc.id,
|
||||
})
|
||||
|
||||
origin_1_activity_2 = origin_1_activity_1.copy()
|
||||
origin_1_activity_2.user_id = self.user_australia
|
||||
origin_1_activity_3 = origin_1_activity_1.copy()
|
||||
origin_1_activity_3.date_deadline += relativedelta(hours=8)
|
||||
|
||||
self.assertEqual(origin_1_activity_1.state, 'today')
|
||||
self.assertEqual(origin_1_activity_2.state, 'overdue')
|
||||
self.assertEqual(origin_1_activity_3.state, 'today')
|
||||
|
||||
origin_2_activity_1 = self.env['mail.activity'].create({
|
||||
'summary': 'Test',
|
||||
'activity_type_id': activity_type.id,
|
||||
'res_model_id': self.env.ref('test_mail.model_mail_test_activity').id,
|
||||
'res_id': origin_2.id,
|
||||
'date_deadline': today_utc + relativedelta(hours=8),
|
||||
'user_id': self.user_utc.id,
|
||||
})
|
||||
|
||||
origin_2_activity_2 = origin_2_activity_1.copy()
|
||||
origin_2_activity_2.user_id = self.user_australia
|
||||
origin_2_activity_3 = origin_2_activity_1.copy()
|
||||
origin_2_activity_3.date_deadline -= relativedelta(hours=8)
|
||||
origin_2_activity_4 = origin_2_activity_1.copy()
|
||||
origin_2_activity_4.date_deadline = datetime(2020, 1, 2, 0, 0, 0)
|
||||
|
||||
self.assertEqual(origin_2_activity_1.state, 'planned')
|
||||
self.assertEqual(origin_2_activity_2.state, 'today')
|
||||
self.assertEqual(origin_2_activity_3.state, 'today')
|
||||
self.assertEqual(origin_2_activity_4.state, 'planned')
|
||||
|
||||
all_activity_mixin_record = self.env['mail.test.activity'].search([])
|
||||
|
||||
result = self.env['mail.test.activity'].search([('activity_state', '=', 'today')])
|
||||
self.assertTrue(len(result) > 0)
|
||||
self.assertEqual(result, all_activity_mixin_record.filtered(lambda p: p.activity_state == 'today'))
|
||||
|
||||
result = self.env['mail.test.activity'].search([('activity_state', 'in', ('today', 'overdue'))])
|
||||
self.assertTrue(len(result) > 0)
|
||||
self.assertEqual(result, all_activity_mixin_record.filtered(lambda p: p.activity_state in ('today', 'overdue')))
|
||||
|
||||
result = self.env['mail.test.activity'].search([('activity_state', 'not in', ('today'))])
|
||||
self.assertTrue(len(result) > 0)
|
||||
self.assertEqual(result, all_activity_mixin_record.filtered(lambda p: p.activity_state != 'today'))
|
||||
|
||||
result = self.env['mail.test.activity'].search([('activity_state', '=', False)])
|
||||
self.assertTrue(len(result) >= 3, "There is more than 3 records without an activity schedule on it")
|
||||
self.assertEqual(result, all_activity_mixin_record.filtered(lambda p: not p.activity_state))
|
||||
|
||||
result = self.env['mail.test.activity'].search([('activity_state', 'not in', ('planned', 'overdue', 'today'))])
|
||||
self.assertTrue(len(result) >= 3, "There is more than 3 records without an activity schedule on it")
|
||||
self.assertEqual(result, all_activity_mixin_record.filtered(lambda p: not p.activity_state))
|
||||
|
||||
# test tricky case when the domain will be reversed in the search method
|
||||
# because of falsy value
|
||||
result = self.env['mail.test.activity'].search([('activity_state', 'not in', ('today', False))])
|
||||
self.assertTrue(len(result) > 0)
|
||||
self.assertEqual(result, all_activity_mixin_record.filtered(lambda p: p.activity_state not in ('today', False)))
|
||||
|
||||
result = self.env['mail.test.activity'].search([('activity_state', 'in', ('today', False))])
|
||||
self.assertTrue(len(result) > 0)
|
||||
self.assertEqual(result, all_activity_mixin_record.filtered(lambda p: p.activity_state in ('today', False)))
|
||||
|
||||
# Check that activity done are not taken into account by group and search by activity_state.
|
||||
Model = self.env['mail.test.activity']
|
||||
search_params = {
|
||||
'domain': [('id', 'in', (origin_1 | origin_2).ids), ('activity_state', '=', 'overdue')]}
|
||||
read_group_params = {
|
||||
'domain': [('id', 'in', (origin_1 | origin_2).ids)],
|
||||
'groupby': ['activity_state'],
|
||||
'aggregates': ['__count'],
|
||||
}
|
||||
self.assertEqual(Model.search(**search_params), origin_1)
|
||||
self.assertEqual(
|
||||
{(e['activity_state'], e['__count']) for e in Model.formatted_read_group(**read_group_params)},
|
||||
{('today', 1), ('overdue', 1)})
|
||||
origin_1_activity_2.action_feedback(feedback='Done')
|
||||
self.assertFalse(Model.search(**search_params))
|
||||
self.assertEqual(
|
||||
{(e['activity_state'], e['__count']) for e in Model.formatted_read_group(**read_group_params)},
|
||||
{('today', 2)})
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.tests')
|
||||
def test_mail_activity_mixin_search_state_different_day_but_close_time(self):
|
||||
"""Test the case where there's less than 24 hours between the deadline and now_tz,
|
||||
but one day of difference (e.g. 23h 01/01/2020 & 1h 02/02/2020). So the state
|
||||
should be "planned" and not "today". This case was tricky to implement in SQL
|
||||
that's why it has its own test.
|
||||
"""
|
||||
|
||||
# Create some records without activity schedule on it for testing
|
||||
self.env['mail.test.activity'].create([
|
||||
{'name': 'Record %i' % record_i}
|
||||
for record_i in range(5)
|
||||
])
|
||||
|
||||
origin_1 = self.env['mail.test.activity'].search([], limit=1)
|
||||
|
||||
with freeze_time(datetime(2020, 1, 1, 23)):
|
||||
today_utc = datetime.today()
|
||||
origin_1_activity_1 = self.env['mail.activity'].create({
|
||||
'summary': 'Test',
|
||||
'activity_type_id': 1,
|
||||
'res_model_id': self.env.ref('test_mail.model_mail_test_activity').id,
|
||||
'res_id': origin_1.id,
|
||||
'date_deadline': today_utc + relativedelta(hours=2),
|
||||
'user_id': self.user_utc.id,
|
||||
})
|
||||
|
||||
self.assertEqual(origin_1_activity_1.state, 'planned')
|
||||
result = self.env['mail.test.activity'].search([('activity_state', '=', 'today')])
|
||||
self.assertNotIn(origin_1, result, 'The activity state miss calculated during the search')
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_my_activity_flow_employee(self):
|
||||
Activity = self.env['mail.activity']
|
||||
date_today = date.today()
|
||||
Activity.create({
|
||||
'activity_type_id': self.env.ref('test_mail.mail_act_test_todo').id,
|
||||
'date_deadline': date_today,
|
||||
'res_model_id': self.env.ref('test_mail.model_mail_test_activity').id,
|
||||
'res_id': self.test_record.id,
|
||||
'user_id': self.user_admin.id,
|
||||
})
|
||||
Activity.create({
|
||||
'activity_type_id': self.env.ref('test_mail.mail_act_test_call').id,
|
||||
'date_deadline': date_today + relativedelta(days=1),
|
||||
'res_model_id': self.env.ref('test_mail.model_mail_test_activity').id,
|
||||
'res_id': self.test_record.id,
|
||||
'user_id': self.user_employee.id,
|
||||
})
|
||||
|
||||
test_record_1 = self.env['mail.test.activity'].with_context(self._test_context).create({'name': 'Test 1'})
|
||||
test_record_1_late_activity = Activity.create({
|
||||
'activity_type_id': self.env.ref('test_mail.mail_act_test_todo').id,
|
||||
'date_deadline': date_today,
|
||||
'res_model_id': self.env.ref('test_mail.model_mail_test_activity').id,
|
||||
'res_id': test_record_1.id,
|
||||
'user_id': self.user_employee.id,
|
||||
})
|
||||
with self.with_user('employee'):
|
||||
record = self.env['mail.test.activity'].search([('my_activity_date_deadline', '=', date_today)])
|
||||
self.assertEqual(test_record_1, record)
|
||||
test_record_1_late_activity._action_done()
|
||||
record = self.env['mail.test.activity'].with_context(active_test=False).search([
|
||||
('my_activity_date_deadline', '=', date_today)
|
||||
])
|
||||
self.assertFalse(record, "Should not find record if the only late activity is done")
|
||||
|
||||
@users('employee')
|
||||
def test_record_unlink(self):
|
||||
test_record = self.test_record.with_user(self.env.user)
|
||||
act1 = test_record.activity_schedule(summary='Active', user_id=self.env.uid)
|
||||
act2 = test_record.activity_schedule(summary='Archived', active=False, user_id=self.env.uid)
|
||||
test_record.unlink()
|
||||
self.assertFalse((act1 + act2).exists(), 'Removing records should remove activities, even archived')
|
||||
|
||||
@users('employee')
|
||||
def test_record_unlinked_orphan_activities(self):
|
||||
"""Test the fix preventing error on corrupted database where activities without related record are present."""
|
||||
test_record = self.env['mail.test.activity'].with_context(
|
||||
self._test_context).create({'name': 'Test'}).with_user(self.user_employee)
|
||||
act = test_record.activity_schedule("test_mail.mail_act_test_todo", summary='Orphan activity')
|
||||
act.action_done()
|
||||
# Delete the record while preventing the cascade deletion of the activity to simulate a corrupted database
|
||||
with patch.object(MailActivity, 'unlink', lambda self: None):
|
||||
test_record.unlink()
|
||||
self.assertTrue(act.exists())
|
||||
self.assertFalse(act.active)
|
||||
self.assertFalse(test_record.exists())
|
||||
|
||||
self.env.invalidate_all()
|
||||
self.assertEqual(
|
||||
self.env['mail.activity'].with_user(self.user_admin).with_context(active_test=False).search(
|
||||
[('active', '=', False)]), act,
|
||||
'Should consider unassigned activity on removed record = access without crash'
|
||||
)
|
||||
self.env.invalidate_all()
|
||||
_dummy = act.with_user(self.user_admin).read(['summary'])
|
||||
|
||||
|
||||
@tests.tagged('mail_activity', 'mail_activity_mixin')
|
||||
class TestORM(TestActivityCommon):
|
||||
"""Test for read_progress_bar"""
|
||||
|
||||
def test_groupby_activity_state_progress_bar_behavior(self):
|
||||
""" Test activity_state groupby logic on mail.test.lead when 'activity_state'
|
||||
is present multiple times in the groupby field list. """
|
||||
lead_timedelta_setup = [0, 0, -2, -2, -2, 2]
|
||||
|
||||
leads = self.env["mail.test.lead"].create([
|
||||
{"name": f"CRM Lead {i}"}
|
||||
for i in range(1, len(lead_timedelta_setup) + 1)
|
||||
])
|
||||
|
||||
with freeze_time("2025-05-21 10:00:00"):
|
||||
self.env["mail.activity"].create([
|
||||
{
|
||||
"date_deadline": datetime.now(timezone.utc) + timedelta(days=delta_days),
|
||||
"res_id": lead.id,
|
||||
"res_model_id": self.env["ir.model"]._get_id("mail.test.lead"),
|
||||
"summary": f"Test activity for CRM lead {lead.id}",
|
||||
"user_id": self.env.user.id,
|
||||
} for lead, delta_days in zip(leads, lead_timedelta_setup)
|
||||
])
|
||||
|
||||
# grouping by 'activity_state' and 'activity_state' as the progress bar
|
||||
domain = [("name", "!=", "")]
|
||||
groupby = "activity_state"
|
||||
progress_bar = {
|
||||
"field": "activity_state",
|
||||
"colors": {
|
||||
"overdue": "danger",
|
||||
"today": "warning",
|
||||
"planned": "success",
|
||||
},
|
||||
}
|
||||
progressbars = self.env["mail.test.lead"].read_progress_bar(
|
||||
domain=domain, group_by=groupby, progress_bar=progress_bar,
|
||||
)
|
||||
|
||||
self.assertEqual(len(progressbars), 3)
|
||||
expected_progressbars = {
|
||||
"overdue": {"overdue": 3, "today": 0, "planned": 0},
|
||||
"today": {"overdue": 0, "today": 2, "planned": 0},
|
||||
"planned": {"overdue": 0, "today": 0, "planned": 1},
|
||||
}
|
||||
self.assertEqual(dict(progressbars), expected_progressbars)
|
||||
|
||||
def test_week_grouping(self):
|
||||
"""The labels associated to each record in read_progress_bar should match
|
||||
the ones from read_group, even in edge cases like en_US locale on sundays
|
||||
"""
|
||||
MailTestActivityCtx = self.env['mail.test.activity'].with_context({"lang": "en_US"})
|
||||
|
||||
# Don't mistake fields date and date_deadline:
|
||||
# * date is just a random value
|
||||
# * date_deadline defines activity_state
|
||||
with freeze_time("2024-09-24 10:00:00"):
|
||||
self.env['mail.test.activity'].create({
|
||||
'date': '2021-05-02',
|
||||
'name': "Yesterday, all my troubles seemed so far away",
|
||||
}).activity_schedule(
|
||||
'test_mail.mail_act_test_todo',
|
||||
summary="Make another test super asap (yesterday)",
|
||||
date_deadline=fields.Date.context_today(MailTestActivityCtx) - timedelta(days=7),
|
||||
user_id=self.env.uid,
|
||||
)
|
||||
self.env['mail.test.activity'].create({
|
||||
'date': '2021-05-09',
|
||||
'name': "Things we said today",
|
||||
}).activity_schedule(
|
||||
'test_mail.mail_act_test_todo',
|
||||
summary="Make another test asap",
|
||||
date_deadline=fields.Date.context_today(MailTestActivityCtx),
|
||||
user_id=self.env.uid,
|
||||
)
|
||||
self.env['mail.test.activity'].create({
|
||||
'date': '2021-05-16',
|
||||
'name': "Tomorrow Never Knows",
|
||||
}).activity_schedule(
|
||||
'test_mail.mail_act_test_todo',
|
||||
summary="Make a test tomorrow",
|
||||
date_deadline=fields.Date.context_today(MailTestActivityCtx) + timedelta(days=7),
|
||||
user_id=self.env.uid,
|
||||
)
|
||||
|
||||
domain = [('date', "!=", False)]
|
||||
groupby = "date:week"
|
||||
progress_bar = {
|
||||
'field': 'activity_state',
|
||||
'colors': {
|
||||
"overdue": 'danger',
|
||||
"today": 'warning',
|
||||
"planned": 'success',
|
||||
}
|
||||
}
|
||||
|
||||
# call read_group to compute group names
|
||||
groups = MailTestActivityCtx.formatted_read_group(domain, groupby=[groupby])
|
||||
progressbars = MailTestActivityCtx.read_progress_bar(domain, group_by=groupby, progress_bar=progress_bar)
|
||||
self.assertEqual(len(groups), 3)
|
||||
self.assertEqual(len(progressbars), 3)
|
||||
|
||||
# format the read_progress_bar result to get a dictionary under this
|
||||
# format: {activity_state: group_name}; the original format
|
||||
# (after read_progress_bar) is {group_name: {activity_state: count}}
|
||||
pg_groups = {
|
||||
next(state for state, count in data.items() if count): group_name
|
||||
for group_name, data in progressbars.items()
|
||||
}
|
||||
|
||||
self.assertEqual(groups[0][groupby][0], pg_groups["overdue"])
|
||||
self.assertEqual(groups[1][groupby][0], pg_groups["today"])
|
||||
self.assertEqual(groups[2][groupby][0], pg_groups["planned"])
|
||||
|
|
@ -0,0 +1,410 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo import fields
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.mail.tests.common_activity import ActivityScheduleCase
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tests import Form, tagged, users
|
||||
from odoo.tools.misc import format_date
|
||||
|
||||
|
||||
@tagged('mail_activity', 'mail_activity_plan')
|
||||
class TestActivitySchedule(ActivityScheduleCase):
|
||||
""" Test plan and activity schedule
|
||||
|
||||
- activity scheduling on a single record and in batch
|
||||
- plan scheduling on a single record and in batch
|
||||
- plan creation and consistency
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
# add some triggered and suggested next activitities
|
||||
cls.test_type_1, cls.test_type_2, cls.test_type_3 = cls.env['mail.activity.type'].create([
|
||||
{'name': 'TestAct1', 'res_model': 'mail.test.activity',},
|
||||
{'name': 'TestAct2', 'res_model': 'mail.test.activity',},
|
||||
{'name': 'TestAct3', 'res_model': 'mail.test.activity',},
|
||||
])
|
||||
cls.test_type_1.write({
|
||||
'chaining_type': 'trigger',
|
||||
'delay_count': 2,
|
||||
'delay_from': 'current_date',
|
||||
'delay_unit': 'days',
|
||||
'triggered_next_type_id': cls.test_type_2.id,
|
||||
})
|
||||
cls.test_type_2.write({
|
||||
'chaining_type': 'suggest',
|
||||
'delay_count': 3,
|
||||
'delay_unit': 'weeks',
|
||||
'suggested_next_type_ids': [(4, cls.test_type_1.id), (4, cls.test_type_3.id)],
|
||||
})
|
||||
|
||||
# prepare plans
|
||||
cls.plan_party = cls.env['mail.activity.plan'].create({
|
||||
'name': 'Test Plan A Party',
|
||||
'res_model': 'mail.test.activity',
|
||||
'template_ids': [
|
||||
(0, 0, {
|
||||
'activity_type_id': cls.activity_type_todo.id,
|
||||
'delay_count': 1,
|
||||
'delay_from': 'before_plan_date',
|
||||
'delay_unit': 'days',
|
||||
'responsible_type': 'on_demand',
|
||||
'sequence': 10,
|
||||
'summary': 'Book a place',
|
||||
}), (0, 0, {
|
||||
'activity_type_id': cls.activity_type_todo.id,
|
||||
'delay_count': 1,
|
||||
'delay_from': 'after_plan_date',
|
||||
'delay_unit': 'weeks',
|
||||
'responsible_id': cls.user_admin.id,
|
||||
'responsible_type': 'other',
|
||||
'sequence': 20,
|
||||
'summary': 'Invite special guest',
|
||||
}),
|
||||
],
|
||||
})
|
||||
cls.plan_onboarding = cls.env['mail.activity.plan'].create({
|
||||
'name': 'Test Onboarding',
|
||||
'res_model': 'mail.test.activity',
|
||||
'template_ids': [
|
||||
(0, 0, {
|
||||
'activity_type_id': cls.activity_type_todo.id,
|
||||
'delay_count': 3,
|
||||
'delay_from': 'before_plan_date',
|
||||
'delay_unit': 'days',
|
||||
'responsible_id': cls.user_admin.id,
|
||||
'responsible_type': 'other',
|
||||
'sequence': 10,
|
||||
'summary': 'Plan training',
|
||||
}), (0, 0, {
|
||||
'activity_type_id': cls.activity_type_todo.id,
|
||||
'delay_count': 2,
|
||||
'delay_from': 'after_plan_date',
|
||||
'delay_unit': 'weeks',
|
||||
'responsible_id': cls.user_admin.id,
|
||||
'responsible_type': 'other',
|
||||
'sequence': 20,
|
||||
'summary': 'Training',
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
# test records
|
||||
cls.reference_now = fields.Datetime.from_string('2023-09-30 14:00:00')
|
||||
cls.test_records = cls.env['mail.test.activity'].create([
|
||||
{
|
||||
'date': cls.reference_now + timedelta(days=(idx - 10)),
|
||||
'email_from': f'customer.activity.{idx}@test.example.com',
|
||||
'name': f'test_record_{idx}'
|
||||
} for idx in range(5)
|
||||
])
|
||||
|
||||
# some big dict comparisons
|
||||
cls.maxDiff = None
|
||||
|
||||
@users('employee')
|
||||
def test_activity_schedule(self):
|
||||
""" Test schedule of an activity on a single or multiple records. """
|
||||
test_records_all = [self.test_records[0], self.test_records[:3]]
|
||||
# sanity check: new activity created without specifying activiy type
|
||||
# will have default type of the available activity type with the lowest sequence, then lowest id
|
||||
self.assertTrue(self.activity_type_todo.sequence < self.activity_type_call.sequence)
|
||||
for test_idx, test_case in enumerate(['mono', 'multi']):
|
||||
test_records = test_records_all[test_idx].with_env(self.env)
|
||||
with self.subTest(test_case=test_case, test_records=test_records):
|
||||
# 1. SCHEDULE ACTIVITIES
|
||||
with freeze_time(self.reference_now):
|
||||
form = self._instantiate_activity_schedule_wizard(test_records)
|
||||
form.summary = 'Write specification'
|
||||
form.note = '<p>Useful link ...</p>'
|
||||
form.activity_user_id = self.user_admin
|
||||
with self._mock_activities():
|
||||
form.save().action_schedule_activities()
|
||||
|
||||
for record in test_records:
|
||||
self.assertActivityCreatedOnRecord(record, {
|
||||
'activity_type_id': self.activity_type_todo,
|
||||
'automated': False,
|
||||
'date_deadline': self.reference_now.date() + timedelta(days=4), # activity type delay
|
||||
'note': '<p>Useful link ...</p>',
|
||||
'summary': 'Write specification',
|
||||
'user_id': self.user_admin,
|
||||
})
|
||||
|
||||
# 2. LOG DONE ACTIVITIES
|
||||
with freeze_time(self.reference_now):
|
||||
form = self._instantiate_activity_schedule_wizard(test_records)
|
||||
form.activity_type_id = self.activity_type_call
|
||||
form.activity_user_id = self.user_admin
|
||||
with self._mock_activities(), freeze_time(self.reference_now):
|
||||
form.save().with_context(
|
||||
mail_activity_quick_update=True
|
||||
).action_schedule_activities_done()
|
||||
|
||||
for record in test_records:
|
||||
self.assertActivityDoneOnRecord(record, self.activity_type_call)
|
||||
|
||||
# 3. CONTINUE WITH SCHEDULE ACTIVITIES
|
||||
# implies deadline addition on top of previous activities
|
||||
with freeze_time(self.reference_now):
|
||||
form = self._instantiate_activity_schedule_wizard(test_records)
|
||||
form.activity_type_id = self.activity_type_call
|
||||
form.activity_user_id = self.user_admin
|
||||
with self._mock_activities():
|
||||
form.save().with_context(
|
||||
mail_activity_quick_update=True
|
||||
).action_schedule_activities()
|
||||
|
||||
for record in test_records:
|
||||
self.assertActivityCreatedOnRecord(record, {
|
||||
'activity_type_id': self.activity_type_call,
|
||||
'automated': False,
|
||||
'date_deadline': self.reference_now.date() + timedelta(days=1), # activity call delay
|
||||
'note': False,
|
||||
'summary': 'TodoSumCallSummary',
|
||||
'user_id': self.user_admin,
|
||||
})
|
||||
|
||||
# global activity creation from tests
|
||||
self.assertEqual(len(self.test_records[0].activity_ids), 4)
|
||||
self.assertEqual(len(self.test_records[1].activity_ids), 2)
|
||||
self.assertEqual(len(self.test_records[2].activity_ids), 2)
|
||||
self.assertEqual(len(self.test_records[3].activity_ids), 0)
|
||||
self.assertEqual(len(self.test_records[4].activity_ids), 0)
|
||||
|
||||
@users('admin')
|
||||
def test_activity_schedule_rights_upload(self):
|
||||
user = mail_new_test_user(
|
||||
self.env,
|
||||
groups='base.group_public',
|
||||
login='bert',
|
||||
name='Bert Tartignole',
|
||||
)
|
||||
demo_record = self.env['mail.test.access'].create({'access': 'admin', 'name': 'Record'})
|
||||
form = self._instantiate_activity_schedule_wizard(demo_record)
|
||||
form.activity_type_id = self.env.ref('test_mail.mail_act_test_upload_document')
|
||||
with self.assertRaises(UserError):
|
||||
form.activity_user_id = user
|
||||
form.save()
|
||||
|
||||
@users('employee')
|
||||
def test_activity_schedule_norecord(self):
|
||||
""" Test scheduling free activities, supported if assigned user. """
|
||||
scheduler = self._instantiate_activity_schedule_wizard(None)
|
||||
self.assertEqual(scheduler.activity_type_id, self.activity_type_todo)
|
||||
with self._mock_activities():
|
||||
scheduler.save().action_schedule_activities()
|
||||
self.assertActivityValues(self._new_activities, {
|
||||
'res_id': False,
|
||||
'res_model': False,
|
||||
'summary': 'TodoSummary',
|
||||
'user_id': self.user_employee,
|
||||
})
|
||||
|
||||
# cannot scheduler unassigned personal activities
|
||||
scheduler = self._instantiate_activity_schedule_wizard(None)
|
||||
scheduler = scheduler.save()
|
||||
with self.assertRaises(ValidationError):
|
||||
scheduler.activity_user_id = False
|
||||
|
||||
def test_plan_copy(self):
|
||||
"""Test plan copy"""
|
||||
copied_plan = self.plan_onboarding.copy()
|
||||
self.assertEqual(copied_plan.name, f'{self.plan_onboarding.name} (copy)')
|
||||
self.assertEqual(len(copied_plan.template_ids), len(self.plan_onboarding.template_ids))
|
||||
|
||||
@users('employee')
|
||||
def test_plan_mode(self):
|
||||
""" Test the plan_mode that allows to preselect a compatible plan. """
|
||||
test_record = self.test_records[0].with_env(self.env)
|
||||
context = {
|
||||
'active_id': test_record.id,
|
||||
'active_ids': test_record.ids,
|
||||
'active_model': test_record._name
|
||||
}
|
||||
plan_mode_context = {**context, 'plan_mode': True}
|
||||
|
||||
with Form(self.env['mail.activity.schedule'].with_context(context)) as form:
|
||||
self.assertFalse(form.plan_id)
|
||||
with Form(self.env['mail.activity.schedule'].with_context(plan_mode_context)) as form:
|
||||
self.assertEqual(form.plan_id, self.plan_party)
|
||||
# should select only model-plans
|
||||
self.plan_party.res_model = 'res.partner'
|
||||
with Form(self.env['mail.activity.schedule'].with_context(plan_mode_context)) as form:
|
||||
self.assertEqual(form.plan_id, self.plan_onboarding)
|
||||
|
||||
@users('admin')
|
||||
def test_plan_next_activities(self):
|
||||
""" Test that next activities are displayed correctly. """
|
||||
test_plan = self.env['mail.activity.plan'].create({
|
||||
'name': 'Test Plan',
|
||||
'res_model': 'mail.test.activity',
|
||||
'template_ids': [
|
||||
(0, 0, {'activity_type_id': self.test_type_1.id}),
|
||||
(0, 0, {'activity_type_id': self.test_type_2.id}),
|
||||
(0, 0, {'activity_type_id': self.test_type_3.id}),
|
||||
],
|
||||
})
|
||||
# Assert expected next activities
|
||||
expected_next_activities = [['TestAct2'], ['TestAct1', 'TestAct3'], []]
|
||||
for template, expected_names in zip(test_plan.template_ids, expected_next_activities, strict=True):
|
||||
self.assertEqual(template.next_activity_ids.mapped('name'), expected_names)
|
||||
# Test the plan summary
|
||||
with self.subTest(test_case='Check plan summary'), \
|
||||
freeze_time(self.reference_now):
|
||||
form = self._instantiate_activity_schedule_wizard(self.test_records[0])
|
||||
form.plan_id = test_plan
|
||||
expected_values = [
|
||||
{'description': 'TestAct1', 'deadline': datetime(2023, 9, 30).date()},
|
||||
{'description': 'TestAct2', 'deadline': datetime(2023, 10, 21).date()},
|
||||
{'description': 'TestAct2', 'deadline': datetime(2023, 9, 30).date()},
|
||||
{'description': 'TestAct1', 'deadline': datetime(2023, 10, 2).date()},
|
||||
{'description': 'TestAct3', 'deadline': datetime(2023, 9, 30).date()},
|
||||
{'description': 'TestAct3', 'deadline': datetime(2023, 9, 30).date()},
|
||||
]
|
||||
for line, expected in zip(form.plan_schedule_line_ids._records, expected_values):
|
||||
with self.subTest(line=line, expected_values=expected):
|
||||
self.assertEqual(line['line_description'], expected['description'])
|
||||
self.assertEqual(line['line_date_deadline'], expected['deadline'])
|
||||
|
||||
@users('employee')
|
||||
def test_plan_schedule(self):
|
||||
""" Test schedule of a plan on a single or multiple records. """
|
||||
test_records_all = [self.test_records[0], self.test_records[:3]]
|
||||
for test_idx, test_case in enumerate(['mono', 'multi']):
|
||||
test_records = test_records_all[test_idx].with_env(self.env)
|
||||
with self.subTest(test_case=test_case, test_records=test_records), \
|
||||
freeze_time(self.reference_now):
|
||||
# No plan_date specified (-> self.reference_now is used), No responsible specified
|
||||
form = self._instantiate_activity_schedule_wizard(test_records)
|
||||
self.assertFalse(form.plan_schedule_line_ids)
|
||||
form.plan_id = self.plan_onboarding
|
||||
expected_values = [
|
||||
{'description': 'Plan training', 'deadline': datetime(2023, 9, 27).date()},
|
||||
{'description': 'Training', 'deadline': datetime(2023, 10, 14).date()},
|
||||
]
|
||||
for line, expected in zip(form.plan_schedule_line_ids._records, expected_values):
|
||||
self.assertEqual(line['line_description'], expected['description'])
|
||||
self.assertEqual(line['line_date_deadline'], expected['deadline'])
|
||||
self.assertTrue(form._get_modifier('plan_on_demand_user_id', 'invisible'))
|
||||
form.plan_id = self.plan_party
|
||||
expected_values = [
|
||||
{'description': 'Book a place', 'deadline': datetime(2023, 9, 29).date()},
|
||||
{'description': 'Invite special guest', 'deadline': datetime(2023, 10, 7).date()},
|
||||
]
|
||||
for line, expected in zip(form.plan_schedule_line_ids._records, expected_values):
|
||||
self.assertEqual(line['line_description'], expected['description'])
|
||||
self.assertEqual(line['line_date_deadline'], expected['deadline'])
|
||||
self.assertFalse(form._get_modifier('plan_on_demand_user_id', 'invisible'))
|
||||
with self._mock_activities():
|
||||
form.save().action_schedule_plan()
|
||||
|
||||
self.assertPlanExecution(
|
||||
self.plan_party, test_records,
|
||||
expected_deadlines=[(self.reference_now + relativedelta(days=-1)).date(),
|
||||
(self.reference_now + relativedelta(days=7)).date()])
|
||||
|
||||
# plan_date specified, responsible specified
|
||||
plan_date = self.reference_now.date() + relativedelta(days=14)
|
||||
responsible_id = self.user_admin
|
||||
form = self._instantiate_activity_schedule_wizard(test_records)
|
||||
form.plan_id = self.plan_party
|
||||
form.plan_date = plan_date
|
||||
form.plan_on_demand_user_id = self.env['res.users']
|
||||
self.assertTrue(form.has_error)
|
||||
self.assertIn(f'No responsible specified for {self.activity_type_todo.name}: Book a place',
|
||||
form.error)
|
||||
form.plan_on_demand_user_id = responsible_id
|
||||
self.assertFalse(form.has_error)
|
||||
deadline_1 = plan_date + relativedelta(days=-1)
|
||||
deadline_2 = plan_date + relativedelta(days=7)
|
||||
expected_values = [
|
||||
{'description': 'Book a place', 'deadline': deadline_1},
|
||||
{'description': 'Invite special guest', 'deadline': deadline_2},
|
||||
]
|
||||
for line, expected in zip(form.plan_schedule_line_ids._records, expected_values):
|
||||
self.assertEqual(line['line_description'], expected['description'])
|
||||
self.assertEqual(line['line_date_deadline'], expected['deadline'])
|
||||
with self._mock_activities():
|
||||
form.save().action_schedule_plan()
|
||||
|
||||
self.assertPlanExecution(
|
||||
self.plan_party, test_records,
|
||||
expected_deadlines=[plan_date + relativedelta(days=-1),
|
||||
plan_date + relativedelta(days=7)],
|
||||
expected_responsible=responsible_id)
|
||||
|
||||
@users('admin')
|
||||
def test_plan_setup_model_consistency(self):
|
||||
""" Test the model consistency of a plan.
|
||||
|
||||
Model consistency between activity_type - activity_template - plan:
|
||||
- a plan is restricted to a model
|
||||
- a plan contains activity plan templates which can be limited to some model
|
||||
through activity type
|
||||
"""
|
||||
# Setup independent activities type to avoid interference with existing data
|
||||
activity_type_1, activity_type_2, activity_type_3 = self.env['mail.activity.type'].create([
|
||||
{'name': 'Todo'},
|
||||
{'name': 'Call'},
|
||||
{'name': 'Partner-specific', 'res_model': 'res.partner'},
|
||||
])
|
||||
test_plan = self.env['mail.activity.plan'].create({
|
||||
'name': 'Test Plan',
|
||||
'res_model': 'mail.test.activity',
|
||||
'template_ids': [
|
||||
(0, 0, {'activity_type_id': activity_type_1.id}),
|
||||
(0, 0, {'activity_type_id': activity_type_2.id})
|
||||
],
|
||||
})
|
||||
|
||||
# ok, all activities generic
|
||||
test_plan.res_model = 'res.partner'
|
||||
test_plan.res_model = 'mail.test.activity'
|
||||
|
||||
with self.assertRaises(
|
||||
ValidationError,
|
||||
msg='Cannot set activity type to res.partner as linked to a plan of another model'):
|
||||
activity_type_1.res_model = 'res.partner'
|
||||
|
||||
activity_type_1.res_model = 'mail.test.activity'
|
||||
with self.assertRaises(
|
||||
ValidationError,
|
||||
msg='Cannot set plan to res.partner as using activities linked to another model'):
|
||||
test_plan.res_model = 'res.partner'
|
||||
|
||||
with self.assertRaises(
|
||||
ValidationError,
|
||||
msg='Cannot create activity template for res.partner as linked to a plan of another model'):
|
||||
self.env['mail.activity.plan.template'].create({
|
||||
'activity_type_id': activity_type_3.id,
|
||||
'plan_id': test_plan.id,
|
||||
})
|
||||
|
||||
@users('admin')
|
||||
def test_plan_setup_validation(self):
|
||||
""" Test plan consistency. """
|
||||
plan = self.env['mail.activity.plan'].create({
|
||||
'name': 'test',
|
||||
'res_model': 'mail.test.activity',
|
||||
})
|
||||
template = self.env['mail.activity.plan.template'].create({
|
||||
'activity_type_id': self.activity_type_todo.id,
|
||||
'plan_id': plan.id,
|
||||
'responsible_type': 'other',
|
||||
'responsible_id': self.user_admin.id,
|
||||
})
|
||||
template.responsible_type = 'on_demand'
|
||||
self.assertFalse(template.responsible_id)
|
||||
with self.assertRaises(
|
||||
ValidationError, msg='When selecting responsible "other", you must specify a responsible.'):
|
||||
template.responsible_type = 'other'
|
||||
template.write({'responsible_type': 'other', 'responsible_id': self.user_admin})
|
||||
1002
odoo-bringout-oca-ocb-test_mail/test_mail/tests/test_mail_alias.py
Normal file
1002
odoo-bringout-oca-ocb-test_mail/test_mail/tests/test_mail_alias.py
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,22 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.test_mail.tests.common import TestMailCommon, TestRecipients
|
||||
from odoo.addons.mail.tests.common import MailCommon, mail_new_test_user
|
||||
from odoo.addons.test_mail.tests.common import TestRecipients
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import users
|
||||
|
||||
|
||||
@tagged('mail_composer_mixin')
|
||||
class TestMailComposerMixin(TestMailCommon, TestRecipients):
|
||||
class TestMailComposerMixin(MailCommon, TestRecipients):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMailComposerMixin, cls).setUpClass()
|
||||
|
||||
# ensure employee can create partners, necessary for templates
|
||||
cls.user_employee.write({
|
||||
'groups_id': [(4, cls.env.ref('base.group_partner_manager').id)],
|
||||
})
|
||||
super().setUpClass()
|
||||
|
||||
cls.mail_template = cls.env['mail.template'].create({
|
||||
'body_html': '<p>EnglishBody for <t t-out="object.name"/></p>',
|
||||
|
|
@ -30,6 +27,22 @@ class TestMailComposerMixin(TestMailCommon, TestRecipients):
|
|||
'customer_id': cls.partner_1.id,
|
||||
})
|
||||
|
||||
# Enable group-based template management
|
||||
cls.env['ir.config_parameter'].set_param('mail.restrict.template.rendering', True)
|
||||
|
||||
# User without the group "mail.group_mail_template_editor"
|
||||
cls.user_rendering_restricted = mail_new_test_user(
|
||||
cls.env,
|
||||
company_id=cls.company_admin.id,
|
||||
groups='base.group_user',
|
||||
login='user_rendering_restricted',
|
||||
name='Code Template Restricted User',
|
||||
notification_type='inbox',
|
||||
signature='--\nErnest'
|
||||
)
|
||||
cls.user_rendering_restricted.group_ids -= cls.env.ref('mail.group_mail_template_editor')
|
||||
cls.user_employee.group_ids += cls.env.ref('mail.group_mail_template_editor')
|
||||
|
||||
cls._activate_multi_lang(
|
||||
layout_arch_db='<body><t t-out="message.body"/> English Layout for <t t-esc="model_description"/></body>',
|
||||
lang_code='es_ES',
|
||||
|
|
@ -41,19 +54,86 @@ class TestMailComposerMixin(TestMailCommon, TestRecipients):
|
|||
def test_content_sync(self):
|
||||
""" Test updating template updates the dynamic fields accordingly. """
|
||||
source = self.test_record.with_env(self.env)
|
||||
template = self.mail_template.with_env(self.env)
|
||||
template_void = template.copy()
|
||||
template_void.write({
|
||||
'body_html': '<p><br /></p>',
|
||||
'lang': False,
|
||||
'subject': False,
|
||||
})
|
||||
|
||||
composer = self.env['mail.test.composer.mixin'].create({
|
||||
'name': 'Invite',
|
||||
'template_id': template.id,
|
||||
'source_ids': [(4, source.id)],
|
||||
})
|
||||
self.assertEqual(composer.body, template.body_html)
|
||||
self.assertTrue(composer.body_has_template_value)
|
||||
self.assertEqual(composer.lang, template.lang)
|
||||
self.assertEqual(composer.subject, template.subject)
|
||||
|
||||
# check rendering
|
||||
body = composer._render_field('body', source.ids)[source.id]
|
||||
self.assertEqual(body, f'<p>EnglishBody for {source.name}</p>')
|
||||
subject = composer._render_field('subject', source.ids)[source.id]
|
||||
self.assertEqual(subject, f'EnglishSubject for {source.name}')
|
||||
|
||||
# manual values > template default values
|
||||
composer.write({
|
||||
'body': '<p>CustomBody for <t t-out="object.name"/></p>',
|
||||
'subject': 'CustomSubject for {{ object.name }}',
|
||||
})
|
||||
self.assertFalse(composer.body_has_template_value)
|
||||
|
||||
body = composer._render_field('body', source.ids)[source.id]
|
||||
self.assertEqual(body, f'<p>CustomBody for {source.name}</p>')
|
||||
subject = composer._render_field('subject', source.ids)[source.id]
|
||||
self.assertEqual(subject, f'CustomSubject for {source.name}')
|
||||
|
||||
# template with void values: should not force void (TODO)
|
||||
composer.template_id = template_void.id
|
||||
self.assertEqual(composer.body, '<p>CustomBody for <t t-out="object.name"/></p>')
|
||||
self.assertFalse(composer.body_has_template_value)
|
||||
self.assertEqual(composer.lang, template.lang)
|
||||
self.assertEqual(composer.subject, 'CustomSubject for {{ object.name }}')
|
||||
|
||||
# reset template TOOD should reset
|
||||
composer.write({'template_id': False})
|
||||
self.assertFalse(composer.body)
|
||||
self.assertFalse(composer.body_has_template_value)
|
||||
self.assertFalse(composer.lang)
|
||||
self.assertFalse(composer.subject)
|
||||
|
||||
@users("user_rendering_restricted")
|
||||
def test_mail_composer_mixin_render_lang(self):
|
||||
""" Test _render_lang when rendering is involved, depending on template
|
||||
editor rights. """
|
||||
source = self.test_record.with_env(self.env)
|
||||
composer = self.env['mail.test.composer.mixin'].create({
|
||||
'description': '<p>Description for <t t-esc="object.name"/></p>',
|
||||
'name': 'Invite',
|
||||
'template_id': self.mail_template.id,
|
||||
'source_ids': [(4, source.id)],
|
||||
})
|
||||
self.assertEqual(composer.body, self.mail_template.body_html)
|
||||
self.assertEqual(composer.subject, self.mail_template.subject)
|
||||
self.assertFalse(composer.lang, 'Fixme: lang is not propagated currently')
|
||||
|
||||
subject = composer._render_field('subject', source.ids)[source.id]
|
||||
self.assertEqual(subject, f'EnglishSubject for {source.name}')
|
||||
body = composer._render_field('body', source.ids)[source.id]
|
||||
self.assertEqual(body, f'<p>EnglishBody for {source.name}</p>')
|
||||
# _render_lang should be ok when content is the same as template
|
||||
rendered = composer._render_lang(source.ids)
|
||||
self.assertEqual(rendered, {source.id: self.partner_1.lang})
|
||||
|
||||
# _render_lang should crash when content is dynamic and not coming from template
|
||||
composer.lang = " {{ 'en_US' }}"
|
||||
with self.assertRaises(AccessError):
|
||||
rendered = composer._render_lang(source.ids)
|
||||
|
||||
# _render_lang should not crash when content is not coming from template
|
||||
# but not dynamic and/or is actually the default computed based on partner
|
||||
for lang_value, expected in [
|
||||
(False, self.partner_1.lang), ("", self.partner_1.lang), ("fr_FR", "fr_FR")
|
||||
]:
|
||||
with self.subTest(lang_value=lang_value):
|
||||
composer.lang = lang_value
|
||||
rendered = composer._render_lang(source.ids)
|
||||
self.assertEqual(rendered, {source.id: expected})
|
||||
|
||||
@users("employee")
|
||||
def test_rendering_custom(self):
|
||||
|
|
@ -84,7 +164,6 @@ class TestMailComposerMixin(TestMailCommon, TestRecipients):
|
|||
source = self.test_record.with_env(self.env)
|
||||
composer = self.env['mail.test.composer.mixin'].create({
|
||||
'description': '<p>Description for <t t-esc="object.name"/></p>',
|
||||
'lang': '{{ object.customer_id.lang }}',
|
||||
'name': 'Invite',
|
||||
'template_id': self.mail_template.id,
|
||||
'source_ids': [(4, source.id)],
|
||||
|
|
@ -103,11 +182,22 @@ class TestMailComposerMixin(TestMailCommon, TestRecipients):
|
|||
|
||||
# ask for dynamic language computation
|
||||
subject = composer._render_field('subject', source.ids, compute_lang=True)[source.id]
|
||||
self.assertEqual(subject, f'EnglishSubject for {source.name}',
|
||||
'Fixme: translations are not done, as taking composer translations and not template one')
|
||||
self.assertEqual(subject, f'SpanishSubject for {source.name}',
|
||||
'Translation comes from the template, as both values equal')
|
||||
body = composer._render_field('body', source.ids, compute_lang=True)[source.id]
|
||||
self.assertEqual(body, f'<p>EnglishBody for {source.name}</p>',
|
||||
'Fixme: translations are not done, as taking composer translations and not template one'
|
||||
)
|
||||
self.assertEqual(body, f'<p>SpanishBody for {source.name}</p>',
|
||||
'Translation comes from the template, as both values equal')
|
||||
description = composer._render_field('description', source.ids)[source.id]
|
||||
self.assertEqual(description, f'<p>Description for {source.name}</p>')
|
||||
|
||||
# check default computation when 'lang' is void -> actually rerouted to template lang
|
||||
composer.lang = False
|
||||
subject = composer._render_field('subject', source.ids, compute_lang=True)[source.id]
|
||||
self.assertEqual(subject, f'SpanishSubject for {source.name}',
|
||||
'Translation comes from the template, as both values equal')
|
||||
|
||||
# check default computation when 'lang' is void in both -> main customer lang
|
||||
self.mail_template.lang = False
|
||||
subject = composer._render_field('subject', source.ids, compute_lang=True)[source.id]
|
||||
self.assertEqual(subject, f'SpanishSubject for {source.name}',
|
||||
'Translation comes from customer lang, being default when no value is rendered')
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue