diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/web/tests | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/web/tests')
| -rw-r--r-- | addons/web/tests/__init__.py | 10 | ||||
| -rw-r--r-- | addons/web/tests/logo_ci.png | bin | 0 -> 2899 bytes | |||
| -rw-r--r-- | addons/web/tests/odoo.png | bin | 0 -> 5631 bytes | |||
| -rw-r--r-- | addons/web/tests/sweden.png | bin | 0 -> 1640 bytes | |||
| -rw-r--r-- | addons/web/tests/test_base_document_layout.py | 226 | ||||
| -rw-r--r-- | addons/web/tests/test_click_everywhere.py | 42 | ||||
| -rw-r--r-- | addons/web/tests/test_image.py | 60 | ||||
| -rw-r--r-- | addons/web/tests/test_js.py | 42 | ||||
| -rw-r--r-- | addons/web/tests/test_menu.py | 55 | ||||
| -rw-r--r-- | addons/web/tests/test_read_progress_bar.py | 144 | ||||
| -rw-r--r-- | addons/web/tests/test_serving_base.py | 1002 |
11 files changed, 1581 insertions, 0 deletions
diff --git a/addons/web/tests/__init__.py b/addons/web/tests/__init__.py new file mode 100644 index 00000000..daac593d --- /dev/null +++ b/addons/web/tests/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import test_image +from . import test_js +from . import test_menu +from . import test_serving_base +from . import test_click_everywhere +from . import test_base_document_layout +from . import test_read_progress_bar diff --git a/addons/web/tests/logo_ci.png b/addons/web/tests/logo_ci.png Binary files differnew file mode 100644 index 00000000..1a1ae58e --- /dev/null +++ b/addons/web/tests/logo_ci.png diff --git a/addons/web/tests/odoo.png b/addons/web/tests/odoo.png Binary files differnew file mode 100644 index 00000000..dd7c1e62 --- /dev/null +++ b/addons/web/tests/odoo.png diff --git a/addons/web/tests/sweden.png b/addons/web/tests/sweden.png Binary files differnew file mode 100644 index 00000000..be480002 --- /dev/null +++ b/addons/web/tests/sweden.png diff --git a/addons/web/tests/test_base_document_layout.py b/addons/web/tests/test_base_document_layout.py new file mode 100644 index 00000000..0f3f9e8b --- /dev/null +++ b/addons/web/tests/test_base_document_layout.py @@ -0,0 +1,226 @@ +import os +from PIL import Image +from functools import partial + +from odoo.tests import TransactionCase, tagged, Form +from odoo.tools import frozendict, image_to_base64, hex_to_rgb + + +dir_path = os.path.dirname(os.path.realpath(__file__)) +_file_cache = {} + + +class TestBaseDocumentLayoutHelpers(TransactionCase): + # + # Public + # + def setUp(self): + super(TestBaseDocumentLayoutHelpers, self).setUp() + self.color_fields = ['primary_color', 'secondary_color'] + self.company = self.env.company + self.css_color_error = 0 + self._set_templates_and_layouts() + self._set_images() + + def assertColors(self, checked_obj, expected): + _expected_getter = expected.get if isinstance(expected, dict) else partial(getattr, expected) + for fname in self.color_fields: + color1 = getattr(checked_obj, fname) + color2 = _expected_getter(fname) + if self.css_color_error: + self._compare_colors_rgb(color1, color2) + else: + self.assertEqual(color1, color2) + + # + # Private + # + def _compare_colors_rgb(self, color1, color2): + self.assertEqual(bool(color1), bool(color2)) + if not color1: + return + color1 = hex_to_rgb(color1) + color2 = hex_to_rgb(color2) + self.assertEqual(len(color1), len(color2)) + for i in range(len(color1)): + self.assertAlmostEqual(color1[i], color2[i], delta=self.css_color_error) + + def _get_images_for_test(self): + return ['sweden.png', 'odoo.png'] + + def _set_images(self): + for fname in self._get_images_for_test(): + fname_split = fname.split('.') + if not fname_split[0] in _file_cache: + with Image.open(os.path.join(dir_path, fname), 'r') as img: + base64_img = image_to_base64(img, 'PNG') + primary, secondary = self.env['base.document.layout'].create( + {})._parse_logo_colors(base64_img) + _img = frozendict({ + 'img': base64_img, + 'colors': { + 'primary_color': primary, + 'secondary_color': secondary, + }, + }) + _file_cache[fname_split[0]] = _img + self.company_imgs = frozendict(_file_cache) + + def _set_templates_and_layouts(self): + self.layout_template1 = self.env['ir.ui.view'].create({ + 'name': 'layout_template1', + 'key': 'web.layout_template1', + 'type': 'qweb', + 'arch': '''<div></div>''', + }) + self.env['ir.model.data'].create({ + 'name': self.layout_template1.name, + 'model': 'ir.ui.view', + 'module': 'web', + 'res_id': self.layout_template1.id, + }) + self.default_colors = { + 'primary_color': '#000000', + 'secondary_color': '#000000', + } + self.report_layout1 = self.env['report.layout'].create({ + 'view_id': self.layout_template1.id, + 'name': 'report_%s' % self.layout_template1.name, + }) + self.layout_template2 = self.env['ir.ui.view'].create({ + 'name': 'layout_template2', + 'key': 'web.layout_template2', + 'type': 'qweb', + 'arch': '''<div></div>''', + }) + self.env['ir.model.data'].create({ + 'name': self.layout_template2.name, + 'model': 'ir.ui.view', + 'module': 'web', + 'res_id': self.layout_template2.id, + }) + self.report_layout2 = self.env['report.layout'].create({ + 'view_id': self.layout_template2.id, + 'name': 'report_%s' % self.layout_template2.name, + }) + + +@tagged('document_layout') +class TestBaseDocumentLayout(TestBaseDocumentLayoutHelpers): + # Logo change Tests + def test_company_no_color_change_logo(self): + """When neither a logo nor the colors are set + The wizard displays the colors of the report layout + Changing logo means the colors on the wizard change too + Emptying the logo works and doesn't change the colors""" + self.company.write({ + 'primary_color': False, + 'secondary_color': False, + 'logo': False, + 'external_report_layout_id': self.env.ref('web.layout_template1').id, + 'paperformat_id': self.env.ref('base.paperformat_us').id, + }) + default_colors = self.default_colors + with Form(self.env['base.document.layout']) as doc_layout: + self.assertColors(doc_layout, default_colors) + self.assertEqual(doc_layout.company_id, self.company) + doc_layout.logo = self.company_imgs['sweden']['img'] + + self.assertColors(doc_layout, self.company_imgs['sweden']['colors']) + + doc_layout.logo = '' + self.assertColors(doc_layout, self.company_imgs['sweden']['colors']) + self.assertEqual(doc_layout.logo, '') + + def test_company_no_color_but_logo_change_logo(self): + """When company colors are not set, but a logo is, + the wizard displays the computed colors from the logo""" + self.company.write({ + 'primary_color': '#ff0080', + 'secondary_color': '#00ff00', + 'logo': self.company_imgs['sweden']['img'], + 'paperformat_id': self.env.ref('base.paperformat_us').id, + }) + + with Form(self.env['base.document.layout']) as doc_layout: + self.assertColors(doc_layout, self.company) + doc_layout.logo = self.company_imgs['odoo']['img'] + self.assertColors(doc_layout, self.company_imgs['odoo']['colors']) + + def test_company_colors_change_logo(self): + """changes of the logo implies displaying the new computed colors""" + self.company.write({ + 'primary_color': '#ff0080', + 'secondary_color': '#00ff00', + 'logo': False, + 'paperformat_id': self.env.ref('base.paperformat_us').id, + }) + + with Form(self.env['base.document.layout']) as doc_layout: + self.assertColors(doc_layout, self.company) + doc_layout.logo = self.company_imgs['odoo']['img'] + self.assertColors(doc_layout, self.company_imgs['odoo']['colors']) + + def test_company_colors_and_logo_change_logo(self): + """The colors of the company may differ from the one the logo computes + Opening the wizard in these condition displays the company's colors + When the logo changes, colors must change according to the logo""" + self.company.write({ + 'primary_color': '#ff0080', + 'secondary_color': '#00ff00', + 'logo': self.company_imgs['sweden']['img'], + 'paperformat_id': self.env.ref('base.paperformat_us').id, + }) + + with Form(self.env['base.document.layout']) as doc_layout: + self.assertColors(doc_layout, self.company) + doc_layout.logo = self.company_imgs['odoo']['img'] + self.assertColors(doc_layout, self.company_imgs['odoo']['colors']) + + # Layout change tests + def test_company_colors_reset_colors(self): + """Reset the colors when they differ from the ones originally + computed from the company logo""" + self.company.write({ + 'primary_color': '#ff0080', + 'secondary_color': '#00ff00', + 'logo': self.company_imgs['sweden']['img'], + 'paperformat_id': self.env.ref('base.paperformat_us').id, + }) + + with Form(self.env['base.document.layout']) as doc_layout: + self.assertColors(doc_layout, self.company) + doc_layout.primary_color = doc_layout.logo_primary_color + doc_layout.secondary_color = doc_layout.logo_secondary_color + self.assertColors(doc_layout, self.company_imgs['sweden']['colors']) + + def test_parse_company_colors_grayscale(self): + """Grayscale images with transparency - make sure the color extraction does not crash""" + self.company.write({ + 'primary_color': '#ff0080', + 'secondary_color': '#00ff00', + 'paperformat_id': self.env.ref('base.paperformat_us').id, + }) + with Form(self.env['base.document.layout']) as doc_layout: + with Image.open(os.path.join(dir_path, 'logo_ci.png'), 'r') as img: + base64_img = image_to_base64(img, 'PNG') + doc_layout.logo = base64_img + self.assertNotEqual(None, doc_layout.primary_color) + + + # /!\ This case is NOT supported, and probably not supportable + # res.partner resizes manu-militari the image it is given + # so res.company._get_logo differs from res.partner.[default image] + # def test_company_no_colors_default_logo_and_layout_change_layout(self): + # """When the default YourCompany logo is set, and no colors are set on company: + # change wizard's color according to template""" + # self.company.write({ + # 'primary_color': False, + # 'secondary_color': False, + # 'external_report_layout_id': self.layout_template1.id, + # }) + # default_colors = self.default_colors + # with Form(self.env['base.document.layout']) as doc_layout: + # self.assertColors(doc_layout, default_colors) + # doc_layout.report_layout_id = self.report_layout2 + # self.assertColors(doc_layout, self.report_layout2) diff --git a/addons/web/tests/test_click_everywhere.py b/addons/web/tests/test_click_everywhere.py new file mode 100644 index 00000000..92b2ad3c --- /dev/null +++ b/addons/web/tests/test_click_everywhere.py @@ -0,0 +1,42 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import logging +import odoo.tests + +_logger = logging.getLogger(__name__) + + +@odoo.tests.tagged('click_all', 'post_install', '-at_install', '-standard') +class TestMenusAdmin(odoo.tests.HttpCase): + + def test_01_click_everywhere_as_admin(self): + menus = self.env['ir.ui.menu'].load_menus(False) + for app in menus['children']: + with self.subTest(app=app['name']): + _logger.runbot('Testing %s', app['name']) + self.browser_js("/web", "odoo.__DEBUG__.services['web.clickEverywhere']('%s');" % app['xmlid'], "odoo.isReady === true", login="admin", timeout=300) + self.terminate_browser() + + +@odoo.tests.tagged('click_all', 'post_install', '-at_install', '-standard') +class TestMenusDemo(odoo.tests.HttpCase): + + def test_01_click_everywhere_as_demo(self): + menus = self.env['ir.ui.menu'].load_menus(False) + for app in menus['children']: + with self.subTest(app=app['name']): + _logger.runbot('Testing %s', app['name']) + self.browser_js("/web", "odoo.__DEBUG__.services['web.clickEverywhere']('%s');" % app['xmlid'], "odoo.isReady === true", login="demo", timeout=300) + self.terminate_browser() + +@odoo.tests.tagged('post_install', '-at_install') +class TestMenusAdminLight(odoo.tests.HttpCase): + + def test_01_click_apps_menus_as_admin(self): + self.browser_js("/web", "odoo.__DEBUG__.services['web.clickEverywhere'](undefined, true);", "odoo.isReady === true", login="admin", timeout=120) + +@odoo.tests.tagged('post_install', '-at_install',) +class TestMenusDemoLight(odoo.tests.HttpCase): + + def test_01_click_apps_menus_as_demo(self): + self.browser_js("/web", "odoo.__DEBUG__.services['web.clickEverywhere'](undefined, true);", "odoo.isReady === true", login="demo", timeout=120) diff --git a/addons/web/tests/test_image.py b/addons/web/tests/test_image.py new file mode 100644 index 00000000..161a0645 --- /dev/null +++ b/addons/web/tests/test_image.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import io +import base64 + +from PIL import Image + +from odoo.tests.common import HttpCase, tagged + + +@tagged('-at_install', 'post_install') +class TestImage(HttpCase): + def test_01_content_image_resize_placeholder(self): + """The goal of this test is to make sure the placeholder image is + resized appropriately depending on the given URL parameters.""" + + # CASE: resize placeholder, given size but original ratio is always kept + response = self.url_open('/web/image/0/200x150') + image = Image.open(io.BytesIO(response.content)) + self.assertEqual(image.size, (150, 150)) + + # CASE: resize placeholder to 128 + response = self.url_open('/web/image/fake/0/image_128') + image = Image.open(io.BytesIO(response.content)) + self.assertEqual(image.size, (128, 128)) + + # CASE: resize placeholder to 256 + response = self.url_open('/web/image/fake/0/image_256') + image = Image.open(io.BytesIO(response.content)) + self.assertEqual(image.size, (256, 256)) + + # CASE: resize placeholder to 1024 (but placeholder image is too small) + response = self.url_open('/web/image/fake/0/image_1024') + image = Image.open(io.BytesIO(response.content)) + self.assertEqual(image.size, (256, 256)) + + # CASE: no size found, use placeholder original size + response = self.url_open('/web/image/fake/0/image_no_size') + image = Image.open(io.BytesIO(response.content)) + self.assertEqual(image.size, (256, 256)) + + def test_02_content_image_Etag_304(self): + """This test makes sure that the 304 response is properly returned if the ETag is properly set""" + + attachment = self.env['ir.attachment'].create({ + 'datas': b"R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=", + 'name': 'testEtag.gif', + 'public': True, + 'mimetype': 'image/gif', + }) + response = self.url_open('/web/image/%s' % attachment.id, timeout=None) + self.assertEqual(response.status_code, 200) + self.assertEqual(base64.b64encode(response.content), attachment.datas) + + etag = response.headers.get('ETag') + + response2 = self.url_open('/web/image/%s' % attachment.id, headers={"If-None-Match": etag}) + self.assertEqual(response2.status_code, 304) + self.assertEqual(len(response2.content), 0) diff --git a/addons/web/tests/test_js.py b/addons/web/tests/test_js.py new file mode 100644 index 00000000..4d97e5f2 --- /dev/null +++ b/addons/web/tests/test_js.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import re +import odoo.tests + +RE_ONLY = re.compile('QUnit\.only\(') + + +@odoo.tests.tagged('post_install', '-at_install') +class WebSuite(odoo.tests.HttpCase): + + def test_js(self): + # webclient desktop test suite + self.browser_js('/web/tests?mod=web&failfast', "", "", login='admin', timeout=1800) + + def test_check_suite(self): + # verify no js test is using `QUnit.only` as it forbid any other test to be executed + self._check_only_call('web.qunit_suite_tests') + self._check_only_call('web.qunit_mobile_suite_tests') + + def _check_only_call(self, suite): + # As we currently aren't in a request context, we can't render `web.layout`. + # redefinied it as a minimal proxy template. + self.env.ref('web.layout').write({'arch_db': '<t t-name="web.layout"><head><meta charset="utf-8"/><t t-raw="head"/></head></t>'}) + + for asset in self.env['ir.qweb']._get_asset_content(suite, options={})[0]: + filename = asset['filename'] + if not filename or asset['atype'] != 'text/javascript': + continue + with open(filename, 'rb') as fp: + if RE_ONLY.search(fp.read().decode('utf-8')): + self.fail("`QUnit.only()` used in file %r" % asset['url']) + + +@odoo.tests.tagged('post_install', '-at_install') +class MobileWebSuite(odoo.tests.HttpCase): + browser_size = '375x667' + + def test_mobile_js(self): + # webclient mobile test suite + self.browser_js('/web/tests/mobile?mod=web&failfast', "", "", login='admin', timeout=1800) diff --git a/addons/web/tests/test_menu.py b/addons/web/tests/test_menu.py new file mode 100644 index 00000000..d60721c1 --- /dev/null +++ b/addons/web/tests/test_menu.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo.tests.common import BaseCase +from ..controllers import main + + +class ActionMungerTest(BaseCase): + def test_actual_treeview(self): + action = { + "views": [[False, "tree"], [False, "form"], + [False, "calendar"]], + "view_type": "tree", + "view_id": False, + "view_mode": "tree,form,calendar" + } + changed = action.copy() + del action['view_type'] + main.fix_view_modes(changed) + + self.assertEqual(changed, action) + + def test_list_view(self): + action = { + "views": [[False, "tree"], [False, "form"], + [False, "calendar"]], + "view_type": "form", + "view_id": False, + "view_mode": "tree,form,calendar" + } + main.fix_view_modes(action) + + self.assertEqual(action, { + "views": [[False, "list"], [False, "form"], + [False, "calendar"]], + "view_id": False, + "view_mode": "list,form,calendar" + }) + + def test_redundant_views(self): + + action = { + "views": [[False, "tree"], [False, "form"], + [False, "calendar"], [42, "tree"]], + "view_type": "form", + "view_id": False, + "view_mode": "tree,form,calendar" + } + main.fix_view_modes(action) + + self.assertEqual(action, { + "views": [[False, "list"], [False, "form"], + [False, "calendar"], [42, "list"]], + "view_id": False, + "view_mode": "list,form,calendar" + }) diff --git a/addons/web/tests/test_read_progress_bar.py b/addons/web/tests/test_read_progress_bar.py new file mode 100644 index 00000000..ab5eaf1c --- /dev/null +++ b/addons/web/tests/test_read_progress_bar.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo.tests import common + + +@common.tagged('post_install', '-at_install') +class TestReadProgressBar(common.TransactionCase): + """Test for read_progress_bar""" + + def setUp(self): + super(TestReadProgressBar, self).setUp() + self.Model = self.env['res.partner'] + + 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 + """ + context = {"lang": "en_US"} + groupby = "date:week" + self.Model.create({'date': '2021-05-02', 'name': "testWeekGrouping_first"}) # Sunday + self.Model.create({'date': '2021-05-09', 'name': "testWeekGrouping_second"}) # Sunday + progress_bar = { + 'field': 'name', + 'colors': { + "testWeekGrouping_first": 'success', + "testWeekGrouping_second": 'danger', + } + } + + groups = self.Model.with_context(context).read_group( + [('name', "like", "testWeekGrouping%")], fields=['date', 'name'], groupby=[groupby]) + progressbars = self.Model.with_context(context).read_progress_bar( + [('name', "like", "testWeekGrouping%")], group_by=groupby, progress_bar=progress_bar) + self.assertEqual(len(groups), 2) + self.assertEqual(len(progressbars), 2) + + # format the read_progress_bar result to get a dictionary under this format : {record_name: group_name} + # original format (after read_progress_bar) is : {group_name: {record_name: count}} + pg_groups = { + next(record_name for record_name, count in data.items() if count): group_name \ + for group_name, data in progressbars.items() + } + + self.assertEqual(groups[0][groupby], pg_groups["testWeekGrouping_first"]) + self.assertEqual(groups[1][groupby], pg_groups["testWeekGrouping_second"]) + + def test_simple(self): + model = self.env['ir.model'].create({ + 'model': 'x_progressbar', + 'name': 'progress_bar', + 'field_id': [ + (0, 0, { + 'field_description': 'Country', + 'name': 'x_country_id', + 'ttype': 'many2one', + 'relation': 'res.country', + }), + (0, 0, { + 'field_description': 'Date', + 'name': 'x_date', + 'ttype': 'date', + }), + (0, 0, { + 'field_description': 'State', + 'name': 'x_state', + 'ttype': 'selection', + 'selection': "[('foo', 'Foo'), ('bar', 'Bar'), ('baz', 'Baz')]", + }), + ], + }) + + c1, c2, c3 = self.env['res.country'].search([], limit=3) + + self.env['x_progressbar'].create([ + # week 21 + {'x_country_id': c1.id, 'x_date': '2021-05-20', 'x_state': 'foo'}, + {'x_country_id': c1.id, 'x_date': '2021-05-21', 'x_state': 'foo'}, + {'x_country_id': c1.id, 'x_date': '2021-05-22', 'x_state': 'foo'}, + {'x_country_id': c1.id, 'x_date': '2021-05-23', 'x_state': 'bar'}, + # week 22 + {'x_country_id': c1.id, 'x_date': '2021-05-24', 'x_state': 'baz'}, + {'x_country_id': c2.id, 'x_date': '2021-05-25', 'x_state': 'foo'}, + {'x_country_id': c2.id, 'x_date': '2021-05-26', 'x_state': 'bar'}, + {'x_country_id': c2.id, 'x_date': '2021-05-27', 'x_state': 'bar'}, + {'x_country_id': c2.id, 'x_date': '2021-05-28', 'x_state': 'baz'}, + {'x_country_id': c2.id, 'x_date': '2021-05-29', 'x_state': 'baz'}, + {'x_country_id': c3.id, 'x_date': '2021-05-30', 'x_state': 'foo'}, + # week 23 + {'x_country_id': c3.id, 'x_date': '2021-05-31', 'x_state': 'foo'}, + {'x_country_id': c3.id, 'x_date': '2021-06-01', 'x_state': 'baz'}, + {'x_country_id': c3.id, 'x_date': '2021-06-02', 'x_state': 'baz'}, + {'x_country_id': c3.id, 'x_date': '2021-06-03', 'x_state': 'baz'}, + ]) + + progress_bar = { + 'field': 'x_state', + 'colors': {'foo': 'success', 'bar': 'warning', 'baz': 'danger'}, + } + result = self.env['x_progressbar'].read_progress_bar([], 'x_country_id', progress_bar) + self.assertEqual(result, { + c1.display_name: {'foo': 3, 'bar': 1, 'baz': 1}, + c2.display_name: {'foo': 1, 'bar': 2, 'baz': 2}, + c3.display_name: {'foo': 2, 'bar': 0, 'baz': 3}, + }) + + # check date aggregation and format + result = self.env['x_progressbar'].read_progress_bar([], 'x_date:week', progress_bar) + self.assertEqual(result, { + 'W21 2021': {'foo': 3, 'bar': 1, 'baz': 0}, + 'W22 2021': {'foo': 2, 'bar': 2, 'baz': 3}, + 'W23 2021': {'foo': 1, 'bar': 0, 'baz': 3}, + }) + + # add a computed field on model + model.write({'field_id': [ + (0, 0, { + 'field_description': 'Related State', + 'name': 'x_state_computed', + 'ttype': 'selection', + 'selection': "[('foo', 'Foo'), ('bar', 'Bar'), ('baz', 'Baz')]", + 'compute': "for rec in self: rec['x_state_computed'] = rec.x_state", + 'depends': 'x_state', + 'readonly': True, + 'store': False, + }), + ]}) + + progress_bar = { + 'field': 'x_state_computed', + 'colors': {'foo': 'success', 'bar': 'warning', 'baz': 'danger'}, + } + result = self.env['x_progressbar'].read_progress_bar([], 'x_country_id', progress_bar) + self.assertEqual(result, { + c1.display_name: {'foo': 3, 'bar': 1, 'baz': 1}, + c2.display_name: {'foo': 1, 'bar': 2, 'baz': 2}, + c3.display_name: {'foo': 2, 'bar': 0, 'baz': 3}, + }) + + result = self.env['x_progressbar'].read_progress_bar([], 'x_date:week', progress_bar) + self.assertEqual(result, { + 'W21 2021': {'foo': 3, 'bar': 1, 'baz': 0}, + 'W22 2021': {'foo': 2, 'bar': 2, 'baz': 3}, + 'W23 2021': {'foo': 1, 'bar': 0, 'baz': 3}, + }) diff --git a/addons/web/tests/test_serving_base.py b/addons/web/tests/test_serving_base.py new file mode 100644 index 00000000..d7d83c25 --- /dev/null +++ b/addons/web/tests/test_serving_base.py @@ -0,0 +1,1002 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import random +import re +from unittest.mock import patch +import textwrap +from datetime import datetime, timedelta +from lxml import etree +import logging + +from odoo.tests.common import BaseCase, tagged +from odoo.tools import topological_sort +from odoo.addons.web.controllers.main import HomeStaticTemplateHelpers + +_logger = logging.getLogger(__name__) + +def sample(population): + return random.sample( + population, + random.randint(0, min(len(population), 5))) + + +class TestModulesLoading(BaseCase): + def setUp(self): + self.mods = [str(i) for i in range(1000)] + + def test_topological_sort(self): + random.shuffle(self.mods) + modules = [ + (k, sample(self.mods[:i])) + for i, k in enumerate(self.mods)] + random.shuffle(modules) + ms = dict(modules) + + seen = set() + sorted_modules = topological_sort(ms) + for module in sorted_modules: + deps = ms[module] + self.assertGreaterEqual( + seen, set(deps), + 'Module %s (index %d), ' \ + 'missing dependencies %s from loaded modules %s' % ( + module, sorted_modules.index(module), deps, seen + )) + seen.add(module) + + +class TestStaticInheritanceCommon(BaseCase): + + def setUp(self): + super(TestStaticInheritanceCommon, self).setUp() + # output is "manifest_glob" return + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ('module_2_file_1', None, 'module_2'), + ] + + self.template_files = { + 'module_1_file_1': b""" + <templates id="template" xml:space="preserve"> + <form t-name="template_1_1" random-attr="gloria"> + <div>At first I was afraid</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + <t t-name="template_1_2"> + <div>And I grew strong</div> + </t> + </templates> + """, + + 'module_2_file_1': b""" + <templates id="template" xml:space="preserve"> + <form t-name="template_2_1" t-inherit="module_1.template_1_1" t-inherit-mode="primary"> + <xpath expr="//div[1]" position="after"> + <div>I was petrified</div> + </xpath> + <xpath expr="//div[2]" position="after"> + <div>But then I spent so many nights thinking how you did me wrong</div> + </xpath> + </form> + <div t-name="template_2_2"> + <div>And I learned how to get along</div> + </div> + <form t-inherit="module_1.template_1_2" t-inherit-mode="extension"> + <xpath expr="//div[1]" position="after"> + <div>And I learned how to get along</div> + </xpath> + </form> + </templates> + """, + } + self._set_patchers() + self._toggle_patchers('start') + self._reg_replace_ws = r"\s|\t" + + def tearDown(self): + super(TestStaticInheritanceCommon, self).tearDown() + self._toggle_patchers('stop') + + # Custom Assert + def assertXMLEqual(self, output, expected): + self.assertTrue(output) + self.assertTrue(expected) + output = textwrap.dedent(output.decode('UTF-8')).strip() + output = re.sub(self._reg_replace_ws, '', output) + + expected = textwrap.dedent(expected.decode('UTF-8')).strip() + expected = re.sub(self._reg_replace_ws, '', expected) + self.assertEqual(output, expected) + + # Private methods + def _get_module_names(self): + return ','.join([glob[2] for glob in self.modules]) + + def _set_patchers(self): + def _patched_for_manifest_glob(*args, **kwargs): + # Ordered by module + return self.modules + + def _patch_for_read_addon_file(*args, **kwargs): + return self.template_files[args[1]] + + self.patchers = [ + patch.object(HomeStaticTemplateHelpers, '_manifest_glob', _patched_for_manifest_glob), + patch.object(HomeStaticTemplateHelpers, '_read_addon_file', _patch_for_read_addon_file), + ] + + def _toggle_patchers(self, mode): + self.assertTrue(mode in ('start', 'stop')) + for p in self.patchers: + getattr(p, mode)() + + +@tagged('static_templates') +class TestStaticInheritance(TestStaticInheritanceCommon): + # Actual test cases + def test_static_inheritance_01(self): + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <form t-name="template_1_1" random-attr="gloria"> + <div>At first I was afraid</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + <t t-name="template_1_2"> + <div>And I grew strong</div> + <!-- Modified by anonymous_template_2 from module_2 --> + <div>And I learned how to get along</div> + </t> + <form t-name="template_2_1" random-attr="gloria"> + <div>At first I was afraid</div> + <div>I was petrified</div> + <div>But then I spent so many nights thinking how you did me wrong</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + <div t-name="template_2_2"> + <div>And I learned how to get along</div> + </div> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_static_inheritance_02(self): + self.template_files = { + 'module_1_file_1': b''' + <templates id="template" xml:space="preserve"> + <form t-name="template_1_1" random-attr="gloria"> + <div>At first I was afraid</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + <form t-name="template_1_2" t-inherit="template_1_1" added="true"> + <xpath expr="//div[1]" position="after"> + <div>I was petrified</div> + </xpath> + </form> + </templates> + ''' + } + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ] + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <form t-name="template_1_1" random-attr="gloria"> + <div>At first I was afraid</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + <form t-name="template_1_2" random-attr="gloria" added="true"> + <div>At first I was afraid</div> + <div>I was petrified</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_static_inheritance_03(self): + self.maxDiff = None + self.template_files = { + 'module_1_file_1': b''' + <templates id="template" xml:space="preserve"> + <form t-name="template_1_1"> + <div>At first I was afraid</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + <form t-name="template_1_2" t-inherit="template_1_1" added="true"> + <xpath expr="//div[1]" position="after"> + <div>I was petrified</div> + </xpath> + </form> + <form t-name="template_1_3" t-inherit="template_1_2" added="false" other="here"> + <xpath expr="//div[2]" position="replace"/> + </form> + </templates> + ''' + } + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ] + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <form t-name="template_1_1"> + <div>At first I was afraid</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + <form t-name="template_1_2" added="true"> + <div>At first I was afraid</div> + <div>I was petrified</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + <form t-name="template_1_3" added="false" other="here"> + <div>At first I was afraid</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_static_inheritance_in_same_module(self): + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ('module_1_file_2', None, 'module_1'), + ] + + self.template_files = { + 'module_1_file_1': b''' + <templates id="template" xml:space="preserve"> + <form t-name="template_1_1"> + <div>At first I was afraid</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + </templates> + ''', + + 'module_1_file_2': b''' + <templates id="template" xml:space="preserve"> + <form t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="primary"> + <xpath expr="//div[1]" position="after"> + <div>I was petrified</div> + </xpath> + </form> + </templates> + ''' + } + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <form t-name="template_1_1"> + <div>At first I was afraid</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + <form t-name="template_1_2"> + <div>At first I was afraid</div> + <div>I was petrified</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_static_inheritance_in_same_file(self): + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ] + + self.template_files = { + 'module_1_file_1': b''' + <templates id="template" xml:space="preserve"> + <form t-name="template_1_1"> + <div>At first I was afraid</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + <form t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="primary"> + <xpath expr="//div[1]" position="after"> + <div>I was petrified</div> + </xpath> + </form> + </templates> + ''', + } + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <form t-name="template_1_1"> + <div>At first I was afraid</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + <form t-name="template_1_2"> + <div>At first I was afraid</div> + <div>I was petrified</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_static_inherit_extended_template(self): + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ] + self.template_files = { + 'module_1_file_1': b''' + <templates id="template" xml:space="preserve"> + <form t-name="template_1_1"> + <div>At first I was afraid</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + <form t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="extension"> + <xpath expr="//div[1]" position="after"> + <div>I was petrified</div> + </xpath> + </form> + <form t-name="template_1_3" t-inherit="template_1_1" t-inherit-mode="primary"> + <xpath expr="//div[3]" position="after"> + <div>But then I spent so many nights thinking how you did me wrong</div> + </xpath> + </form> + </templates> + ''', + } + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <form t-name="template_1_1"> + <div>At first I was afraid</div> + <!-- Modified by template_1_2 from module_1 --> + <div>I was petrified</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + <form t-name="template_1_3"> + <div>At first I was afraid</div> + <div>I was petrified</div> + <div>Kept thinking I could never live without you by my side</div> + <div>But then I spent so many nights thinking how you did me wrong</div> + </form> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_sibling_extension(self): + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ('module_2_file_1', None, 'module_2'), + ('module_3_file_1', None, 'module_3'), + ] + self.template_files = { + 'module_1_file_1': b''' + <templates id="template" xml:space="preserve"> + <form t-name="template_1_1"> + <div>I am a man of constant sorrow</div> + <div>I've seen trouble all my days</div> + </form> + </templates> + ''', + + 'module_2_file_1': b''' + <templates id="template" xml:space="preserve"> + <form t-name="template_2_1" t-inherit="module_1.template_1_1" t-inherit-mode="extension"> + <xpath expr="//div[1]" position="after"> + <div>In constant sorrow all through his days</div> + </xpath> + </form> + </templates> + ''', + + 'module_3_file_1': b''' + <templates id="template" xml:space="preserve"> + <form t-name="template_3_1" t-inherit="module_1.template_1_1" t-inherit-mode="extension"> + <xpath expr="//div[2]" position="after"> + <div>Oh Brother !</div> + </xpath> + </form> + </templates> + ''' + } + + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <form t-name="template_1_1"> + <div>I am a man of constant sorrow</div> + <!-- Modified by template_2_1 from module_2 --> + <div>In constant sorrow all through his days</div> + <!-- Modified by template_3_1 from module_3 --> + <div>Oh Brother !</div> + <div>I've seen trouble all my days</div> + </form> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_static_misordered_modules(self): + self.modules.reverse() + with self.assertRaises(ValueError) as ve: + HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + + self.assertEqual( + str(ve.exception), + 'Module module_1 not loaded or inexistent, or templates of addon being loaded (module_2) are misordered' + ) + + def test_static_misordered_templates(self): + self.template_files['module_2_file_1'] = b""" + <templates id="template" xml:space="preserve"> + <form t-name="template_2_1" t-inherit="module_2.template_2_2" t-inherit-mode="primary"> + <xpath expr="//div[1]" position="after"> + <div>I was petrified</div> + </xpath> + </form> + <div t-name="template_2_2"> + <div>And I learned how to get along</div> + </div> + </templates> + """ + with self.assertRaises(ValueError) as ve: + HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + + self.assertEqual( + str(ve.exception), + 'No template found to inherit from. Module module_2 and template name template_2_2' + ) + + def test_replace_in_debug_mode(self): + """ + Replacing a template's meta definition in place doesn't keep the original attrs of the template + """ + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ] + self.template_files = { + 'module_1_file_1': b""" + <templates id="template" xml:space="preserve"> + <form t-name="template_1_1" random-attr="gloria"> + <div>At first I was afraid</div> + </form> + <t t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="extension"> + <xpath expr="." position="replace"> + <div overriden-attr="overriden">And I grew strong</div> + </xpath> + </t> + </templates> + """, + } + + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <div overriden-attr="overriden" t-name="template_1_1"> + <!-- Modified by template_1_2 from module_1 -->And I grew strong + </div> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_replace_in_debug_mode2(self): + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ] + self.template_files = { + 'module_1_file_1': b""" + <templates id="template" xml:space="preserve"> + <form t-name="template_1_1" random-attr="gloria"> + <div>At first I was afraid</div> + </form> + <t t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="extension"> + <xpath expr="." position="replace"> + <div> + And I grew strong + <p>And I learned how to get along</p> + And so you're back + </div> + </xpath> + </t> + </templates> + """, + } + + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <div t-name="template_1_1"> + <!-- Modified by template_1_2 from module_1 --> + And I grew strong + <p>And I learned how to get along</p> + And so you're back + </div> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_replace_in_debug_mode3(self): + """Text outside of a div which will replace a whole template + becomes outside of the template + This doesn't mean anything in terms of the business of template inheritance + But it is in the XPATH specs""" + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ] + self.template_files = { + 'module_1_file_1': b""" + <templates id="template" xml:space="preserve"> + <form t-name="template_1_1" random-attr="gloria"> + <div>At first I was afraid</div> + </form> + <t t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="extension"> + <xpath expr="." position="replace"> + <div> + And I grew strong + <p>And I learned how to get along</p> + </div> + And so you're back + </xpath> + </t> + </templates> + """, + } + + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <div t-name="template_1_1"> + <!-- Modified by template_1_2 from module_1 --> + And I grew strong + <p>And I learned how to get along</p> + </div> + And so you're back + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_replace_root_node_tag(self): + """ + Root node IS targeted by //NODE_TAG in xpath + """ + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ] + self.template_files = { + 'module_1_file_1': b""" + <templates id="template" xml:space="preserve"> + <form t-name="template_1_1" random-attr="gloria"> + <div>At first I was afraid</div> + <form>Inner Form</form> + </form> + <t t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="extension"> + <xpath expr="//form" position="replace"> + <div> + Form replacer + </div> + </xpath> + </t> + </templates> + """, + } + + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <div t-name="template_1_1"> + <!-- Modified by template_1_2 from module_1 --> + Form replacer + </div> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_replace_root_node_tag_in_primary(self): + """ + Root node IS targeted by //NODE_TAG in xpath + """ + self.maxDiff = None + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ] + self.template_files = { + 'module_1_file_1': b""" + <templates id="template" xml:space="preserve"> + <form t-name="template_1_1" random-attr="gloria"> + <div>At first I was afraid</div> + <form>Inner Form</form> + </form> + <form t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="primary"> + <xpath expr="//form" position="replace"> + <div>Form replacer</div> + </xpath> + </form> + </templates> + """, + } + + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <form t-name="template_1_1" random-attr="gloria"> + <div>At first I was afraid</div> + <form>Inner Form</form> + </form> + <div t-name="template_1_2"> + Form replacer + </div> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_inherit_primary_replace_debug(self): + """ + The inheriting template has got both its own defining attrs + and new ones if one is to replace its defining root node + """ + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ] + self.template_files = { + 'module_1_file_1': b""" + <templates id="template" xml:space="preserve"> + <form t-name="template_1_1" random-attr="gloria"> + <div>At first I was afraid</div> + </form> + <t t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="primary"> + <xpath expr="." position="replace"> + <div overriden-attr="overriden"> + And I grew strong + <p>And I learned how to get along</p> + </div> + </xpath> + </t> + </templates> + """, + } + + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <form t-name="template_1_1" random-attr="gloria"> + <div>At first I was afraid</div> + </form> + <div overriden-attr="overriden" t-name="template_1_2"> + And I grew strong + <p>And I learned how to get along</p> + </div> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_replace_in_nodebug_mode1(self): + """Comments already in the arch are ignored""" + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ] + self.template_files = { + 'module_1_file_1': b""" + <templates id="template" xml:space="preserve"> + <form t-name="template_1_1" random-attr="gloria"> + <div>At first I was afraid</div> + </form> + <t t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="extension"> + <xpath expr="." position="replace"> + <div> + <!-- Random Comment --> + And I grew strong + <p>And I learned how to get along</p> + And so you're back + </div> + </xpath> + </t> + </templates> + """, + } + + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=False) + expected = b""" + <templates> + <div t-name="template_1_1"> + And I grew strong + <p>And I learned how to get along</p> + And so you're back + </div> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_inherit_from_dotted_tname_1(self): + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ] + self.template_files = { + 'module_1_file_1': b""" + <templates id="template" xml:space="preserve"> + <form t-name="module_1.template_1_1.dot" random-attr="gloria"> + <div>At first I was afraid</div> + </form> + <t t-name="template_1_2" t-inherit="template_1_1.dot" t-inherit-mode="primary"> + <xpath expr="." position="replace"> + <div overriden-attr="overriden"> + And I grew strong + <p>And I learned how to get along</p> + </div> + </xpath> + </t> + </templates> + """, + } + + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <form t-name="module_1.template_1_1.dot" random-attr="gloria"> + <div>At first I was afraid</div> + </form> + <div overriden-attr="overriden" t-name="template_1_2"> + And I grew strong + <p>And I learned how to get along</p> + </div> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_inherit_from_dotted_tname_2(self): + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ] + self.template_files = { + 'module_1_file_1': b""" + <templates id="template" xml:space="preserve"> + <form t-name="template_1_1.dot" random-attr="gloria"> + <div>At first I was afraid</div> + </form> + <t t-name="template_1_2" t-inherit="template_1_1.dot" t-inherit-mode="primary"> + <xpath expr="." position="replace"> + <div overriden-attr="overriden"> + And I grew strong + <p>And I learned how to get along</p> + </div> + </xpath> + </t> + </templates> + """, + } + + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <form t-name="template_1_1.dot" random-attr="gloria"> + <div>At first I was afraid</div> + </form> + <div overriden-attr="overriden" t-name="template_1_2"> + And I grew strong + <p>And I learned how to get along</p> + </div> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_inherit_from_dotted_tname_2bis(self): + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ] + self.template_files = { + 'module_1_file_1': b""" + <templates id="template" xml:space="preserve"> + <form t-name="template_1_1.dot" random-attr="gloria"> + <div>At first I was afraid</div> + </form> + <t t-name="template_1_2" t-inherit="module_1.template_1_1.dot" t-inherit-mode="primary"> + <xpath expr="." position="replace"> + <div overriden-attr="overriden"> + And I grew strong + <p>And I learned how to get along</p> + </div> + </xpath> + </t> + </templates> + """, + } + + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <form t-name="template_1_1.dot" random-attr="gloria"> + <div>At first I was afraid</div> + </form> + <div overriden-attr="overriden" t-name="template_1_2"> + And I grew strong + <p>And I learned how to get along</p> + </div> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_inherit_from_dotted_tname_2ter(self): + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ] + self.template_files = { + 'module_1_file_1': b""" + <templates id="template" xml:space="preserve"> + <form t-name="module_1" random-attr="gloria"> + <div>At first I was afraid</div> + </form> + <t t-name="template_1_2" t-inherit="module_1" t-inherit-mode="primary"> + <xpath expr="." position="replace"> + <div overriden-attr="overriden"> + And I grew strong + <p>And I learned how to get along</p> + </div> + </xpath> + </t> + </templates> + """, + } + + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <form t-name="module_1" random-attr="gloria"> + <div>At first I was afraid</div> + </form> + <div overriden-attr="overriden" t-name="template_1_2"> + And I grew strong + <p>And I learned how to get along</p> + </div> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_inherit_from_dotted_tname_3(self): + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ('module_2_file_1', None, 'module_2'), + ] + self.template_files = { + 'module_1_file_1': b""" + <templates id="template" xml:space="preserve"> + <form t-name="module_1.template_1_1.dot" random-attr="gloria"> + <div>At first I was afraid</div> + </form> + </templates> + """, + + 'module_2_file_1': b""" + <templates id="template" xml:space="preserve"> + <t t-name="template_2_1" t-inherit="module_1.template_1_1.dot" t-inherit-mode="primary"> + <xpath expr="." position="replace"> + <div overriden-attr="overriden"> + And I grew strong + <p>And I learned how to get along</p> + </div> + </xpath> + </t> + </templates> + """ + } + + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <form t-name="module_1.template_1_1.dot" random-attr="gloria"> + <div>At first I was afraid</div> + </form> + <div overriden-attr="overriden" t-name="template_2_1"> + And I grew strong + <p>And I learned how to get along</p> + </div> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + +@tagged('-standard', 'static_templates_performance') +class TestStaticInheritancePerformance(TestStaticInheritanceCommon): + def _sick_script(self, nMod, nFilePerMod, nTemplatePerFile, stepInheritInModule=2, stepInheritPreviousModule=3): + """ + Make a sick amount of templates to test perf + nMod modules + each module: has nFilesPerModule files, each of which contains nTemplatePerFile templates + """ + self.modules = [] + self.template_files = {} + number_templates = 0 + for m in range(nMod): + for f in range(nFilePerMod): + mname = 'mod_%s' % m + fname = 'mod_%s_file_%s' % (m, f) + self.modules.append((fname, None, mname)) + + _file = '<templates id="template" xml:space="preserve">' + + for t in range(nTemplatePerFile): + _template = '' + if t % stepInheritInModule or t % stepInheritPreviousModule or t == 0: + _template += """ + <div t-name="template_%(t_number)s_mod_%(m_number)s"> + <div>Parent</div> + </div> + """ + + elif not t % stepInheritInModule and t >= 1: + _template += """ + <div t-name="template_%(t_number)s_mod_%(m_number)s" + t-inherit="template_%(t_inherit)s_mod_%(m_number)s" + t-inherit-mode="primary"> + <xpath expr="/div/div[1]" position="before"> + <div>Sick XPath</div> + </xpath> + </div> + """ + + elif not t % stepInheritPreviousModule and m >= 1: + _template += """ + <div t-name="template_%(t_number)s_mod_%(m_number)s" + t-inherit="mod_%(m_module_inherit)s.template_%(t_module_inherit)s_mod_%(m_module_inherit)s" + t-inherit-mode="primary"> + <xpath expr="/div/div[1]" position="inside"> + <div>Mental XPath</div> + </xpath> + </div> + """ + if _template: + number_templates += 1 + + _template_number = 1000 * f + t + _file += _template % { + 't_number': _template_number, + 'm_number': m, + 't_inherit': _template_number - 1, + 't_module_inherit': _template_number, + 'm_module_inherit': m - 1, + } + _file += '</templates>' + + self.template_files[fname] = _file.encode() + self.assertEqual(number_templates, nMod * nFilePerMod * nTemplatePerFile) + + def test_static_templates_treatment_linearity(self): + # With 2500 templates for starters + nMod, nFilePerMod, nTemplatePerFile = 50, 5, 10 + self._sick_script(nMod, nFilePerMod, nTemplatePerFile) + + before = datetime.now() + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + after = datetime.now() + delta2500 = after - before + _logger.runbot('Static Templates Inheritance: 2500 templates treated in %s seconds' % delta2500.total_seconds()) + + whole_tree = etree.fromstring(contents) + self.assertEqual(len(whole_tree), nMod * nFilePerMod * nTemplatePerFile) + + # With 25000 templates next + nMod, nFilePerMod, nTemplatePerFile = 50, 5, 100 + self._sick_script(nMod, nFilePerMod, nTemplatePerFile) + + before = datetime.now() + HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + after = datetime.now() + delta25000 = after - before + + time_ratio = delta25000.total_seconds() / delta2500.total_seconds() + _logger.runbot('Static Templates Inheritance: 25000 templates treated in %s seconds' % delta25000.total_seconds()) + _logger.runbot('Static Templates Inheritance: Computed linearity ratio: %s' % time_ratio) + self.assertLessEqual(time_ratio, 12) |
