summaryrefslogtreecommitdiff
path: root/odoo/addons/test_testing_utilities
diff options
context:
space:
mode:
authorXavier Morel <xmo@odoo.com>2017-11-14 11:47:44 +0100
committerXavier Morel <xmo@odoo.com>2017-12-04 13:11:53 +0100
commit4bf97bdbe28cc3dc3456a272b0acc3741fdf6e1b (patch)
tree22b22bafa3a8c763fb30e984fdbb411968dd8c02 /odoo/addons/test_testing_utilities
parent026979c278175de4fa27fba1843b5b9320894d3e (diff)
[ADD] base: server-side formview
Useful to better test business flows & replace yaml test files: instantiating records & calling onchanges by hand is very error-prone (it's easy to miss onchanges, or to manually set fields which can't be set through the view we're interested in, ...) odoo.tests.common.Form implements the basic *creation* flow (fields_get, default_get, onchange) up to saving the record, including proper handling of the required and readonly modifiers & their domains. * edition of existing records, including filtering out unmodified fields when saving * possibly buttons * ensuring the API is convenient
Diffstat (limited to 'odoo/addons/test_testing_utilities')
-rw-r--r--odoo/addons/test_testing_utilities/__init__.py2
-rw-r--r--odoo/addons/test_testing_utilities/__manifest__.py15
-rw-r--r--odoo/addons/test_testing_utilities/ir.model.access.csv14
-rw-r--r--odoo/addons/test_testing_utilities/menu.xml94
-rw-r--r--odoo/addons/test_testing_utilities/models.py171
-rw-r--r--odoo/addons/test_testing_utilities/tests/__init__.py2
-rw-r--r--odoo/addons/test_testing_utilities/tests/test_form_impl.py461
7 files changed, 759 insertions, 0 deletions
diff --git a/odoo/addons/test_testing_utilities/__init__.py b/odoo/addons/test_testing_utilities/__init__.py
new file mode 100644
index 00000000000..a0fdc10fe11
--- /dev/null
+++ b/odoo/addons/test_testing_utilities/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+from . import models
diff --git a/odoo/addons/test_testing_utilities/__manifest__.py b/odoo/addons/test_testing_utilities/__manifest__.py
new file mode 100644
index 00000000000..eb7731a7826
--- /dev/null
+++ b/odoo/addons/test_testing_utilities/__manifest__.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+{
+ 'name': "Test testing utilities",
+ 'category': "Tests",
+ 'description': """Non-trivial testing utilities can require models & all
+
+This here module is useful to validate that they're doing what they're
+supposed to do
+ """,
+ 'depends': ['base'],
+ 'data': [
+ 'ir.model.access.csv',
+ 'menu.xml',
+ ]
+}
diff --git a/odoo/addons/test_testing_utilities/ir.model.access.csv b/odoo/addons/test_testing_utilities/ir.model.access.csv
new file mode 100644
index 00000000000..ced191d38e2
--- /dev/null
+++ b/odoo/addons/test_testing_utilities/ir.model.access.csv
@@ -0,0 +1,14 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_test_testing_utilities_a,access_test_testing_utilities_a,model_test_testing_utilities_a,,1,0,0,0
+access_test_testing_utilities_readonly,access_test_testing_utilities_readonly,model_test_testing_utilities_readonly,,1,0,0,0
+access_test_testing_utilities_c,access_test_testing_utilities_c,model_test_testing_utilities_c,,1,0,0,0
+access_test_testing_utilities_d,access_test_testing_utilities_d,model_test_testing_utilities_d,,1,0,0,0
+access_test_testing_utilities_e,access_test_testing_utilities_e,model_test_testing_utilities_e,,1,0,0,0
+access_test_testing_utilities_f,access_test_testing_utilities_f,model_test_testing_utilities_f,,1,0,0,0
+access_test_testing_utilities_g,access_test_testing_utilities_g,model_test_testing_utilities_g,,1,0,0,0
+access_test_testing_utilities_m2o,access_test_testing_utilities_m2o,model_test_testing_utilities_m2o,,1,0,0,0
+access_test_testing_utilities_default,access_test_testing_utilities_default,model_test_testing_utilities_default,,1,0,0,0
+access_test_testing_utilities_parent,access_test_testing_utilities_parent,model_test_testing_utilities_parent,,1,0,0,0
+access_test_testing_utilities_sub,access_test_testing_utilities_sub,model_test_testing_utilities_sub,,1,0,0,0
+access_test_testing_utilities_sub2,access_test_testing_utilities_sub2,model_test_testing_utilities_sub2,,1,0,0,0
+access_test_testing_utilities_sub3,access_test_testing_utilities_sub3,model_test_testing_utilities_sub3,,1,0,0,0
diff --git a/odoo/addons/test_testing_utilities/menu.xml b/odoo/addons/test_testing_utilities/menu.xml
new file mode 100644
index 00000000000..72a4ba79157
--- /dev/null
+++ b/odoo/addons/test_testing_utilities/menu.xml
@@ -0,0 +1,94 @@
+<odoo>
+ <record id="action_test_o2m_onchange" model="ir.actions.act_window">
+ <field name="name">Test o2m onchange thing</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">test_testing_utilities.parent</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+
+ <menuitem id="menu_o2m" parent="base.menu_tests"
+ name="O2M onchange test thing"
+ action="action_test_o2m_onchange"/>
+
+ <record id="o2m_parent" model="ir.ui.view">
+ <field name="name">Parent External Form</field>
+ <field name="priority">1</field>
+ <field name="model">test_testing_utilities.parent</field>
+ <field name="arch" type="xml">
+ <form>
+ <sheet>
+ <group>
+ <field name="value"/>
+ <field name="v"/>
+ </group>
+ <group>
+ <field name="subs"/>
+ </group>
+ </sheet>
+ </form>
+ </field>
+ </record>
+ <record id="o2m_parent_readonly" model="ir.ui.view">
+ <field name="name">Parent readonly o2m field</field>
+ <field name="model">test_testing_utilities.parent</field>
+ <field name="inherit_id" ref="o2m_parent"/>
+ <field name="mode">primary</field>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='subs']" position="attributes">
+ <attribute name="readonly">1</attribute>
+ </xpath>
+ </field>
+ </record>
+ <record id="o2m_tree" model="ir.ui.view">
+ <field name="name">Sub regular</field>
+ <field name="priority">1</field>
+ <field name="model">test_testing_utilities.sub</field>
+ <field name="arch" type="xml">
+ <tree>
+ <field name="value"/>
+ <field name="v"/>
+ <field name="name"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="o2m_parent_ed" model="ir.ui.view">
+ <field name="name">Parent External Tree</field>
+ <field name="model">test_testing_utilities.parent</field>
+ <field name="arch" type="xml">
+ <form>
+ <field name="value"/>
+ <field name="v"/>
+ <field name="subs" context="{'tree_view_ref': 'test_testing_utilities.editable_external'}"/>
+ </form>
+ </field>
+ </record>
+ <record id="editable_external" model="ir.ui.view">
+ <field name="name">Sub regular</field>
+ <field name="model">test_testing_utilities.sub</field>
+ <field name="arch" type="xml">
+ <tree editable="bottom">
+ <field name="value"/>
+ <field name="v"/>
+ <field name="name"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="o2m_parent_inline" model="ir.ui.view">
+ <field name="name">Parent Inline Tree</field>
+ <field name="model">test_testing_utilities.parent</field>
+ <field name="arch" type="xml">
+ <form>
+ <field name="value"/>
+ <field name="v"/>
+ <field name="subs">
+ <tree editable="bottom">
+ <field name="value"/>
+ <field name="name"/>
+ </tree>
+ </field>
+ </form>
+ </field>
+ </record>
+</odoo>
diff --git a/odoo/addons/test_testing_utilities/models.py b/odoo/addons/test_testing_utilities/models.py
new file mode 100644
index 00000000000..2bb6e5e5dd6
--- /dev/null
+++ b/odoo/addons/test_testing_utilities/models.py
@@ -0,0 +1,171 @@
+# -*- coding: utf-8 -*-
+from __future__ import division
+from odoo import api, fields, models
+
+class A(models.Model):
+ _name = 'test_testing_utilities.a'
+
+ f1 = fields.Integer(required=True)
+ f2 = fields.Integer(default=42)
+ f3 = fields.Integer()
+ f4 = fields.Integer(compute='_compute_f4')
+
+ @api.onchange('f2')
+ def _on_change_f2(self):
+ self.f3 = int(self.f2 / 2)
+
+ @api.depends('f1', 'f2')
+ def _compute_f4(self):
+ for r in self:
+ r.f4 = r.f2 / (r.f1 or 1)
+
+class B(models.Model):
+ _name = 'test_testing_utilities.readonly'
+
+ f1 = fields.Integer(default=1, readonly=True)
+ f2 = fields.Integer(compute='_compute_f2')
+
+ @api.depends('f1')
+ def _compute_f2(self):
+ for r in self:
+ r.f2 = 2 * r.f1
+
+class C(models.Model):
+ _name = 'test_testing_utilities.c'
+
+ name = fields.Char("name", required=True)
+ f2 = fields.Many2one('test_testing_utilities.m2o')
+
+ @api.onchange('f2')
+ def _on_change_f2(self):
+ self.name = self.f2.name
+
+class M2O(models.Model):
+ _name = 'test_testing_utilities.m2o'
+
+ name = fields.Char(required=True)
+
+class M2Onchange(models.Model):
+ _name = 'test_testing_utilities.d'
+
+ # used to check that defaults & onchange to m2o work
+ f = fields.Many2one(
+ 'test_testing_utilities.m2o',
+ required=True,
+ default=lambda self: self.env['test_testing_utilities.m2o'].search(
+ [], limit=1
+ )
+ )
+ f2 = fields.Char()
+
+ @api.onchange('f2')
+ def _on_change_f2(self):
+ self.f = self.env['test_testing_utilities.m2o'].search([
+ ('name', 'ilike', self.f2),
+ ], limit=1)
+
+class M2MChange(models.Model):
+ _name = 'test_testing_utilities.e'
+
+ m2m = fields.Many2many('test_testing_utilities.sub2')
+ count = fields.Integer(compute='_m2m_count')
+
+ @api.depends('m2m')
+ def _m2m_count(self):
+ for r in self:
+ r.count = len(r.m2m)
+
+class M2MSub(models.Model):
+ _name = 'test_testing_utilities.sub2'
+
+ name = fields.Char()
+
+class M2MChange2(models.Model):
+ _name = 'test_testing_utilities.f'
+
+ def _get_some(self):
+ r = self.env['test_testing_utilities.sub2'].search([], limit=2)
+ return r
+
+ m2m = fields.Many2many(
+ 'test_testing_utilities.sub2',
+ default=_get_some,
+ )
+ m2o = fields.Many2one('test_testing_utilities.sub2')
+
+ @api.onchange('m2o')
+ def _on_change_m2o(self):
+ self.m2m = self.m2m | self.m2o
+
+class M2MReadonly(models.Model):
+ _name = 'test_testing_utilities.g'
+
+ m2m = fields.Many2many('test_testing_utilities.sub3', readonly=True)
+
+class M2MSub3(models.Model):
+ _name = 'test_testing_utilities.sub3'
+
+ name = fields.Char()
+
+class O2MChange(models.Model):
+ _name = 'test_testing_utilities.parent'
+
+ value = fields.Integer(default=1)
+ v = fields.Integer()
+ subs = fields.One2many('test_testing_utilities.sub', 'parent_id')
+
+ @api.onchange('value', 'subs')
+ def _onchange_values(self):
+ self.v = self.value + sum(s.value for s in self.subs)
+
+class O2MSub(models.Model):
+ _name = 'test_testing_utilities.sub'
+
+ name = fields.Char(compute='_compute_name')
+ value = fields.Integer(default=2)
+ v = fields.Integer()
+ parent_id = fields.Many2one('test_testing_utilities.parent')
+ has_parent = fields.Boolean()
+
+ @api.onchange('value')
+ def _onchange_value(self):
+ self.v = self.value
+
+ @api.depends('v')
+ def _compute_name(self):
+ for r in self:
+ r.name = str(r.v)
+
+ @api.onchange('has_parent')
+ def _onchange_has_parent(self):
+ self.has_parent = bool(self.parent_id)
+ if self.has_parent:
+ self.value = self.parent_id.value
+
+class O2MDefault(models.Model):
+ _name = 'test_testing_utilities.default'
+
+ def _default_subs(self):
+ return [
+ (0, 0, {'v': 5})
+ ]
+ value = fields.Integer(default=1)
+ v = fields.Integer()
+ subs = fields.One2many('test_testing_utilities.sub3', 'parent_id', default=_default_subs)
+
+class O2MSub3(models.Model):
+ _name = 'test_testing_utilities.sub3'
+
+ name = fields.Char(compute='_compute_name')
+ value = fields.Integer(default=2)
+ v = fields.Integer(default=6)
+ parent_id = fields.Many2one('test_testing_utilities.default')
+
+ @api.onchange('value')
+ def _onchange_value(self):
+ self.v = self.value
+
+ @api.depends('v')
+ def _compute_name(self):
+ for r in self:
+ r.name = str(r.v)
diff --git a/odoo/addons/test_testing_utilities/tests/__init__.py b/odoo/addons/test_testing_utilities/tests/__init__.py
new file mode 100644
index 00000000000..0c0095dfd37
--- /dev/null
+++ b/odoo/addons/test_testing_utilities/tests/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+from . import test_form_impl
diff --git a/odoo/addons/test_testing_utilities/tests/test_form_impl.py b/odoo/addons/test_testing_utilities/tests/test_form_impl.py
new file mode 100644
index 00000000000..19d192365a5
--- /dev/null
+++ b/odoo/addons/test_testing_utilities/tests/test_form_impl.py
@@ -0,0 +1,461 @@
+# -*- coding: utf-8 -*-
+"""
+Test for the pseudo-form implementation (odoo.tests.common.Form), which should
+basically be a server-side implementation of form views (though probably not
+complete) intended for properly validating business "view" flows (onchanges,
+readonly, required, ...) and make it easier to generate sensible & coherent
+business objects.
+"""
+from operator import itemgetter
+
+from odoo.tests.common import TransactionCase, Form
+
+
+class TestBasic(TransactionCase):
+ def test_defaults(self):
+ """
+ Checks that we can load a default form view and perform trivial
+ default_get & onchanges & computations
+ """
+ f = Form(self.env['test_testing_utilities.a'])
+ self.assertEqual(f.id, False, "check that our record is not in db (yet)")
+
+ self.assertEqual(f.f2, 42)
+ self.assertEqual(f.f3, 21)
+ self.assertEqual(f.f4, 42)
+
+ f.f1 = 4
+ self.assertEqual(f.f2, 42)
+ self.assertEqual(f.f3, 21)
+ self.assertEqual(f.f4, 10)
+
+ f.f2 = 8
+ self.assertEqual(f.f3, 4)
+ self.assertEqual(f.f4, 2)
+
+ r = f.save()
+ self.assertEqual(
+ (r.f1, r.f2, r.f3, r.f4),
+ (4, 8, 4, 2),
+ )
+
+ def test_required(self):
+ f = Form(self.env['test_testing_utilities.a'])
+ # f1 no default & no value => should fail
+ with self.assertRaisesRegexp(AssertionError, 'f1 is a required field'):
+ f.save()
+ # set f1 and unset f2 => should work
+ f.f1 = 1
+ f.f2 = False
+ r = f.save()
+ self.assertEqual(
+ (r.f1, r.f2, r.f3, r.f4),
+ (1, 0, 0, 0)
+ )
+
+ def test_readonly(self):
+ """
+ Checks that fields with readonly modifiers (marked as readonly or
+ computed w/o set) raise an error when set.
+ """
+ f = Form(self.env['test_testing_utilities.readonly'])
+
+ with self.assertRaises(AssertionError):
+ f.f1 = 5
+ with self.assertRaises(AssertionError):
+ f.f2 = 42
+
+class TestM2O(TransactionCase):
+ def test_default_and_onchange(self):
+ """ Checks defaults & onchanges impacting m2o fields
+ """
+ Sub = self.env['test_testing_utilities.m2o']
+ a = Sub.create({'name': "A"})
+ b = Sub.create({'name': "B"})
+
+ f = Form(self.env['test_testing_utilities.d'])
+
+ self.assertEqual(
+ f.f, a,
+ "The default value for the m2o should be the first Sub record"
+ )
+ f.f2 = "B"
+ self.assertEqual(
+ f.f, b,
+ "The new m2o value should match the second field by name"
+ )
+
+ f.save()
+
+ def test_set(self):
+ """
+ Checks that we get/set recordsets for m2o & that set correctly
+ triggers onchange
+ """
+ r1 = self.env['test_testing_utilities.m2o'].create({'name': "A"})
+ r2 = self.env['test_testing_utilities.m2o'].create({'name': "B"})
+
+ f = Form(self.env['test_testing_utilities.c'])
+
+ # check that basic manipulations work
+ f.f2 = r1
+ self.assertEqual(f.f2, r1)
+ self.assertEqual(f.name, 'A')
+ f.f2 = r2
+ self.assertEqual(f.name, 'B')
+
+ # can't set an int to an m2o field
+ with self.assertRaises(AssertionError):
+ f.f2 = r1.id
+ self.assertEqual(f.f2, r2)
+ self.assertEqual(f.name, 'B')
+
+ # can't set a record of the wrong model
+ temp = self.env['test_testing_utilities.readonly'].create({})
+ with self.assertRaises(AssertionError):
+ f.f2 = temp
+ self.assertEqual(f.f2, r2)
+ self.assertEqual(f.name, 'B')
+
+ r = f.save()
+ self.assertEqual(r.f2, r2)
+
+class TestM2M(TransactionCase):
+ def test_add(self):
+ Sub = self.env['test_testing_utilities.sub2']
+ f = Form(self.env['test_testing_utilities.e'])
+
+ r1 = Sub.create({'name': "Item"})
+ r2 = Sub.create({'name': "Item2"})
+
+ f.m2m.add(r1)
+ f.m2m.add(r2)
+
+ r = f.save()
+
+ self.assertEqual(
+ r.m2m,
+ r1 | r2
+ )
+
+ def test_remove_by_index(self):
+ Sub = self.env['test_testing_utilities.sub2']
+ f = Form(self.env['test_testing_utilities.e'])
+
+ r1 = Sub.create({'name': "Item"})
+ r2 = Sub.create({'name': "Item2"})
+
+ f.m2m.add(r1)
+ f.m2m.add(r2)
+ f.m2m.remove(index=0)
+
+ r = f.save()
+
+ self.assertEqual(
+ r.m2m,
+ r2
+ )
+
+ def test_remove_by_id(self):
+ Sub = self.env['test_testing_utilities.sub2']
+ f = Form(self.env['test_testing_utilities.e'])
+
+ r1 = Sub.create({'name': "Item"})
+ r2 = Sub.create({'name': "Item2"})
+
+ f.m2m.add(r1)
+ f.m2m.add(r2)
+ f.m2m.remove(id=r1.id)
+
+ r = f.save()
+
+ self.assertEqual(
+ r.m2m,
+ r2
+ )
+
+ def test_on_m2m_change(self):
+ Sub = self.env['test_testing_utilities.sub2']
+ f = Form(self.env['test_testing_utilities.e'])
+
+ self.assertEqual(f.count, 0)
+ f.m2m.add(Sub.create({'name': 'a'}))
+ self.assertEqual(f.count, 1)
+ f.m2m.add(Sub.create({'name': 'a'}))
+ f.m2m.add(Sub.create({'name': 'a'}))
+ f.m2m.add(Sub.create({'name': 'a'}))
+ self.assertEqual(f.count, 4)
+ f.m2m.remove(index=0)
+ f.m2m.remove(index=0)
+ f.m2m.remove(index=0)
+ self.assertEqual(f.count, 1)
+
+ def test_m2m_changed(self):
+ Sub = self.env['test_testing_utilities.sub2']
+ a = Sub.create({'name': 'a'})
+ b = Sub.create({'name': 'b'})
+ c = Sub.create({'name': 'c'})
+ d = Sub.create({'name': 'd'})
+
+ f = Form(self.env['test_testing_utilities.f'])
+ # check default_get
+ self.assertEqual(f.m2m[:], a | b)
+
+ f.m2o = c
+ self.assertEqual(f.m2m[:], a | b | c)
+
+ f.m2o = d
+ self.assertEqual(f.m2m[:], a | b | c | d)
+
+ def test_m2m_readonly(self):
+ Sub = self.env['test_testing_utilities.sub3']
+ a = Sub.create({'name': 'a'})
+ b = Sub.create({'name': 'b'})
+ r = self.env['test_testing_utilities.g'].create({
+ 'm2m': [(6, 0, a.ids)]
+ })
+
+ f = Form(r)
+
+ with self.assertRaises(AssertionError):
+ f.m2m.add(b)
+ with self.assertRaises(AssertionError):
+ f.m2m.remove(id=a.id)
+
+ f.save()
+ self.assertEqual(r.m2m, a)
+
+
+get = itemgetter('name', 'value', 'v')
+class TestO2M(TransactionCase):
+ def test_basic_alterations(self):
+ """ Tests that the o2m proxy allows adding, removing and editing o2m
+ records
+ """
+ f = Form(self.env['test_testing_utilities.parent'], view='test_testing_utilities.o2m_parent')
+
+ f.subs.new().save()
+ f.subs.new().save()
+ f.subs.new().save()
+ f.subs.remove(index=0)
+
+ r = f.save()
+
+ self.assertEqual(
+ [get(s) for s in r.subs],
+ [("2", 2, 2), ("2", 2, 2)]
+ )
+
+ with Form(r, view='test_testing_utilities.o2m_parent') as f:
+ with f.subs.new() as sub:
+ sub.value = 5
+ f.subs.new().save()
+
+ with f.subs.edit(index=2) as sub:
+ self.assertEqual(sub.v, 5)
+
+ f.subs.remove(index=0)
+
+ self.assertEqual(
+ [get(s) for s in r.subs],
+ [("2", 2, 2), ("5", 5, 5), ("2", 2, 2)]
+ )
+
+ with Form(r, view='test_testing_utilities.o2m_parent') as f, \
+ f.subs.edit(index=0) as sub,\
+ self.assertRaises(AssertionError):
+ sub.name = "whop whop"
+
+ def test_o2m_editable_list(self):
+ """ Tests the o2m proxy when the list view is editable rather than
+ delegating to a separate form view
+ """
+ f = Form(self.env['test_testing_utilities.parent'], view='test_testing_utilities.o2m_parent_ed')
+
+ with f.subs.new() as s:
+ s.value = 1
+ with f.subs.new() as s:
+ s.value = 3
+ with f.subs.new() as s:
+ s.value = 7
+
+ r = f.save()
+
+ self.assertEqual(r.v, 12)
+ self.assertEqual(
+ [get(s) for s in r.subs],
+ [('1', 1, 1), ('3', 3, 3), ('7', 7, 7)]
+ )
+
+ def test_o2m_inline(self):
+ """ Tests the o2m proxy when the list and form views are provided
+ inline rather than fetched separately
+ """
+ f = Form(self.env['test_testing_utilities.parent'], view='test_testing_utilities.o2m_parent_inline')
+
+ with f.subs.new() as s:
+ s.value = 42
+
+ r = f.save()
+
+ self.assertEqual(
+ [get(s) for s in r.subs],
+ [("0", 42, 0)],
+ "should not have set v (and thus not name)"
+ )
+
+ def test_o2m_default(self):
+ """ Tests that default_get can return defaults for the o2m
+ """
+ f = Form(self.env['test_testing_utilities.default'])
+
+ with f.subs.edit(index=0) as s:
+ self.assertEqual(s.v, 5)
+ self.assertEqual(s.value, False)
+
+ r = f.save()
+
+ self.assertEqual(
+ [get(s) for s in r.subs],
+ [("5", 2, 5)]
+ )
+
+ def test_o2m_inner_default(self):
+ """ Tests that creating an o2m record will get defaults for it
+ """
+ f = Form(self.env['test_testing_utilities.default'])
+
+ with f.subs.new() as s:
+ self.assertEqual(s.value, 2)
+ self.assertEqual(s.v, 2, "should have onchanged value to v")
+
+ def test_o2m_onchange_parent(self):
+ """ Tests that changing o2m content triggers onchange in the parent
+ """
+ f = Form(self.env['test_testing_utilities.parent'])
+
+ self.assertEqual(f.value, 1, "value should have its default")
+ self.assertEqual(f.v, 1, "v should be equal to value")
+
+ f.subs.new().save()
+
+ self.assertEqual(f.v, 3, "should be sum of value & children v")
+
+ def test_o2m_onchange_inner(self):
+ """ Tests that editing a field of an o2m record triggers onchange
+ in the o2m record and its parent
+ """
+ f = Form(self.env['test_testing_utilities.parent'])
+
+ # only apply the onchange on edition end (?)
+ with f.subs.new() as sub:
+ sub.value = 6
+ self.assertEqual(sub.v, 6)
+ self.assertEqual(f.v, 1)
+ self.assertEqual(f.v, 7)
+
+ def test_o2m_parent_content(self):
+ """ Tests that when editing a field of an o2m the data sent contains
+ the parent data
+ """
+ f = Form(self.env['test_testing_utilities.parent'])
+
+ # only apply the onchange on edition end (?)
+ with f.subs.new() as sub:
+ sub.has_parent = True
+ self.assertEqual(sub.has_parent, True)
+ self.assertEqual(sub.value, 1)
+ self.assertEqual(sub.v, 1)
+
+ def test_m2o_readonly(self):
+ r = self.env['test_testing_utilities.parent'].create({
+ 'subs': [(0, 0, {})]
+ })
+ f = Form(r, view='test_testing_utilities.o2m_parent_readonly')
+
+ with self.assertRaises(AssertionError):
+ f.subs.new()
+ with self.assertRaises(AssertionError):
+ f.subs.edit(index=0)
+ with self.assertRaises(AssertionError):
+ f.subs.remove(index=0)
+
+class TestEdition(TransactionCase):
+ """ These use the context manager form as we don't need the record
+ post-save (we already have it) and it's easier to see what bits act on
+ the form (inside `with`) versus outside. That let me catch a few
+ mistakes.
+ """
+ def test_trivial(self):
+ r = self.env['test_testing_utilities.a'].create({
+ 'f1': 5,
+ })
+
+ with Form(r) as f:
+ self.assertEqual(f.id, r.id)
+ self.assertEqual(f.f1, 5)
+ self.assertEqual(f.f4, 8)
+
+ f.f2 = 5
+ self.assertEqual(f.f3, 2)
+ self.assertEqual(f.f4, 1)
+
+ self.assertEqual(r.f2, 5)
+ self.assertEqual(r.f3, 2)
+
+ def test_m2o(self):
+ Sub = self.env['test_testing_utilities.m2o']
+ a = Sub.create({'name': 'a'})
+ b = Sub.create({'name': 'b'})
+ c = Sub.create({'name': 'c'})
+
+ r = self.env['test_testing_utilities.d'].create({
+ 'f': c.id,
+ 'f2': "OK",
+ })
+
+ with Form(r) as f:
+ # no default/onchange should have run so loading an incoherent
+ # record should still be incoherent
+ self.assertEqual(f.f, c)
+ self.assertEqual(f.f2, 'OK')
+
+ f.f2 = "b"
+ self.assertEqual(f.f, b)
+ f.f2 = "Whoops"
+ self.assertEqual(f.f, Sub)
+ f.f2 = "a"
+ self.assertEqual(f.f, a)
+
+ self.assertEqual(r.f2, "a")
+ self.assertEqual(r.f, a)
+
+ def test_m2m_empty(self):
+ sub = self.env['test_testing_utilities.sub2'].create({'name': 'a'})
+
+ r = self.env['test_testing_utilities.f'].create({
+ 'm2m': []
+ })
+
+ with Form(r) as f:
+ f.m2o = sub
+
+ self.assertEqual(r.m2o, sub)
+ self.assertEqual(r.m2m, sub)
+
+ def test_m2m_nonempty(self):
+ Sub = self.env['test_testing_utilities.sub2']
+ a = Sub.create({'name': 'a'})
+ b = Sub.create({'name': 'b'})
+ c = Sub.create({'name': 'c'})
+
+ r = self.env['test_testing_utilities.f'].create({
+ 'm2m': [(6, 0, (a | b | c).ids)]
+ })
+
+ with Form(r) as f:
+ self.assertEqual(f.m2m[:], (a | b | c))
+ f.m2m.remove(index=0)
+ self.assertEqual(f.m2m[:], (b | c))
+
+ self.assertEqual(r.m2m, (b | c))