import random
import textwrap
from odoo.http import _request_stack
from odoo.tests.common import TransactionCase, tagged
from odoo.tools import DotDict
from odoo.addons.web_studio.controllers.main import WebStudioController
@tagged('web_studio_normalization')
class TestViewNormalization(TransactionCase):
maxDiff = None
def setUp(self):
super(TestViewNormalization, self).setUp()
random.seed('https://youtu.be/tFjNH9l6-sQ')
self.session = DotDict({'debug': ''})
_request_stack.push(self)
self.base_view = self.env.ref('base.view_partner_form')
self.gantt_view = self.env['ir.ui.view'].create({
'arch_base':
"""
""",
'model': 'res.partner',
'type': 'gantt',
})
self.view = self.base_view.create({
'arch_base':
"""
""",
'model': 'res.partner'})
self.studio_controller = WebStudioController()
def _test_view_normalization(self, original, expected, view='form'):
if view == 'gantt':
view = self.gantt_view
else:
view = self.view
original = original and textwrap.dedent(original)
self.studio_controller._set_studio_view(view, original)
studio_view = self.studio_controller._get_studio_view(view)
studio_view = studio_view.with_context(load_all_views=True)
normalized = studio_view.normalize()
self.studio_controller._set_studio_view(view, normalized)
self.env[self.view.model].with_context(studio=True, load_all_views=True).get_view(view.id, view.type)
normalized = normalized and normalized.strip()
expected = expected and textwrap.dedent(expected).strip()
self.assertEqual(normalized, expected)
# Flatten all xpath that target nodes added by studio itself
def test_view_normalization_00(self):
self._test_view_normalization("""
""", """
""")
# Delete children of deleted nodes and reanchor siblings
def test_view_normalization_01(self):
self._test_view_normalization("""
""", """
""")
# When there is no more sibling, we need to reanchor on the parent
def test_view_normalization_02(self):
self._test_view_normalization("""
""", """
""")
# When a field is deleted, other xpath that targets it need to be reanchored.
def test_view_normalization_03(self):
self._test_view_normalization("""
""", """
""")
# If there is nothing left in the studio view, delete it.
def test_view_normalization_04(self):
expected = ''
self._test_view_normalization("""
""", expected)
studio_view = self.studio_controller._set_studio_view(self.view, expected)
studio_view = self.studio_controller._get_studio_view(self.view)
self.assertEqual(len(studio_view), 0)
# An after can become a replace if the following sibling has been removed.
def test_view_normalization_05(self):
self._test_view_normalization("""
""", """
""")
# Multiple additions of fields should not appear if it was deleted
def test_view_normalization_06(self):
self._test_view_normalization("""
""", """
""")
# Consecutive xpaths around a field that was moved away can be merged.
def test_view_normalization_07(self):
self._test_view_normalization("""
""", """
""")
# A field that was added, then moved then deleted should not appear.
def test_view_normalization_08(self):
self._test_view_normalization("""
""", """
""")
# Fields that were added then removed should not appear in the view at all,
# and every other xpath that was using it should be reanchored elsewhere.
def test_view_normalization_09(self):
self._test_view_normalization("""
""", """
""")
# When two fields are added after a given field, the second one will appear
# before the first one.
def test_view_normalization_10(self):
self._test_view_normalization("""
""", """
""")
# When we add a field after another one and replace the sibling of this one,
# everything could be done in a single replace on the sibling node.
def test_view_normalization_11(self):
self._test_view_normalization("""
""", """
""")
# When closest previous node has no name, the closest next node should be
# used instead, provided it has a name. Also, attributes need to be handled
# in a single xpath and alphabetically sorted.
def test_view_normalization_12(self):
self._test_view_normalization("""
PAGE 1 ZZZPAGE 1 HELP
""", """
PAGE 1 HELPPAGE 1 ZZZ
""")
# Changing an already existing attribute will generate a remove line for
# the previous value and an addition line for the new value. The removing
# line should not close the attributes xpath, both attributes need to be
# redefined in a single xpath.
def test_view_normalization_13(self):
self._test_view_normalization("""
PAGE 1PAGE 1 HELP
""", """
PAGE 1 HELPPAGE 1
""")
def test_view_normalization_14(self):
# There is already a chatter on res.partner.form view, which is why
# the resulting xpath is /div instead of /sheet.
self._test_view_normalization("""
""", """
""")
# Don't break on text with newlines
def test_view_normalization_15(self):
# New lines in text used to create a new line in the diff, desynchronizing
# the diff lines and the tree elements iterator
self._test_view_normalization("""
THIS
IS
A MULTILINE
TITLE
""", """
THIS
IS
A MULTILINE
TITLE
""")
# Test anchoring next to studio fields
def test_view_normalization_16(self):
self._test_view_normalization("""
""", """
""")
# Test replace of last element in arch
def test_view_normalization_17(self):
self._test_view_normalization("""
""", """
""")
# Replace an existing element then add it back in but somewhere before
# its original position
def test_view_normalization_18(self):
self._test_view_normalization("""
""", """
""")
# Delete an existing element, then replace another element with the deleted
# element further down
def test_view_normalization_19(self):
self._test_view_normalization("""
""", """
""")
# Delete an existing element, then replace another element before the
# original element with the latter
def test_view_normalization_20(self):
self._test_view_normalization("""
""", """
""")
# template fields are appended to the templates, not to the kanban itself
def test_view_normalization_21(self):
self._test_view_normalization("""
""", """
""")
# adding kanban and template fields while using absolute xpaths
def test_view_normalization_22(self):
self._test_view_normalization("""
""")
# Correctly calculate the expr on flat views
def test_view_normalization_23(self):
self._test_view_normalization("""
date
""", """
date
""", 'gantt')
# test that unnamed groups/pages are given a pseudo-random name attribute
def test_view_normalization_24(self):
random.seed("https://i.redd.it/pnyr50lf0jh01.png")
self._test_view_normalization("""
hello world!
foo bar baz
spam eggs bacon
""", """
hello world!
foo bar baz
spam eggs bacon
""")
random.seed()
# test that unnamed pages are given a pseudo-random name attribute
def test_view_normalization_25(self):
self._test_view_normalization("""
hello world!
foo bar baz
spam eggs bacon
""", """
hello world!
foo bar baz
spam eggs bacon
""")
# Adjacent addition/removal changes ends with correct xpath
def test_view_normalization_26(self):
self._test_view_normalization("""
""", """
""")
# Test descendants equivalent nodes does not change children
def test_view_normalization_27(self):
self._test_view_normalization("""
""", """
""")
# Test descendants equivalent nodes does not change children with unwrapped field
def test_view_normalization_28(self):
self._test_view_normalization("""
""", """
""")
# Move an existing element (after its position)
def test_view_normalization_29_1(self):
self._test_view_normalization("""
""", """
""")
# Move an existing element (before its position)
def test_view_normalization_29_2(self):
self._test_view_normalization("""
""", """
""")
# Move two existing elements (after its position)
def test_view_normalization_30_1(self):
self._test_view_normalization("""
""", """
""")
# Move two existing elements (before its position)
def test_view_normalization_30_2(self):
self._test_view_normalization("""
""", """
""")
# Move two consequentive existing elements
def test_view_normalization_30_3(self):
self._test_view_normalization("""
""", """
""")
# xpath based on a moved element
def test_view_normalization_31(self):
self._test_view_normalization("""
""", """
""")
# Move an existing element and its attributes
def test_view_normalization_32(self):
self._test_view_normalization("""
Kikou
""", """
Kikou
""")
# Move fields and one of them needs to generate an absolute xpath
def test_view_normalization_33(self):
self._test_view_normalization("""
""", """
""")
def test_view_normalization_34(self):
self._test_view_normalization("""
""", """
""")
def test_view_normalization_29(self):
self.view = self.base_view.create({
'arch_base':
'''
''',
'model': 'res.partner',
'type': 'form'})
self._test_view_normalization(
'''
''')
# Added line adjacent to comment should be ignored when xpathing
def test_view_normalization_36(self):
self.view = self.base_view.create({
'arch_base':
'''
''',
'model': 'res.partner',
'type': 'form'})
# end result: orator:/hello/cruel/world/!?
self._test_view_normalization(
'''
''',
'''
''')
# Identical fields in same xpath do not have mistaken identity
def test_view_normalization_37(self):
self.view = self.base_view.create({
'arch_base':
'''
''',
'model': 'res.partner',
'type': 'form'})
self._test_view_normalization(
'''
''',
'''
''')
def test_view_normalization_37_2(self):
"""Button have a name which is not unique"""
self.view = self.base_view.create({
'arch_base':
'''
''',
'model': 'res.partner',
'type': 'form'})
self._test_view_normalization(
'''
on the bayou
''',
'''
on the bayou
''')
def tearDown(self):
super(TestViewNormalization, self).tearDown()
random.seed()
_request_stack.pop()