Files
odoo_source/odoo/addons/test_convert/tests/test_convert.py
T
Renaud Thiry f7e5fd7aab [FIX] base: use html method from lxml
Prior to this change the convert tool did could mangle
the value of html fields when importing static data.

This is because html does not support 'self-closing'
except for HTML5 where it is allowed on void elements (such as img).

Browsers will assume that they are opening tags, left unclosed.
<span/> becomes <span>
They will then try to repair them with variying degrees of success.

Example where it fails:
`<t><t/></t><t><t/></t>`
should become
`<t><t></t></t><t><t></t></t>`
but it becomes
`<t><t></t><t><t></t></t>`

i.e. instead of closing the 'self-closing' tag immediately,
it puts everything inside a single t node

More concretely:
```
<t t-if>
  <t t-out />
</t>
<t t-else>
  <t t-out/>
</t>
```
becomes
```
<t t-if>
  <t t-out></t>
  <t t-else>
     <t t-out></t>
  </t>
</t>
```
which is invalid

-------------------------
The fix is simply to tell lxml that we want to print the xml nodes
as HTML nodes. This will make sure the output is compliant with
the standard and keep the semantic clear for the browser.

The issue does not appear before 16.2, as jquery used to fix it
for us until an update here 9c41ee5091ac06ac3ca71aeac607195c70061e4a

task-3162320

X-original-commit: 8ff2e1018264972107f19755ecda352d78dfa829
Part-of: odoo/odoo#118710
2023-04-17 11:47:11 +02:00

289 lines
10 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import collections
import unittest
from lxml import etree as ET
from lxml.builder import E
import odoo
from odoo.modules.module import get_resource_path
from odoo.tests import common
from odoo.tools.convert import convert_file, xml_import, _eval_xml
Field = E.field
Value = E.value
class TestEvalXML(common.TransactionCase):
def eval_xml(self, node, obj=None):
return _eval_xml(obj, node, self.env)
def test_char(self):
self.assertEqual(
self.eval_xml(Field("foo")),
"foo")
self.assertEqual(
self.eval_xml(Field("None")),
"None")
def test_int(self):
self.assertIsNone(
self.eval_xml(Field("None", type='int')),
"what the fuck?")
self.assertEqual(
self.eval_xml(Field(" 42 ", type="int")),
42)
with self.assertRaises(ValueError):
self.eval_xml(Field("4.82", type="int"))
with self.assertRaises(ValueError):
self.eval_xml(Field("Whelp", type="int"))
def test_float(self):
self.assertEqual(
self.eval_xml(Field("4.78", type="float")),
4.78)
with self.assertRaises(ValueError):
self.eval_xml(Field("None", type="float"))
with self.assertRaises(ValueError):
self.eval_xml(Field("Foo", type="float"))
def test_list(self):
self.assertEqual(
self.eval_xml(Field(type="list")),
[])
self.assertEqual(
self.eval_xml(Field(
Value("foo"),
Value("5", type="int"),
Value("4.76", type="float"),
Value("None", type="int"),
type="list"
)),
["foo", 5, 4.76, None])
def test_file(self):
Obj = collections.namedtuple('Obj', ['module', 'idref'])
obj = Obj('test_convert', None)
self.assertEqual(
self.eval_xml(Field('test_file.txt', type='file'), obj),
'test_convert,test_file.txt')
with self.assertRaises(IOError):
self.eval_xml(Field('test_nofile.txt', type='file'), obj)
def test_function(self):
obj = xml_import(self.env, 'test_convert', None, 'init')
# pass args in eval
xml = E.function(
model="test_convert.usered",
name="model_method",
eval="[1, 2]",
)
rec, args, kwargs = self.eval_xml(xml, obj)
self.assertEqual(rec.env.context, self.env.context)
self.assertEqual(rec.ids, [])
self.assertEqual(args, (1, 2))
self.assertEqual(kwargs, {})
xml = E.function(
model="test_convert.usered",
name="method",
eval="[1, 2]",
)
rec, args, kwargs = self.eval_xml(xml, obj)
self.assertEqual(rec.env.context, self.env.context)
self.assertEqual(rec.ids, [1])
self.assertEqual(args, (2,))
self.assertEqual(kwargs, {})
# pass args in child elements
xml = E.function(
E.value(eval="1"), E.value(eval="2"),
model="test_convert.usered",
name="model_method",
)
rec, args, kwargs = self.eval_xml(xml, obj)
self.assertEqual(rec.env.context, self.env.context)
self.assertEqual(rec.ids, [])
self.assertEqual(args, (1, 2))
self.assertEqual(kwargs, {})
xml = E.function(
E.value(eval="1"), E.value(eval="2"),
model="test_convert.usered",
name="method",
)
rec, args, kwargs = self.eval_xml(xml, obj)
self.assertEqual(rec.env.context, self.env.context)
self.assertEqual(rec.ids, [1])
self.assertEqual(args, (2,))
self.assertEqual(kwargs, {})
def test_function_kwargs(self):
obj = xml_import(self.env, 'test_convert', None, 'init')
# pass args and kwargs in child elements
xml = E.function(
E.value(eval="1"), E.value(name="foo", eval="2"),
model="test_convert.usered",
name="model_method",
)
rec, args, kwargs = self.eval_xml(xml, obj)
self.assertEqual(rec.env.context, self.env.context)
self.assertEqual(rec.ids, [])
self.assertEqual(args, (1,))
self.assertEqual(kwargs, {'foo': 2})
xml = E.function(
E.value(eval="1"), E.value(name="foo", eval="2"),
model="test_convert.usered",
name="method",
)
rec, args, kwargs = self.eval_xml(xml, obj)
self.assertEqual(rec.env.context, self.env.context)
self.assertEqual(rec.ids, [1])
self.assertEqual(args, ())
self.assertEqual(kwargs, {'foo': 2})
# pass args and context in kwargs
xml = E.function(
E.value(eval="1"), E.value(name="context", eval="{'foo': 2}"),
model="test_convert.usered",
name="model_method",
)
rec, args, kwargs = self.eval_xml(xml, obj)
self.assertEqual(rec.env.context, {'foo': 2})
self.assertEqual(rec.ids, [])
self.assertEqual(args, (1,))
self.assertEqual(kwargs, {})
xml = E.function(
E.value(eval="1"), E.value(name="context", eval="{'foo': 2}"),
model="test_convert.usered",
name="method",
)
rec, args, kwargs = self.eval_xml(xml, obj)
self.assertEqual(rec.env.context, {'foo': 2})
self.assertEqual(rec.ids, [1])
self.assertEqual(args, ())
self.assertEqual(kwargs, {})
def test_function_function(self):
obj = xml_import(self.env, 'test_convert', None, 'init')
xml = E.function(
E.function(model="test_convert.usered", name="search", eval="[[]]"),
model="test_convert.usered",
name="method",
)
rec, args, kwargs = self.eval_xml(xml, obj)
self.assertEqual(rec.env.context, {})
self.assertEqual(rec.ids, [])
self.assertEqual(args, ())
self.assertEqual(kwargs, {})
def test_o2m_sub_records(self):
# patch the model's class with a proxy that copies the argument
Model = self.registry['test_convert.test_model']
call_args = []
def _load_records(self, data_list, update=False):
call_args.append(data_list)
# pylint: disable=bad-super-call
return super(Model, self)._load_records(data_list, update=update)
self.patch(Model, '_load_records', _load_records)
# import a record with a subrecord
xml = ET.fromstring("""
<record id="test_convert.test_o2m_record" model="test_convert.test_model">
<field name="usered_ids">
<record id="test_convert.test_o2m_subrecord" model="test_convert.usered">
<field name="name">subrecord</field>
</record>
</field>
</record>
""".strip())
obj = xml_import(self.env, 'test_convert', None, 'init')
obj._tag_record(xml)
# check that field 'usered_ids' is not passed
self.assertEqual(len(call_args), 1)
for data in call_args[0]:
self.assertNotIn('usered_ids', data['values'],
"Unexpected value in O2M When loading XML with sub records")
def test_translated_field(self):
"""Tests importing a data file with a lang set in the environment sets the source (en_US)
rather than setting the translation (fr_FR)
This is currently the expected behavior.
Maybe one day we would like that `convert_file` and `xml_import` respect the environment lang,
but currently the API, and code blocks using `convert_file`/`xml_import` expect to set the source (en_US)
"""
self.env['res.lang']._activate_lang('fr_FR')
env_fr = self.env(context=dict(self.env.context, lang='fr_FR'))
record = self.env.ref('test_convert.test_translated_field')
# 1. Test xml_import, which is sometimes imported and used directly in addons' code
# Change the value of the record `name` field
record.name = 'bar'
self.assertEqual(record.name, 'bar')
# Reset the value to the one from the XML data file,
# with a lang passed in the environment.
filepath = get_resource_path('test_convert', 'data/test_translated_field/test_model_data.xml')
doc = ET.parse(filepath)
obj = xml_import(env_fr, 'test_convert', {}, mode='init', xml_filename=filepath)
obj.parse(doc.getroot())
self.assertEqual(record.with_context(lang=None).name, 'foo')
# 2. Test convert_file with an XML
# Change the value of the record `name` field
record.name = 'bar'
self.assertEqual(record.name, 'bar')
# Reset the value to the one from the XML data file,
# with a lang passed in the environment.
convert_file(env_fr, 'test_convert', 'data/test_translated_field/test_model_data.xml', {})
self.assertEqual(record.with_context(lang=None).name, 'foo')
# 3. Test convert_file with a CSV
# Change the value of the record `name` field
record.name = 'bar'
self.assertEqual(record.name, 'bar')
# Reset the value to the one from the XML data file,
# with a lang passed in the environment.
convert_file(env_fr, 'test_convert', 'data/test_translated_field/test_convert.test_model.csv', {})
self.assertEqual(record.with_context(lang=None).name, 'foo')
@unittest.skip("not tested")
def test_xml(self):
pass
def test_html(self):
self.assertEqual(
self.eval_xml(Field(ET.fromstring(
"""<parent>
<t t-if="True">
<t t-out="'text'"/>
</t>
<t t-else="">
<t t-out="'text2'"></t>
</t>
</parent>"""), type="html")),
"""<parent>
<t t-if="True">
<t t-out="'text'"></t>
</t>
<t t-else="">
<t t-out="'text2'"></t>
</t>
</parent>""",
"Evaluating an HTML field should give empty nodes instead of self-closing tags"
)