diff --git a/addons/project/__manifest__.py b/addons/project/__manifest__.py index 82ab8755fa2..d4558548f20 100644 --- a/addons/project/__manifest__.py +++ b/addons/project/__manifest__.py @@ -80,6 +80,9 @@ ], 'web.qunit_suite_tests': [ 'project/static/tests/burndown_chart_tests.js', - ] + ], + 'web.assets_tests': [ + 'project/static/tests/tours/**/*', + ], } } diff --git a/addons/project/static/tests/tours/project_update_tour_tests.js b/addons/project/static/tests/tours/project_update_tour_tests.js new file mode 100644 index 00000000000..98109fb6a52 --- /dev/null +++ b/addons/project/static/tests/tours/project_update_tour_tests.js @@ -0,0 +1,139 @@ +/** @odoo-module **/ + +import tour from 'web_tour.tour'; + +tour.register('project_update_tour', { + test: true, + url: '/web', +}, +[tour.stepUtils.showAppsMenuItem(), { + trigger: '.o_app[data-menu-xmlid="project.menu_main_pm"]', +}, { + trigger: '.o-kanban-button-new', + extra_trigger: '.o_project_kanban', + width: 200, +}, { + trigger: 'input.o_project_name', + run: 'text New Project' +}, { + trigger: '.o_open_tasks', + run: function (actions) { + actions.auto('.modal:visible .btn.btn-primary'); + }, +}, { + trigger: ".o_kanban_project_tasks .o_column_quick_create .input-group", + run: function (actions) { + actions.text("New", this.$anchor.find("input")); + }, +}, { + trigger: ".o_kanban_project_tasks .o_column_quick_create .o_kanban_add", + auto: true, +}, { + trigger: ".o_kanban_project_tasks .o_column_quick_create .input-group", + extra_trigger: '.o_kanban_group', + run: function (actions) { + actions.text("Done", this.$anchor.find("input")); + }, +}, { + trigger: ".o_kanban_project_tasks .o_column_quick_create .o_kanban_add", + auto: true, +}, { + trigger: '.o-kanban-button-new', + extra_trigger: '.o_kanban_group:eq(0)' +}, { + trigger: '.o_kanban_quick_create input.o_field_char[name=name]', + extra_trigger: '.o_kanban_project_tasks', + run: 'text New task' +}, { + trigger: '.o_kanban_quick_create .o_kanban_add', + extra_trigger: '.o_kanban_project_tasks' +}, { + trigger: '.o-kanban-button-new', + extra_trigger: '.o_kanban_group:eq(0)' +}, { + trigger: '.o_kanban_quick_create input.o_field_char[name=name]', + extra_trigger: '.o_kanban_project_tasks', + run: 'text Second task' +}, { + trigger: '.o_kanban_quick_create .o_kanban_add', + extra_trigger: '.o_kanban_project_tasks' +}, { + trigger: '.o_kanban_header:eq(1)', + run: function () { + $('.o_kanban_config.dropdown .dropdown-toggle').eq(1).click(); + } +}, { + trigger: ".dropdown-item.o_column_edit", +}, { + trigger: ".o_field_widget[name=fold] input", +}, { + trigger: ".modal-footer button", +}, { + trigger: ".o_kanban_record .oe_kanban_content", + extra_trigger: '.o_kanban_project_tasks', + run: "drag_and_drop .o_kanban_group:eq(1) ", +}, { + trigger: ".o_project_updates_breadcrumb", +}, { + trigger: ".o_add_milestone a", +}, { + trigger: "input.o_field_widget[name=name]", + run: 'text New milestone' +}, { + trigger: "input.datetimepicker-input[name=deadline]", + run: 'text 12/12/2099' +}, { + trigger: ".modal-footer button" +}, { + trigger: ".o_add_milestone a", +}, { + trigger: "input.o_field_widget[name=name]", + run: 'text Second milestone' +}, { + trigger: "input.datetimepicker-input[name=deadline]", + run: 'text 12/12/2021' +}, { + trigger: ".modal-footer button" +}, { + trigger: ".o_rightpanel_left_button:eq(0)" +}, { + trigger: ".breadcrumb :contains('Tasks Analysis')", +}, { + trigger: ".o_back_button" +}, { + trigger: ".o_rightpanel_left_button:eq(1)" +}, { + trigger: ".breadcrumb :contains('Burndown Chart')", +}, { + trigger: ".o_back_button" +}, { + trigger: ".o_open_milestone:eq(1) .o_milestone_detail span:eq(0)", + extra_trigger: ".o_add_milestone a" +}, { + trigger: "input.datetimepicker-input[name=deadline]", + run: 'text 12/12/2100' +}, { + trigger: ".modal-footer button" +}, { + trigger: ".o-kanban-button-new" +}, { + trigger: "input.o_field_widget[name=name]", + run: 'text New update' +}, { + trigger: ".o_form_button_save" +}, { + trigger: ".o_field_widget[name=description] :contains('1 tasks are open, out of 2.')", + run: function () {}, +}, { + trigger: ".o_field_widget[name=description] :contains('2 were created')", + run: function () {}, +}, { + trigger: ".o_field_widget[name=description] :contains('1 was closed')", + run: function () {}, +}, { + trigger: ".o_field_widget[name=description] :contains('New milestone') :contains('(due on 12/12/2100)')", + run: function () {}, +}, { + trigger: ".o_field_widget[name=description] :contains('New milestone') :contains('(12/12/2099 => 12/12/2100)')", + run: function () {}, +}]); diff --git a/addons/project/tests/__init__.py b/addons/project/tests/__init__.py index a7a0bacc312..c127c45b1e6 100644 --- a/addons/project/tests/__init__.py +++ b/addons/project/tests/__init__.py @@ -7,6 +7,9 @@ from . import test_project_flow from . import test_project_recurrence from . import test_project_subtasks from . import test_project_ui +from . import test_project_update_access_rights +from . import test_project_update_flow +from . import test_project_update_ui from . import test_portal from . import test_multicompany from . import test_task_dependencies diff --git a/addons/project/tests/test_project_update_access_rights.py b/addons/project/tests/test_project_update_access_rights.py new file mode 100644 index 00000000000..59ef64edb67 --- /dev/null +++ b/addons/project/tests/test_project_update_access_rights.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- + +from odoo.exceptions import AccessError +from odoo.tests import tagged +from odoo.tests.common import users + +from odoo.addons.mail.tests.common import mail_new_test_user +from odoo.addons.project.tests.test_project_base import TestProjectCommon + +@tagged('-at_install', 'post_install') +class TestProjectUpdateAccessRights(TestProjectCommon): + @classmethod + def setUpClass(cls): + super(TestProjectUpdateAccessRights, cls).setUpClass() + cls.project_update_1 = cls.env['project.update'].create({ + 'name': "Test Project Update", + 'project_id': cls.project_pigs.id, + 'status': 'on_track', + }) + cls.base_user = mail_new_test_user(cls.env, 'Base user', groups='base.group_user') + cls.project_user = mail_new_test_user(cls.env, 'Project user', groups='project.group_project_user') + cls.project_manager = mail_new_test_user(cls.env, 'Project admin', groups='project.group_project_manager') + + @users('Project user', 'Project admin', 'Base user') + def test_project_update_user_can_read(self): + self.project_update_1.with_user(self.env.user).name + + @users('Project user', 'Base user') + def test_project_update_user_no_write(self): + with self.assertRaises(AccessError, msg="%s should not be able to write in the project update" % self.env.user.name): + self.project_update_1.with_user(self.env.user).name = "Test write" + + @users('Project admin') + def test_project_update_admin_can_write(self): + self.project_update_1.with_user(self.env.user).name = "Test write" + + @users('Project user') + def test_project_update_user_project_owner_cannot_write(self): + self.project_pigs.user_id = self.env.user + with self.assertRaises(AccessError, msg="%s, who owns the project, should not be able to write in the project update" % self.env.user.name): + self.project_update_1.with_user(self.env.user).name = "Test write" + + @users('Project user', 'Base user') + def test_project_update_user_no_unlink(self): + with self.assertRaises(AccessError, msg="%s should not be able to unlink in the project update" % self.env.user.name): + self.project_update_1.with_user(self.env.user).unlink() + + @users('Project admin') + def test_project_update_admin_unlink(self): + self.project_update_1.with_user(self.env.user).unlink() + + @users('Project user') + def test_project_update_user_project_owner_no_unlink(self): + self.project_pigs.user_id = self.env.user + with self.assertRaises(AccessError, msg="%s should not be able to unlink the project update even if project owner" % self.env.user.name): + self.project_update_1.with_user(self.env.user).unlink() diff --git a/addons/project/tests/test_project_update_flow.py b/addons/project/tests/test_project_update_flow.py new file mode 100644 index 00000000000..18179a572dd --- /dev/null +++ b/addons/project/tests/test_project_update_flow.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- + +from dateutil.relativedelta import relativedelta + +from odoo import fields +from odoo.tests import tagged +from odoo.tests.common import Form + +from odoo.addons.project.tests.test_project_base import TestProjectCommon + +@tagged('-at_install', 'post_install') +class TestProjectUpdate(TestProjectCommon): + + def test_project_update_form(self): + with Form(self.env['project.milestone'].with_context({'default_project_id': self.project_pigs.id})) as milestone_form: + milestone_form.name = "Test 1" + milestone_form.deadline = fields.Date.today() + + try: + with Form(self.env['project.update'].with_context({'default_project_id': self.project_pigs.id})) as update_form: + update_form.name = "Test" + update_form.progress = 65 + update = update_form.save() + except Exception as e: + raise AssertionError("Error raised unexpectedly while filling the project update form ! Exception : " + e.args[0]) + + self.assertEqual(update.user_id, self.env.user, "The author is the user who created the update.") + self.assertNotEqual(len(update.description), 0, "The description should not be empty.") + self.assertTrue("Tasks" in update.description, "The description should contain 'Tasks'.") + self.assertTrue("Milestone" in update.description, "The description should contain 'Milestone'.") + self.assertEqual(update.status, 'on_track', "The status should be the default one.") + + with Form(self.env['project.update'].with_context({'default_project_id': self.project_pigs.id})) as update_form: + update_form.name = "Test 2" + update = update_form.save() + self.assertEqual(update.progress, 65, "The default progress is the one from the previous update by default") + + def test_project_update_description(self): + with Form(self.env['project.milestone'].with_context({'default_project_id': self.project_pigs.id})) as milestone_form: + milestone_form.name = "Test 1" + milestone_form.deadline = fields.Date.today() + with Form(self.env['project.milestone'].with_context({'default_project_id': self.project_pigs.id})) as milestone_form: + milestone_form.name = "Test 2" + milestone_form.deadline = fields.Date.today() + with Form(self.env['project.milestone'].with_context({'default_project_id': self.project_pigs.id})) as milestone_form: + milestone_form.name = "Test 3" + milestone_form.deadline = fields.Date.today() + relativedelta(years=2) + + template_values = self.env['project.update']._get_template_values(self.project_pigs) + + self.assertEqual(template_values['tasks']['open_tasks'], 2, "Open tasks should be equal to 2") + self.assertEqual(template_values['tasks']['total_tasks'], 2, "Total tasks should be equal to 2") + self.assertEqual(template_values['tasks']['created_tasks'], 2, "Created tasks should be equal to 2") + self.assertEqual(template_values['tasks']['closed_tasks'], 0, "Closed tasks should be equal to 0") + + self.assertEqual(len(template_values['milestones']['list']), 2, "Milestone list length should be equal to 2") + self.assertEqual(len(template_values['milestones']['created']), 3, "Milestone created length tasks should be equal to 3") + + def test_project_update_panel(self): + with Form(self.env['project.milestone'].with_context({'default_project_id': self.project_pigs.id})) as milestone_form: + milestone_form.name = "Test 1" + milestone_form.deadline = fields.Date.today() + relativedelta(years=-1) + with Form(self.env['project.milestone'].with_context({'default_project_id': self.project_pigs.id})) as milestone_form: + milestone_form.name = "Test 2" + milestone_form.deadline = fields.Date.today() + relativedelta(years=-1) + milestone_form.is_reached = True + with Form(self.env['project.milestone'].with_context({'default_project_id': self.project_pigs.id})) as milestone_form: + milestone_form.name = "Test 3" + milestone_form.deadline = fields.Date.today() + relativedelta(years=2) + + panel_data = self.project_pigs.get_panel_data() + + self.assertEqual(len(panel_data['tasks_analysis']['data']), 2, "Panel data should contain 'tasks' entry") + self.assertEqual(len(panel_data['milestones']['data']), 3, "Panel data should contain 'milestone' entry") + self.assertFalse(panel_data['milestones']['data'][0]['is_deadline_exceeded'], "Milestone is achieved") + self.assertTrue(panel_data['milestones']['data'][1]['is_deadline_exceeded'], "Milestone is exceeded") + self.assertTrue(panel_data['milestones']['data'][0]['is_reached'], "Milestone is done") + self.assertFalse(panel_data['milestones']['data'][1]['is_reached'], "Milestone isn't done") + # sorting + self.assertEqual(panel_data['milestones']['data'][0]['name'], "Test 2", "Sorting isn't correct") + self.assertEqual(panel_data['milestones']['data'][1]['name'], "Test 1", "Sorting isn't correct") + self.assertEqual(panel_data['milestones']['data'][2]['name'], "Test 3", "Sorting isn't correct") diff --git a/addons/project/tests/test_project_update_ui.py b/addons/project/tests/test_project_update_ui.py new file mode 100644 index 00000000000..df60a3eb64a --- /dev/null +++ b/addons/project/tests/test_project_update_ui.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- + +from odoo.tests import HttpCase, tagged + +@tagged('post_install', '-at_install') +class TestProjectUpdateUi(HttpCase): + + def test_01_project_tour(self): + self.start_tour("/web", 'project_update_tour', login="admin")