From 72f53c43b3b2a44f242eec9f7d5f0085bda72989 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Mon, 19 Nov 2018 07:15:24 +0000 Subject: [IMP] web: force mobile screen size for mobile suite Before this rev., the mobile js tests suite was run on a desktop like chrome-headless (1366x768). This can be a problem for this suite which tests mobile behaviors and thus clicks on parts of the interface that are only visible on small devices (thanks to scss media queries). This wasn't really a problem until now, because the test framework allowed to click on invisible elements. But it's no longer the case since the introduction of click helpers. --- addons/web/tests/test_js.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/addons/web/tests/test_js.py b/addons/web/tests/test_js.py index 04df8be91c1..71e96c8b3b8 100644 --- a/addons/web/tests/test_js.py +++ b/addons/web/tests/test_js.py @@ -10,14 +10,10 @@ RE_ONLY = re.compile('QUnit\.only\(') @odoo.tests.tagged('post_install', '-at_install') class WebSuite(odoo.tests.HttpCase): - def test_01_js(self): + def test_js(self): # webclient desktop test suite self.phantom_js('/web/tests?mod=web&failfast', "", "", login='admin', timeout=1800) - def test_02_js(self): - # webclient mobile test suite - self.phantom_js('/web/tests/mobile?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') @@ -35,3 +31,12 @@ class WebSuite(odoo.tests.HttpCase): 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.phantom_js('/web/tests/mobile?mod=web&failfast', "", "", login='admin', timeout=1800) -- cgit v1.2.3 From bcbb7ee3fecaad3c7e801fa5dbc90a566faac27f Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Mon, 19 Nov 2018 08:05:17 +0000 Subject: [REF] web: properly name relational fields test files --- .../relational_fields/field_many2many_tests.js | 1160 +++ .../relational_fields/field_many2one_tests.js | 3025 ++++++++ .../relational_fields/field_many_to_many_tests.js | 1160 --- .../relational_fields/field_many_to_one_tests.js | 3025 -------- .../relational_fields/field_one2many_tests.js | 8176 ++++++++++++++++++++ .../relational_fields/field_one_to_many_tests.js | 8176 -------------------- addons/web/views/webclient_templates.xml | 6 +- 7 files changed, 12364 insertions(+), 12364 deletions(-) create mode 100644 addons/web/static/tests/fields/relational_fields/field_many2many_tests.js create mode 100644 addons/web/static/tests/fields/relational_fields/field_many2one_tests.js delete mode 100644 addons/web/static/tests/fields/relational_fields/field_many_to_many_tests.js delete mode 100644 addons/web/static/tests/fields/relational_fields/field_many_to_one_tests.js create mode 100644 addons/web/static/tests/fields/relational_fields/field_one2many_tests.js delete mode 100644 addons/web/static/tests/fields/relational_fields/field_one_to_many_tests.js diff --git a/addons/web/static/tests/fields/relational_fields/field_many2many_tests.js b/addons/web/static/tests/fields/relational_fields/field_many2many_tests.js new file mode 100644 index 00000000000..e782a27cb4a --- /dev/null +++ b/addons/web/static/tests/fields/relational_fields/field_many2many_tests.js @@ -0,0 +1,1160 @@ +odoo.define('web.field_many_to_many_tests', function (require) { +"use strict"; + +var FormView = require('web.FormView'); +var testUtils = require('web.test_utils'); +var createView = testUtils.createView; + +QUnit.module('fields', {}, function () { + + QUnit.module('relational_fields', { + beforeEach: function () { + this.data = { + partner: { + fields: { + display_name: { string: "Displayed name", type: "char" }, + foo: { string: "Foo", type: "char", default: "My little Foo Value" }, + int_field: { string: "int_field", type: "integer", sortable: true }, + turtles: { string: "one2many turtle field", type: "one2many", relation: 'turtle', relation_field: 'turtle_trululu' }, + timmy: { string: "pokemon", type: "many2many", relation: 'partner_type' }, + color: { + type: "selection", + selection: [['red', "Red"], ['black', "Black"]], + default: 'red', + string: "Color", + }, + user_id: { string: "User", type: 'many2one', relation: 'user' }, + reference: { + string: "Reference Field", type: 'reference', selection: [ + ["product", "Product"], ["partner_type", "Partner Type"], ["partner", "Partner"]] + }, + }, + records: [{ + id: 1, + display_name: "first record", + foo: "yop", + int_field: 10, + turtles: [2], + timmy: [], + user_id: 17, + reference: 'product,37', + }, { + id: 2, + display_name: "second record", + foo: "blip", + int_field: 9, + timmy: [], + user_id: 17, + }, { + id: 4, + display_name: "aaa", + }], + onchanges: {}, + }, + product: { + fields: { + name: { string: "Product Name", type: "char" } + }, + records: [{ + id: 37, + display_name: "xphone", + }, { + id: 41, + display_name: "xpad", + }] + }, + partner_type: { + fields: { + name: { string: "Partner Type", type: "char" }, + color: { string: "Color index", type: "integer" }, + }, + records: [ + { id: 12, display_name: "gold", color: 2 }, + { id: 14, display_name: "silver", color: 5 }, + ] + }, + turtle: { + fields: { + display_name: { string: "Displayed name", type: "char" }, + turtle_foo: { string: "Foo", type: "char" }, + turtle_bar: { string: "Bar", type: "boolean", default: true }, + partner_ids: { string: "Partner", type: "many2many", relation: 'partner' }, + }, + records: [{ + id: 1, + display_name: "leonardo", + turtle_foo: "yop", + partner_ids: [], + }, { + id: 2, + display_name: "donatello", + turtle_foo: "blip", + partner_ids: [2, 4], + }, { + id: 3, + display_name: "raphael", + turtle_foo: "kawa", + partner_ids: [], + }], + onchanges: {}, + }, + user: { + fields: { + name: { string: "Name", type: "char" }, + }, + records: [{ + id: 17, + name: "Aline", + }, { + id: 19, + name: "Christine", + }] + }, + }; + } + }, function () { + QUnit.module('FieldMany2Many'); + + QUnit.test('many2many kanban: edition', function (assert) { + assert.expect(32); + + this.data.partner.records[0].timmy = [12, 14]; + this.data.partner_type.records.push({ id: 15, display_name: "red", color: 6 }); + this.data.partner_type.records.push({ id: 18, display_name: "yellow", color: 4 }); + this.data.partner_type.records.push({ id: 21, display_name: "blue", color: 1 }); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + archs: { + 'partner_type,false,form': '
', + 'partner_type,false,list': '', + 'partner_type,false,search': '' + + '' + + '', + }, + res_id: 1, + mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/partner_type/write') { + assert.strictEqual(args.args[1].display_name, "new name", "should write 'new_name'"); + } + if (route === '/web/dataset/call_kw/partner_type/create') { + assert.strictEqual(args.args[0].display_name, "A new type", "should create 'A new type'"); + } + if (route === '/web/dataset/call_kw/partner/write') { + var commands = args.args[1].timmy; + assert.strictEqual(commands.length, 1, "should have generated one command"); + assert.strictEqual(commands[0][0], 6, "generated command should be REPLACE WITH"); + // get the created type's id + var createdType = _.findWhere(this.data.partner_type.records, { + display_name: "A new type" + }); + var ids = _.sortBy([12, 15, 18].concat(createdType.id), _.identity.bind(_)); + assert.ok(_.isEqual(_.sortBy(commands[0][2], _.identity.bind(_)), ids), + "new value should be " + ids); + } + return this._super.apply(this, arguments); + }, + }); + + // the SelectCreateDialog requests the session, so intercept its custom + // event to specify a fake session to prevent it from crashing + testUtils.intercept(form, 'get_session', function (event) { + event.data.callback({ user_context: {} }); + }); + + assert.ok(!form.$('.o_kanban_view .delete_icon').length, + 'delete icon should not be visible in readonly'); + assert.ok(!form.$('.o_field_many2many .o-kanban-button-new').length, + '"Add" button should not be visible in readonly'); + + form.$buttons.find('.o_form_button_edit').click(); + + assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 2, + 'should contain 2 records'); + assert.strictEqual(form.$('.o_kanban_record:first() span').text(), 'gold', + 'display_name of subrecord should be the one in DB'); + assert.ok(form.$('.o_kanban_view .delete_icon').length, + 'delete icon should be visible in edit'); + assert.ok(form.$('.o_field_many2many .o-kanban-button-new').length, + '"Add" button should be visible in edit'); + assert.strictEqual(form.$('.o_field_many2many .o-kanban-button-new').text().trim(), "Add", + 'Create button should have "Add" label'); + + // edit existing subrecord + form.$('.oe_kanban_global_click:first()').click(); + + $('.modal .o_form_view input').val('new name').trigger('input'); + $('.modal .modal-footer .btn-primary').click(); // save + assert.strictEqual(form.$('.o_kanban_record:first() span').text(), 'new name', + 'value of subrecord should have been updated'); + + // add subrecords + // -> single select + form.$('.o_field_many2many .o-kanban-button-new').click(); + assert.ok($('.modal .o_list_view').length, "should have opened a list view in a modal"); + assert.strictEqual($('.modal .o_list_view tbody .o_list_record_selector').length, 3, + "list view should contain 3 records"); + $('.modal .o_list_view tbody tr:contains(red)').click(); // select red + assert.ok(!$('.modal .o_list_view').length, "should have closed the modal"); + assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 3, + 'kanban should now contain 3 records'); + assert.ok(form.$('.o_kanban_record:contains(red)').length, + 'record "red" should be in the kanban'); + + // -> multiple select + form.$('.o_field_many2many .o-kanban-button-new').click(); + assert.ok($('.modal .o_select_button').prop('disabled'), "select button should be disabled"); + assert.strictEqual($('.modal .o_list_view tbody .o_list_record_selector').length, 2, + "list view should contain 2 records"); + $('.modal .o_list_view thead .o_list_record_selector input').click(); // select all + $('.modal .o_select_button').click(); // validate selection + assert.ok(!$('.modal .o_select_button').prop('disabled'), "select button should be enabled"); + assert.ok(!$('.modal .o_list_view').length, "should have closed the modal"); + assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 5, + 'kanban should now contain 5 records'); + // -> created record + form.$('.o_field_many2many .o-kanban-button-new').click(); + $('.modal .modal-footer .btn-primary:nth(1)').click(); // click on 'Create' + assert.ok($('.modal .o_form_view.o_form_editable').length, + "should have opened a form view in edit mode, in a modal"); + $('.modal .o_form_view input').val('A new type').trigger('input'); + $('.modal:nth(1) footer .btn-primary:first()').click(); // click on 'Save & Close' + assert.ok(!$('.modal').length, "should have closed both modals"); + assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 6, + 'kanban should now contain 6 records'); + assert.ok(form.$('.o_kanban_record:contains(A new type)').length, + 'the newly created type should be in the kanban'); + + // delete subrecords + form.$('.o_kanban_record:contains(silver)').click(); + assert.strictEqual($('.modal .modal-footer .o_btn_remove').length, 1, + 'There should be a modal having Remove Button'); + $('.modal .modal-footer .o_btn_remove').click(); // Click Remove Button of Formview Dialog + assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 5, + 'should contain 5 records'); + assert.ok(!form.$('.o_kanban_record:contains(silver)').length, + 'the removed record should not be in kanban anymore'); + + form.$('.o_kanban_record:contains(blue) .delete_icon').click(); + assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 4, + 'should contain 4 records'); + assert.ok(!form.$('.o_kanban_record:contains(blue)').length, + 'the removed record should not be in kanban anymore'); + + // save the record + form.$buttons.find('.o_form_button_save').click(); + form.destroy(); + }); + + QUnit.test('many2many kanban(editable): properly handle create_text node option', function (assert) { + assert.expect(1); + + this.data.partner.records[0].timmy = [12]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '
' + + '' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
', + res_id: 1, + }); + + form.$buttons.find('.o_form_button_edit').click(); + assert.strictEqual(form.$('.o_field_many2many[name="timmy"] .o-kanban-button-new').text().trim(), + "Add timmy", "In M2M Kanban, Add button should have 'Add timmy' label"); + + form.destroy(); + }); + + QUnit.test('many2many kanban: create action disabled', function (assert) { + assert.expect(4); + + this.data.partner.records[0].timmy = [12, 14]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + archs: { + 'partner_type,false,list': '', + 'partner_type,false,search': '' + + '' + + '', + }, + res_id: 1, + session: { user_context: {} }, + }); + + assert.ok(!form.$('.o-kanban-button-new').length, + '"Add" button should not be available in readonly'); + + form.$buttons.find('.o_form_button_edit').click(); + + assert.ok(form.$('.o-kanban-button-new').length, + '"Add" button should be available in edit'); + assert.ok(form.$('.o_kanban_view .delete_icon').length, + 'delete icon should be visible in edit'); + + form.$('.o-kanban-button-new').click(); // click on 'Add' + assert.strictEqual($('.modal .modal-footer .btn-primary').length, 1, // only button 'Select' + '"Create" button should not be available in the modal'); + + form.destroy(); + }); + + QUnit.test('many2many list (non editable): edition', function (assert) { + assert.expect(27); + + this.data.partner.records[0].timmy = [12, 14]; + this.data.partner_type.records.push({ id: 15, display_name: "bronze", color: 6 }); + this.data.partner_type.fields.float_field = { string: 'Float', type: 'float' }; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + archs: { + 'partner_type,false,list': '', + 'partner_type,false,search': '', + }, + res_id: 1, + mockRPC: function (route, args) { + if (args.method !== 'load_views') { + assert.step(_.last(route.split('/'))); + } + if (args.method === 'write' && args.model === 'partner') { + assert.deepEqual(args.args[1].timmy, [ + [6, false, [12, 15]], + ]); + } + return this._super.apply(this, arguments); + }, + }); + + assert.ok(!form.$('.o_list_record_remove').length, + 'delete icon should not be visible in readonly'); + assert.ok(!form.$('.o_field_x2many_list_row_add').length, + '"Add an item" should not be visible in readonly'); + + form.$buttons.find('.o_form_button_edit').click(); + + assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 2, + 'should contain 2 records'); + assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'gold', + 'display_name of first subrecord should be the one in DB'); + assert.ok(form.$('.o_list_record_remove').length, + 'delete icon should be visible in edit'); + assert.ok(form.$('.o_field_x2many_list_row_add').length, + '"Add an item" should be visible in edit'); + + // edit existing subrecord + form.$('.o_list_view tbody tr:first()').click(); + + $('.modal .o_form_view input').val('new name').trigger('input'); + $('.modal .modal-footer .btn-primary').click(); // save + assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name', + 'value of subrecord should have been updated'); + + // add new subrecords + form.$('.o_field_x2many_list_row_add a').click(); + assert.strictEqual($('.modal .o_list_view').length, 1, + "a modal should be open"); + assert.strictEqual($('.modal .o_list_view .o_data_row').length, 1, + "the list should contain one row"); + $('.modal .o_list_view .o_data_row').click(); // select a record + assert.strictEqual($('.modal .o_list_view').length, 0, + "the modal should be closed"); + assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 3, + 'should contain 3 subrecords'); + + // remove subrecords + form.$('.o_list_record_remove:nth(1)').click(); + assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 2, + 'should contain 2 subrecords'); + assert.strictEqual(form.$('.o_list_view .o_data_row td:first').text(), 'new name', + 'the updated row still has the correct values'); + + // save + form.$buttons.find('.o_form_button_save').click(); + assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 2, + 'should contain 2 subrecords'); + assert.strictEqual(form.$('.o_list_view .o_data_row td:first').text(), + 'new name', 'the updated row still has the correct values'); + + assert.verifySteps([ + 'read', // main record + 'read', // relational field + 'read', // relational record in dialog + 'write', // save relational record from dialog + 'read', // relational field (updated) + 'search_read', // list view in dialog + 'read', // relational field (updated) + 'write', // save main record + 'read', // main record + 'read', // relational field + ]); + + form.destroy(); + }); + + QUnit.test('many2many list (editable): edition', function (assert) { + assert.expect(31); + + this.data.partner.records[0].timmy = [12, 14]; + this.data.partner_type.records.push({ id: 15, display_name: "bronze", color: 6 }); + this.data.partner_type.fields.float_field = { string: 'Float', type: 'float' }; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + archs: { + 'partner_type,false,list': '', + 'partner_type,false,search': '', + }, + mockRPC: function (route, args) { + if (args.method !== 'load_views') { + assert.step(_.last(route.split('/'))); + } + if (args.method === 'write') { + assert.deepEqual(args.args[1].timmy, [ + [6, false, [12, 15]], + [1, 12, { display_name: 'new name' }], + ]); + } + return this._super.apply(this, arguments); + }, + res_id: 1, + }); + + assert.ok(!form.$('.o_list_record_remove').length, + 'delete icon should not be visible in readonly'); + assert.ok(!form.$('.o_field_x2many_list_row_add').length, + '"Add an item" should not be visible in readonly'); + + form.$buttons.find('.o_form_button_edit').click(); + + assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 2, + 'should contain 2 records'); + assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'gold', + 'display_name of first subrecord should be the one in DB'); + assert.ok(form.$('.o_list_record_remove').length, + 'delete icon should be visible in edit'); + assert.ok(form.$('td.o_list_record_remove button').first().hasClass('fa fa-times'), + "should have X icons to remove (unlink) records"); + assert.ok(form.$('.o_field_x2many_list_row_add').length, + '"Add an item" should not visible in edit'); + + // edit existing subrecord + form.$('.o_list_view tbody td:first()').click(); + assert.ok(!$('.modal').length, + 'in edit, clicking on a subrecord should not open a dialog'); + assert.ok(form.$('.o_list_view tbody tr:first()').hasClass('o_selected_row'), + 'first row should be in edition'); + form.$('.o_list_view input:first()').val('new name').trigger('input'); + assert.ok(form.$('.o_list_view .o_data_row:first').hasClass('o_selected_row'), + 'first row should still be in edition'); + assert.strictEqual(form.$('.o_list_view input[name=display_name]').get(0), + document.activeElement, 'edited field should still have the focus'); + form.$el.click(); // click outside the list to validate the row + assert.ok(!form.$('.o_list_view tbody tr:first').hasClass('o_selected_row'), + 'first row should not be in edition anymore'); + assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name', + 'value of subrecord should have been updated'); + assert.verifySteps(['read', 'read']); + + // add new subrecords + form.$('.o_field_x2many_list_row_add a').click(); + assert.strictEqual($('.modal .o_list_view').length, 1, + "a modal should be open"); + assert.strictEqual($('.modal .o_list_view .o_data_row').length, 1, + "the list should contain one row"); + $('.modal .o_list_view .o_data_row').click(); // select a record + assert.strictEqual($('.modal .o_list_view').length, 0, + "the modal should be closed"); + assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 3, + 'should contain 3 subrecords'); + + // remove subrecords + form.$('.o_list_record_remove:nth(1)').click(); + assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 2, + 'should contain 2 subrecord'); + assert.strictEqual(form.$('.o_list_view tbody .o_data_row td:first').text(), + 'new name', 'the updated row still has the correct values'); + + // save + form.$buttons.find('.o_form_button_save').click(); + assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 2, + 'should contain 2 subrecords'); + assert.strictEqual(form.$('.o_list_view .o_data_row td:first').text(), + 'new name', 'the updated row still has the correct values'); + + assert.verifySteps([ + 'read', // main record + 'read', // relational field + 'search_read', // list view in dialog + 'read', // relational field (updated) + 'write', // save main record + 'read', // main record + 'read', // relational field + ]); + + form.destroy(); + }); + + QUnit.test('many2many: create & delete attributes', function (assert) { + assert.expect(4); + + this.data.partner.records[0].timmy = [12, 14]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + form.$buttons.find('.o_form_button_edit').click(); + + assert.strictEqual(form.$('.o_field_x2many_list_row_add').length, 1, "should have the 'Add an item' link"); + assert.strictEqual(form.$('.o_list_record_remove').length, 2, "should have the 'Add an item' link"); + + form.destroy(); + + form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + form.$buttons.find('.o_form_button_edit').click(); + + assert.strictEqual(form.$('.o_field_x2many_list_row_add').length, 0, "should not have the 'Add an item' link"); + assert.strictEqual(form.$('.o_list_record_remove').length, 0, "should not have the 'Add an item' link"); + + form.destroy(); + }); + + QUnit.test('many2many list: create action disabled', function (assert) { + assert.expect(2); + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + assert.ok(!form.$('.o_field_x2many_list_row_add').length, + '"Add an item" link should not be available in readonly'); + + form.$buttons.find('.o_form_button_edit').click(); + + assert.ok(!form.$('.o_field_x2many_list_row_add').length, + '"Add an item" link should not be available in edit either'); + + form.destroy(); + }); + + QUnit.test('many2many list: list of id as default value', function (assert) { + assert.expect(1); + + this.data.partner.fields.turtles.default = [2, 3]; + this.data.partner.fields.turtles.type = "many2many"; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + }); + + assert.strictEqual(form.$('td.o_data_cell').text(), "blipkawa", + "should have loaded default data"); + + form.destroy(); + }); + + QUnit.test('many2many checkboxes with default values', function (assert) { + assert.expect(7); + + this.data.partner.fields.turtles.default = [3]; + this.data.partner.fields.turtles.type = "many2many"; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '
', + mockRPC: function (route, args) { + if (args.method === 'create') { + assert.deepEqual(args.args[0].turtles, [[6, false, [1]]], + "correct values should have been sent to create"); + } + return this._super.apply(this, arguments); + } + }); + + assert.notOk(form.$('.custom-checkbox input').eq(0).prop('checked'), + "first checkbox should not be checked"); + assert.notOk(form.$('.custom-checkbox input').eq(1).prop('checked'), + "second checkbox should not be checked"); + assert.ok(form.$('.custom-checkbox input').eq(2).prop('checked'), + "third checkbox should be checked"); + + form.$('.custom-checkbox input:checked').click(); // Uncheck default record + form.$('.custom-checkbox input').first().click(); // Check first record + form.$('.custom-checkbox input').first().click(); // Uncheck first record + form.$('.custom-checkbox input').first().click(); // Recheck first record + + assert.ok(form.$('.custom-checkbox input').eq(0).prop('checked'), + "first checkbox should be checked"); + assert.notOk(form.$('.custom-checkbox input').eq(1).prop('checked'), + "second checkbox should not be checked"); + assert.notOk(form.$('.custom-checkbox input').eq(2).prop('checked'), + "third checkbox should not be checked"); + + form.$buttons.find('.o_form_button_save').click(); + + form.destroy(); + }); + + QUnit.test('many2many checkboxes with default values', function (assert) { + assert.expect(7); + + this.data.partner.fields.turtles.default = [3]; + this.data.partner.fields.turtles.type = "many2many"; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '
', + mockRPC: function (route, args) { + if (args.method === 'create') { + assert.deepEqual(args.args[0].turtles, [[6, false, [1]]], + "correct values should have been sent to create"); + } + return this._super.apply(this, arguments); + } + }); + + assert.notOk(form.$('.custom-checkbox input').eq(0).prop('checked'), + "first checkbox should not be checked"); + assert.notOk(form.$('.custom-checkbox input').eq(1).prop('checked'), + "second checkbox should not be checked"); + assert.ok(form.$('.custom-checkbox input').eq(2).prop('checked'), + "third checkbox should be checked"); + + form.$('.custom-checkbox input:checked').click(); // Uncheck default record + form.$('.custom-checkbox input').first().click(); // Check first record + form.$('.custom-checkbox input').first().click(); // Uncheck first record + form.$('.custom-checkbox input').first().click(); // Recheck first record + + assert.ok(form.$('.custom-checkbox input').eq(0).prop('checked'), + "first checkbox should be checked"); + assert.notOk(form.$('.custom-checkbox input').eq(1).prop('checked'), + "second checkbox should not be checked"); + assert.notOk(form.$('.custom-checkbox input').eq(2).prop('checked'), + "third checkbox should not be checked"); + + form.$buttons.find('.o_form_button_save').click(); + + form.destroy(); + }); + + QUnit.test('many2many list with x2many: add a record', function (assert) { + assert.expect(18); + + this.data.partner_type.fields.m2m = { + string: "M2M", type: "many2many", relation: 'turtle', + }; + this.data.partner_type.records[0].m2m = [1, 2]; + this.data.partner_type.records[1].m2m = [2, 3]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '', + res_id: 1, + archs: { + 'partner_type,false,list': '' + + '' + + '' + + '', + 'partner_type,false,search': '' + + '' + + '', + }, + mockRPC: function (route, args) { + if (args.method !== 'load_views') { + assert.step(_.last(route.split('/')) + ' on ' + args.model); + } + if (args.model === 'turtle') { + assert.step(args.args[0]); // the read ids + } + return this._super.apply(this, arguments); + }, + viewOptions: { + mode: 'edit', + }, + }); + + form.$('.o_field_x2many_list_row_add a').click(); + $('.modal .o_data_row:first').click(); // add a first record to the relation + + assert.strictEqual(form.$('.o_data_row').length, 1, + "the record should have been added to the relation"); + assert.strictEqual(form.$('.o_data_row:first .o_badge_text').text(), 'leonardodonatello', + "inner m2m should have been fetched and correctly displayed"); + + form.$('.o_field_x2many_list_row_add a').click(); + $('.modal .o_data_row:first').click(); // add a second record to the relation + + assert.strictEqual(form.$('.o_data_row').length, 2, + "the second record should have been added to the relation"); + assert.strictEqual(form.$('.o_data_row:nth(1) .o_badge_text').text(), 'donatelloraphael', + "inner m2m should have been fetched and correctly displayed"); + + assert.verifySteps([ + 'read on partner', + 'search_read on partner_type', + 'read on turtle', + [1, 2, 3], + 'read on partner_type', + 'read on turtle', + [1, 2], + 'search_read on partner_type', + 'read on turtle', + [2, 3], + 'read on partner_type', + 'read on turtle', + [2, 3], + ]); + + form.destroy(); + }); + + QUnit.test('many2many with a domain', function (assert) { + // The domain specified on the field should not be replaced by the potential + // domain the user writes in the dialog, they should rather be concatenated + assert.expect(2); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '', + res_id: 1, + archs: { + 'partner_type,false,list': '' + + '' + + '', + 'partner_type,false,search': '' + + '' + + '', + }, + viewOptions: { + mode: 'edit', + }, + }); + + form.$('.o_field_x2many_list_row_add a').click(); + assert.strictEqual($('.modal .o_data_row').length, 1, + "should contain only one row (gold)"); + + $('.modal .o_searchview_input').trigger({ type: 'keypress', which: 115 }); // s + $('.modal .o_searchview_input').trigger({ type: 'keydown', which: 13 }); // enter + + assert.strictEqual($('.modal .o_data_row').length, 0, "should contain no row"); + + form.destroy(); + }); + + QUnit.test('many2many list with onchange and edition of a record', function (assert) { + assert.expect(8); + + this.data.partner.fields.turtles.type = "many2many"; + this.data.partner.onchanges.turtles = function () { }; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + archs: { + 'turtle,false,form': '
', + }, + mockRPC: function (route, args) { + assert.step(args.method); + return this._super.apply(this, arguments); + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('td.o_data_cell:first').click(); + + $('.modal-body input[type="checkbox"]').click(); + $('.modal .modal-footer .btn-primary').first().click(); + + // there is nothing left to save -> should not do a 'write' RPC + form.$buttons.find('.o_form_button_save').click(); + + assert.verifySteps([ + 'read', // read initial record (on partner) + 'read', // read many2many turtles + 'load_views', // load arch of turtles form view + 'read', // read missing field when opening record in modal form view + 'write', // when saving the modal + 'onchange', // onchange should be triggered on partner + 'read', // reload many2many + ]); + + form.destroy(); + }); + + QUnit.test('onchange with 40+ commands for a many2many', function (assert) { + // this test ensures that the basic_model correctly handles more LINK_TO + // commands than the limit of the dataPoint (40 for x2many kanban) + assert.expect(23); + + // create a lot of partner_types that will be linked by the onchange + var commands = [[5]]; + for (var i = 0; i < 45; i++) { + var id = 100 + i; + this.data.partner_type.records.push({ id: id, display_name: "type " + id }); + commands.push([4, id]); + } + this.data.partner.onchanges = { + foo: function (obj) { + obj.timmy = commands; + }, + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '
' + + '
' + + '
' + + '
' + + '', + res_id: 1, + mockRPC: function (route, args) { + assert.step(args.method); + if (args.method === 'write') { + assert.strictEqual(args.args[1].timmy[0][0], 6, + "should send a command 6"); + assert.strictEqual(args.args[1].timmy[0][2].length, 45, + "should replace with 45 ids"); + } + return this._super.apply(this, arguments); + }, + viewOptions: { + mode: 'edit', + }, + }); + + assert.verifySteps(['read']); + + form.$('.o_field_widget[name=foo]').val('trigger onchange').trigger('input'); + + assert.verifySteps(['read', 'onchange', 'read']); + assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(), + '1-40 / 45', "pager should be correct"); + assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40, + 'there should be 40 records displayed on page 1'); + + form.$('.o_pager_next').click(); + assert.verifySteps(['read', 'onchange', 'read', 'read']); + assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(), + '41-45 / 45', "pager should be correct"); + assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 5, + 'there should be 5 records displayed on page 2'); + + form.$buttons.find('.o_form_button_save').click(); + + assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(), + '1-40 / 45', "pager should be correct"); + assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40, + 'there should be 40 records displayed on page 1'); + + form.$('.o_pager_next').click(); + assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(), + '41-45 / 45', "pager should be correct"); + assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 5, + 'there should be 5 records displayed on page 2'); + + form.$('.o_pager_next').click(); // back to page 1 + assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(), + '1-40 / 45', "pager should be correct"); + assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40, + 'there should be 40 records displayed on page 1'); + + form.destroy(); + }); + + QUnit.test('default_get, onchange, onchange on m2m', function (assert) { + assert.expect(1); + + this.data.partner.onchanges.int_field = function (obj) { + if (obj.int_field === 2) { + assert.deepEqual(obj.timmy, [ + [6, false, [12]], + [1, 12, { display_name: 'gold' }] + ]); + } + obj.timmy = [ + [5], + [1, 12, { display_name: 'gold' }] + ]; + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + }); + + form.$('.o_field_widget[name=int_field]').val(2).trigger('input'); + form.destroy(); + }); + + QUnit.test('widget many2many_tags', function (assert) { + assert.expect(1); + this.data.turtle.records[0].partner_ids = [2]; + + var form = createView({ + View: FormView, + model: 'turtle', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + assert.deepEqual( + form.$('.o_field_many2manytags.o_field_widget .badge .o_badge_text').attr('title'), + 'second record', 'the title should be filled in' + ); + + form.destroy(); + }); + + QUnit.test('many2many list add *many* records, remove, re-add', function (assert) { + assert.expect(5); + + this.data.partner.fields.timmy.domain = [['color', '=', 2]]; + this.data.partner.fields.timmy.onChange = true; + this.data.partner_type.fields.product_ids = { string: "Product", type: "many2many", relation: 'product' }; + + for (var i = 0; i < 50; i++) { + var new_record_partner_type = { id: 100 + i, display_name: "batch" + i, color: 2 }; + this.data.partner_type.records.push(new_record_partner_type); + } + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + archs: { + 'partner_type,false,list': '', + 'partner_type,false,search': '', + }, + mockRPC: function (route, args) { + if (args.method === 'get_formview_id') { + assert.deepEqual(args.args[0], [1], "should call get_formview_id with correct id"); + return $.when(false); + } + return this._super(route, args); + }, + }); + + // First round: add 51 records in batch + form.$buttons.find('.btn.btn-primary.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + + var $modal = $('.modal-lg'); + + assert.equal($modal.length, 1, + 'There should be one modal'); + + $modal.find('thead input[type=checkbox]').click(); //select all the records we created in batch + 'gold' + + $modal.find('.btn.btn-primary.o_select_button').click(); + + var m2m_records = form.$('.o_field_many2many.o_field_widget.o_field_x2many.o_field_x2many_list .o_data_cell:not(.o_many2many_tags_cell)'); + assert.equal(m2m_records.length, 51, + 'We should have added all the records present in the search view to the m2m field'); // the 50 in batch + 'gold' + + form.$buttons.find('.btn.btn-primary.o_form_button_save').click(); + + // Secound round: remove one record + form.$buttons.find('.btn.btn-primary.o_form_button_edit').click(); + var trash_buttons = form.$('.o_field_many2many.o_field_widget.o_field_x2many.o_field_x2many_list .o_list_record_remove'); + + trash_buttons.first().click(); + + var pager_limit = form.$('.o_field_many2many.o_field_widget.o_field_x2many.o_field_x2many_list .o_pager_limit'); + assert.equal(pager_limit.text(), '50', + 'We should have 50 records in the m2m field'); + + // Third round: re-add 1 records + form.$('.o_field_x2many_list_row_add a').click(); + + $modal = $('.modal-lg'); + + assert.equal($modal.length, 1, + 'There should be one modal'); + + $modal.find('thead input[type=checkbox]').click(); + + $modal.find('.btn.btn-primary.o_select_button').click(); + + pager_limit = form.$('.o_field_many2many.o_field_widget.o_field_x2many.o_field_x2many_list .o_pager_limit'); + assert.equal(pager_limit.text(), '51', + 'We should have 51 records in the m2m field'); + + form.destroy(); + }); + }); +}); +}); diff --git a/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js b/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js new file mode 100644 index 00000000000..72044b99d2f --- /dev/null +++ b/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js @@ -0,0 +1,3025 @@ +odoo.define('web.field_many_to_one_tests', function (require) { +"use strict"; + +var BasicModel = require('web.BasicModel'); +var concurrency = require('web.concurrency'); +var FormView = require('web.FormView'); +var ListView = require('web.ListView'); +var relationalFields = require('web.relational_fields'); +var StandaloneFieldManagerMixin = require('web.StandaloneFieldManagerMixin'); +var testUtils = require('web.test_utils'); +var Widget = require('web.Widget'); + +var createView = testUtils.createView; + +QUnit.module('fields', {}, function () { + + QUnit.module('relational_fields', { + beforeEach: function () { + this.data = { + partner: { + fields: { + display_name: { string: "Displayed name", type: "char" }, + foo: { string: "Foo", type: "char", default: "My little Foo Value" }, + bar: { string: "Bar", type: "boolean", default: true }, + int_field: { string: "int_field", type: "integer", sortable: true }, + p: { string: "one2many field", type: "one2many", relation: 'partner', relation_field: 'trululu' }, + turtles: { string: "one2many turtle field", type: "one2many", relation: 'turtle', relation_field: 'turtle_trululu' }, + trululu: { string: "Trululu", type: "many2one", relation: 'partner' }, + timmy: { string: "pokemon", type: "many2many", relation: 'partner_type' }, + product_id: { string: "Product", type: "many2one", relation: 'product' }, + color: { + type: "selection", + selection: [['red', "Red"], ['black', "Black"]], + default: 'red', + string: "Color", + }, + date: { string: "Some Date", type: "date" }, + datetime: { string: "Datetime Field", type: 'datetime' }, + user_id: { string: "User", type: 'many2one', relation: 'user' }, + reference: { + string: "Reference Field", type: 'reference', selection: [ + ["product", "Product"], ["partner_type", "Partner Type"], ["partner", "Partner"]] + }, + }, + records: [{ + id: 1, + display_name: "first record", + bar: true, + foo: "yop", + int_field: 10, + p: [], + turtles: [2], + timmy: [], + trululu: 4, + user_id: 17, + reference: 'product,37', + }, { + id: 2, + display_name: "second record", + bar: true, + foo: "blip", + int_field: 9, + p: [], + timmy: [], + trululu: 1, + product_id: 37, + date: "2017-01-25", + datetime: "2016-12-12 10:55:05", + user_id: 17, + }, { + id: 4, + display_name: "aaa", + bar: false, + }], + onchanges: {}, + }, + product: { + fields: { + name: { string: "Product Name", type: "char" } + }, + records: [{ + id: 37, + display_name: "xphone", + }, { + id: 41, + display_name: "xpad", + }] + }, + partner_type: { + fields: { + name: { string: "Partner Type", type: "char" }, + color: { string: "Color index", type: "integer" }, + }, + records: [ + { id: 12, display_name: "gold", color: 2 }, + { id: 14, display_name: "silver", color: 5 }, + ] + }, + turtle: { + fields: { + display_name: { string: "Displayed name", type: "char" }, + turtle_foo: { string: "Foo", type: "char" }, + turtle_bar: { string: "Bar", type: "boolean", default: true }, + turtle_int: { string: "int", type: "integer", sortable: true }, + turtle_trululu: { string: "Trululu", type: "many2one", relation: 'partner' }, + turtle_ref: { + string: "Reference", type: 'reference', selection: [ + ["product", "Product"], ["partner", "Partner"]] + }, + product_id: { string: "Product", type: "many2one", relation: 'product', required: true }, + partner_ids: { string: "Partner", type: "many2many", relation: 'partner' }, + }, + records: [{ + id: 1, + display_name: "leonardo", + turtle_bar: true, + turtle_foo: "yop", + partner_ids: [], + }, { + id: 2, + display_name: "donatello", + turtle_bar: true, + turtle_foo: "blip", + turtle_int: 9, + partner_ids: [2, 4], + }, { + id: 3, + display_name: "raphael", + product_id: 37, + turtle_bar: false, + turtle_foo: "kawa", + turtle_int: 21, + partner_ids: [], + turtle_ref: 'product,37', + }], + onchanges: {}, + }, + user: { + fields: { + name: { string: "Name", type: "char" }, + partner_ids: { string: "one2many partners field", type: "one2many", relation: 'partner', relation_field: 'user_id' }, + }, + records: [{ + id: 17, + name: "Aline", + partner_ids: [1, 2], + }, { + id: 19, + name: "Christine", + }] + }, + }; + } + }, function () { + QUnit.module('FieldMany2One'); + + QUnit.test('many2ones in form views', function (assert) { + assert.expect(5); + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + archs: { + 'partner,false,form': '
', + }, + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'get_formview_action') { + assert.deepEqual(args.args[0], [4], "should call get_formview_action with correct id"); + return $.when({ + res_id: 17, + type: 'ir.actions.act_window', + target: 'current', + res_model: 'res.partner' + }); + } + if (args.method === 'get_formview_id') { + assert.deepEqual(args.args[0], [4], "should call get_formview_id with correct id"); + return $.when(false); + } + return this._super(route, args); + }, + }); + + testUtils.intercept(form, 'do_action', function (event) { + assert.strictEqual(event.data.action.res_id, 17, + "should do a do_action with correct parameters"); + }); + + assert.strictEqual(form.$('a.o_form_uri:contains(aaa)').length, 1, + "should contain a link"); + form.$('a.o_form_uri').click(); // click on the link in readonly mode (should trigger do_action) + + form.$buttons.find('.o_form_button_edit').click(); + + form.$('.o_external_button').click(); // click on the external button (should do an RPC) + + assert.strictEqual($('.modal .modal-title').text().trim(), 'Open: custom label', + "dialog title should display the custom string label"); + + // TODO: test that we can edit the record in the dialog, and that the value is correctly + // updated on close + form.destroy(); + }); + + QUnit.test('editing a many2one, but not changing anything', function (assert) { + assert.expect(2); + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '
', + archs: { + 'partner,false,form': '
', + }, + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'get_formview_id') { + assert.deepEqual(args.args[0], [4], "should call get_formview_id with correct id"); + return $.when(false); + } + return this._super(route, args); + }, + viewOptions: { + ids: [1, 2], + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + + // click on the external button (should do an RPC) + form.$('.o_external_button').click(); + // save and close modal + $('.modal .modal-footer .btn-primary:first').click(); + // save form + form.$buttons.find('.o_form_button_save').click(); + // click next on pager + form.pager.$('.o_pager_next').click(); + + // this checks that the view did not ask for confirmation that the + // record is dirty + assert.strictEqual(form.pager.$el.text().trim(), '2 / 2', + 'pager should be at second page'); + form.destroy(); + }); + + QUnit.test('context in many2one and default get', function (assert) { + assert.expect(1); + + this.data.partner.fields.int_field.default = 14; + this.data.partner.fields.trululu.default = 2; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '', + mockRPC: function (route, args) { + if (args.method === 'name_get') { + assert.strictEqual(args.kwargs.context.blip, 14, + 'context should have been properly sent to the nameget rpc'); + } + return this._super(route, args); + }, + }); + form.destroy(); + }); + + QUnit.test('editing a many2one (with form view opened with external button)', function (assert) { + assert.expect(1); + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '
', + archs: { + 'partner,false,form': '
', + }, + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'get_formview_id') { + return $.when(false); + } + return this._super(route, args); + }, + viewOptions: { + ids: [1, 2], + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + + // click on the external button (should do an RPC) + form.$('.o_external_button').click(); + + $('.modal input[name="foo"]').val('brandon').trigger('input'); + + // save and close modal + $('.modal .modal-footer .btn-primary:first').click(); + // save form + form.$buttons.find('.o_form_button_save').click(); + // click next on pager + form.pager.$('.o_pager_next').click(); + + // this checks that the view did not ask for confirmation that the + // record is dirty + assert.strictEqual(form.pager.$el.text().trim(), '2 / 2', + 'pager should be at second page'); + form.destroy(); + }); + + QUnit.test('many2ones in form views with show_adress', function (assert) { + assert.expect(4); + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + mockRPC: function (route, args) { + if (args.method === 'name_get') { + return this._super(route, args).then(function (result) { + result[0][1] += '\nStreet\nCity ZIP'; + return result; + }); + } + return this._super(route, args); + }, + res_id: 1, + }); + + assert.strictEqual($('a.o_form_uri').html(), 'aaa
Street
City ZIP', + "input should have a multi-line content in readonly due to show_address"); + form.$buttons.find('.o_form_button_edit').click(); + assert.strictEqual(form.$('button.o_external_button:visible').length, 1, + "should have an open record button"); + + form.$('input.o_input').click(); + + assert.strictEqual(form.$('button.o_external_button:visible').length, 1, + "should still have an open record button"); + form.$('input.o_input').trigger('focusout'); + assert.strictEqual($('.modal button:contains(Create and edit)').length, 0, + "there should not be a quick create modal"); + + form.destroy(); + }); + + QUnit.test('show_address works in a view embedded in a view of another type', function (assert) { + assert.expect(1); + + this.data.turtle.records[1].turtle_trululu = 2; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '', + res_id: 1, + archs: { + "turtle,false,form": '
' + + '' + + '' + + '', + "turtle,false,list": '' + + '' + + '', + }, + mockRPC: function (route, args) { + if (args.method === 'name_get') { + return this._super(route, args).then(function (result) { + if (args.model === 'partner' && args.kwargs.context.show_address) { + result[0][1] += '\nrue morgue\nparis 75013'; + } + return result; + }); + } + return this._super(route, args); + }, + }); + // click the turtle field, opens a modal with the turtle form view + form.$('.o_data_row:first td.o_data_cell').click(); + + assert.strictEqual($('[name="turtle_trululu"]').text(), "second recordrue morgueparis 75013", + "The partner's address should be displayed"); + form.destroy(); + }); + + QUnit.test('many2one data is reloaded if there is a context to take into account', function (assert) { + assert.expect(1); + + this.data.turtle.records[1].turtle_trululu = 2; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '', + res_id: 1, + archs: { + "turtle,false,form": '
' + + '' + + '' + + '', + "turtle,false,list": '' + + '' + + '' + + '', + }, + mockRPC: function (route, args) { + if (args.method === 'name_get') { + return this._super(route, args).then(function (result) { + if (args.model === 'partner' && args.kwargs.context.show_address) { + result[0][1] += '\nrue morgue\nparis 75013'; + } + return result; + }); + } + return this._super(route, args); + }, + }); + // click the turtle field, opens a modal with the turtle form view + form.$('.o_data_row:first').click(); + + assert.strictEqual($('.modal [name=turtle_trululu]').text(), "second recordrue morgueparis 75013", + "The partner's address should be displayed"); + form.destroy(); + }); + + QUnit.test('many2ones in form views with search more', function (assert) { + assert.expect(3); + this.data.partner.records.push({ + id: 5, + display_name: "Partner 4", + }, { + id: 6, + display_name: "Partner 5", + }, { + id: 7, + display_name: "Partner 6", + }, { + id: 8, + display_name: "Partner 7", + }, { + id: 9, + display_name: "Partner 8", + }, { + id: 10, + display_name: "Partner 9", + }); + this.data.partner.fields.datetime.searchable = true; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + archs: { + 'partner,false,list': '', + 'partner,false,search': '', + }, + res_id: 1, + }); + + form.$buttons.find('.o_form_button_edit').click(); + var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); + form.$('.o_field_many2one input').click(); + $dropdown.find('.o_m2o_dropdown_option:contains(Search)').mouseenter().click(); // Open Search More + + assert.strictEqual($('tr.o_data_row').length, 9, "should display 9 records"); + + $('button:contains(Filters)').click(); + $('.o_add_custom_filter:visible').click(); // Add a custom filter, datetime field is selected + assert.strictEqual($('.o_filter_condition select.o_searchview_extended_prop_field').val(), 'datetime', + "datetime field should be selected"); + $('.o_apply_filter').click(); + + assert.strictEqual($('tr.o_data_row').length, 0, "should display 0 records"); + form.destroy(); + }); + + QUnit.test('onchanges on many2ones trigger when editing record in form view', function (assert) { + assert.expect(10); + + this.data.partner.onchanges.user_id = function () { }; + this.data.user.fields.other_field = { string: "Other Field", type: "char" }; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + archs: { + 'user,false,form': '
', + }, + res_id: 1, + mockRPC: function (route, args) { + assert.step(args.method); + if (args.method === 'get_formview_id') { + return $.when(false); + } + if (args.method === 'onchange') { + assert.strictEqual(args.args[1].user_id, 17, + "onchange is triggered with correct user_id"); + } + return this._super(route, args); + }, + }); + + // open the many2one in form view and change something + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_external_button').click(); + $('.modal-body input[name="other_field"]').val('wood').trigger('input'); + + // save the modal and make sure an onchange is triggered + $('.modal .modal-footer .btn-primary').first().click(); + assert.verifySteps(['read', 'get_formview_id', 'load_views', 'read', 'write', 'onchange', 'read']); + + // save the main record, and check that no extra rpcs are done (record + // is not dirty, only a related record was modified) + form.$buttons.find('.o_form_button_save').click(); + assert.verifySteps(['read', 'get_formview_id', 'load_views', 'read', 'write', 'onchange', 'read']); + form.destroy(); + }); + + QUnit.test('many2one fields with option "no_open"', function (assert) { + assert.expect(3); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + assert.strictEqual(form.$('span.o_field_widget[name=trululu]').length, 1, + "should be displayed inside a span (sanity check)"); + assert.strictEqual(form.$('span.o_form_uri').length, 0, "should not have an anchor"); + + form.$buttons.find('.o_form_button_edit').click(); + assert.strictEqual(form.$('.o_field_widget[name=trululu] .o_external_button').length, + 0, "should not have the button to open the record"); + + form.destroy(); + }); + + QUnit.test('many2one in edit mode', function (assert) { + assert.expect(16); + + // create 10 partners to have the 'Search More' option in the autocomplete dropdown + for (var i = 0; i < 10; i++) { + var id = 20 + i; + this.data.partner.records.push({ id: id, display_name: "Partner " + id }); + } + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + archs: { + 'partner,false,list': '', + 'partner,false,search': '' + + '' + + '', + }, + mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/partner/write') { + assert.strictEqual(args.args[1].trululu, 20, "should write the correct id"); + } + return this._super.apply(this, arguments); + }, + }); + + // the SelectCreateDialog requests the session, so intercept its custom + // event to specify a fake session to prevent it from crashing + testUtils.intercept(form, 'get_session', function (event) { + event.data.callback({ user_context: {} }); + }); + + form.$buttons.find('.o_form_button_edit').click(); + var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); + + form.$('.o_field_many2one input').click(); + assert.ok($dropdown.is(':visible'), + 'clicking on the m2o input should open the dropdown if it is not open yet'); + assert.strictEqual($dropdown.find('li:not(.o_m2o_dropdown_option)').length, 7, + 'autocomplete should contains 7 suggestions'); + assert.strictEqual($dropdown.find('li.o_m2o_dropdown_option').length, 2, + 'autocomplete should contain "Search More" and Create and Edit..."'); + + form.$('.o_field_many2one input').click(); + assert.ok(!$dropdown.is(':visible'), + 'clicking on the m2o input should close the dropdown if it is open'); + + // change the value of the m2o with a suggestion of the dropdown + form.$('.o_field_many2one input').click(); + $dropdown.find('li:first()').click(); + assert.ok(!$dropdown.is(':visible'), 'clicking on a value should close the dropdown'); + assert.strictEqual(form.$('.o_field_many2one input').val(), 'first record', + 'value of the m2o should have been correctly updated'); + + // change the value of the m2o with a record in the 'Search More' modal + form.$('.o_field_many2one input').click(); + // click on 'Search More' (mouseenter required by ui-autocomplete) + $dropdown.find('.o_m2o_dropdown_option:contains(Search)').mouseenter().click(); + assert.ok($('.modal .o_list_view').length, "should have opened a list view in a modal"); + assert.ok(!$('.modal .o_list_view .o_list_record_selector').length, + "there should be no record selector in the list view"); + assert.ok(!$('.modal .modal-footer .o_select_button').length, + "there should be no 'Select' button in the footer"); + assert.ok($('.modal tbody tr').length > 10, "list should contain more than 10 records"); + // filter the list using the searchview + $('.modal .o_searchview_input').trigger({ type: 'keypress', which: 80 }); // P + $('.modal .o_searchview_input').trigger({ type: 'keydown', which: 13 }); // enter + assert.strictEqual($('.modal tbody tr').length, 10, + "list should be restricted to records containing a P (10 records)"); + // choose a record + $('.modal tbody tr:contains(Partner 20)').click(); // choose record 'Partner 20' + assert.ok(!$('.modal').length, "should have closed the modal"); + assert.ok(!$dropdown.is(':visible'), 'should have closed the dropdown'); + assert.strictEqual(form.$('.o_field_many2one input').val(), 'Partner 20', + 'value of the m2o should have been correctly updated'); + + // save + form.$buttons.find('.o_form_button_save').click(); + assert.strictEqual(form.$('a.o_form_uri').text(), 'Partner 20', + "should display correct value after save"); + + form.destroy(); + }); + + QUnit.test('many2one in non edit mode', function (assert) { + assert.expect(3); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '', + res_id: 1, + }); + + assert.strictEqual(form.$('a.o_form_uri').length, 1, + "should display 1 m2o link in form"); + assert.strictEqual(form.$('a.o_form_uri').attr('href'), "#id=4&model=partner", + "href should contain id and model"); + + // Remove value from many2one and then save, there should not have href with id and model on m2o anchor + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_many2one input').val('').trigger('keyup').trigger('focusout'); + form.$buttons.find('.o_form_button_save').click(); + + assert.strictEqual(form.$('a.o_form_uri').attr('href'), "#", + "href should have #"); + + form.destroy(); + }); + + QUnit.test('many2one searches with correct value', function (assert) { + var done = assert.async(); + assert.expect(6); + + var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '
', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'name_search') { + assert.step('search: ' + args.kwargs.name); + } + return this._super.apply(this, arguments); + }, + viewOptions: { + mode: 'edit', + }, + }); + + assert.strictEqual(form.$('.o_field_many2one input').val(), 'aaa', + "should be initially set to 'aaa'"); + + form.$('.o_field_many2one input').click(); // should search with '' + // unset the many2one -> should search again with '' + form.$('.o_field_many2one input').val('').trigger('keydown'); + concurrency.delay(0).then(function () { + // write 'p' -> should search with 'p' + form.$('.o_field_many2one input').val('p').trigger('keydown').trigger('keyup'); + + return concurrency.delay(0); + }).then(function () { + // close and re-open the dropdown -> should search with 'p' again + form.$('.o_field_many2one input').click(); + form.$('.o_field_many2one input').click(); + + assert.verifySteps(['search: ', 'search: ', 'search: p', 'search: p']); + + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + form.destroy(); + done(); + }); + }); + + QUnit.test('many2one field with option always_reload', function (assert) { + assert.expect(4); + var count = 0; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '', + res_id: 2, + mockRPC: function (route, args) { + if (args.method === 'name_get') { + count++; + return $.when([[1, "first record\nand some address"]]); + } + return this._super(route, args); + }, + }); + + assert.strictEqual(count, 1, "an extra name_get should have been done"); + assert.ok(form.$('a:contains(and some address)').length, + "should display additional result"); + + form.$buttons.find('.o_form_button_edit').click(); + + assert.strictEqual(form.$('input').val(), "first record", + "actual field value should be displayed to be edited"); + + form.$buttons.find('.o_form_button_save').click(); + + assert.ok(form.$('a:contains(and some address)').length, + "should still display additional result"); + form.destroy(); + }); + + QUnit.test('many2one field and list navigation', function (assert) { + assert.expect(3); + + var list = createView({ + View: ListView, + model: 'partner', + data: this.data, + arch: '', + }); + + // edit first input, to trigger autocomplete + list.$('.o_data_row .o_data_cell').first().click(); + list.$('.o_data_cell input').val('').trigger('input'); + + // press keydown, to select first choice + list.$('.o_data_cell input').focus().trigger($.Event('keydown', { + which: $.ui.keyCode.DOWN, + keyCode: $.ui.keyCode.DOWN, + })); + + // we now check that the dropdown is open (and that the focus did not go + // to the next line) + var $dropdown = list.$('.o_field_many2one input').autocomplete('widget'); + assert.ok($dropdown.is(':visible'), "dropdown should be visible"); + assert.ok(list.$('.o_data_row:eq(0)').hasClass('o_selected_row'), + 'first data row should still be selected'); + assert.ok(!list.$('.o_data_row:eq(1)').hasClass('o_selected_row'), + 'second data row should not be selected'); + + list.destroy(); + }); + + QUnit.test('standalone many2one field', function (assert) { + assert.expect(4); + var done = assert.async(); + + var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; + + var fixture = $('#qunit-fixture'); + var self = this; + + var model = testUtils.createModel({ + Model: BasicModel, + data: this.data, + }); + + model.makeRecord('coucou', [{ + name: 'partner_id', + relation: 'partner', + type: 'many2one', + value: [1, 'first partner'], + }]).then(function (recordID) { + var record = model.get(recordID); + // create a new widget that uses the StandaloneFieldManagerMixin + var StandaloneWidget = Widget.extend(StandaloneFieldManagerMixin, { + init: function (parent) { + this._super.apply(this, arguments); + StandaloneFieldManagerMixin.init.call(this, parent); + }, + }); + var parent = new StandaloneWidget(model); + testUtils.addMockEnvironment(parent, { + data: self.data, + mockRPC: function (route, args) { + assert.step(args.method); + return this._super.apply(this, arguments); + }, + }); + + var relField = new relationalFields.FieldMany2One(parent, 'partner_id', record, { + mode: 'edit', + noOpen: true, + }); + + relField.appendTo(fixture); + $('input.o_input').val('xyzzrot').trigger('input'); + + concurrency.delay(0).then(function () { + var $dropdown = $('input.o_input').autocomplete('widget'); + $dropdown.find('.o_m2o_dropdown_option:contains(Create)') + .first().mouseenter().click(); + + assert.strictEqual(relField.$('.o_external_button').length, 0, + "should not have the button to open the record"); + assert.verifySteps(['name_search', 'name_create']); + + parent.destroy(); + model.destroy(); + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + done(); + }); + }); + }); + + // QUnit.test('onchange on a many2one to a different model', function (assert) { + // This test is commented because the mock server does not give the correct response. + // It should return a couple [id, display_name], but I don't know the logic used + // by the server, so it's hard to emulate it correctly + // assert.expect(2); + + // this.data.partner.records[0].product_id = 41; + // this.data.partner.onchanges = { + // foo: function(obj) { + // obj.product_id = 37; + // }, + // }; + + // var form = createView({ + // View: FormView, + // model: 'partner', + // data: this.data, + // arch: '
' + + // '' + + // '' + + // '', + // res_id: 1, + // }); + // form.$buttons.find('.o_form_button_edit').click(); + // assert.strictEqual(form.$('input').eq(1).val(), 'xpad', "initial product_id val should be xpad"); + + // form.$('input').eq(0).val("let us trigger an onchange").trigger('input'); + + // assert.strictEqual(form.$('input').eq(1).val(), 'xphone', "onchange should have been applied"); + // }); + + QUnit.test('form: quick create then save directly', function (assert) { + var done = assert.async(); + assert.expect(5); + + var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; + + var def = $.Deferred(); + var newRecordID; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '', + mockRPC: function (route, args) { + var result = this._super.apply(this, arguments); + if (args.method === 'name_create') { + assert.step('name_create'); + return def.then(_.constant(result)).then(function (nameGet) { + newRecordID = nameGet[0]; + return nameGet; + }); + } + if (args.method === 'create') { + assert.step('create'); + assert.strictEqual(args.args[0].trululu, newRecordID, + "should create with the correct m2o id"); + } + return result; + }, + }); + + var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); + form.$('.o_field_many2one input').val('b').trigger('keydown'); + concurrency.delay(0).then(function () { + $dropdown.find('li:first()').click(); // quick create 'b' + form.$buttons.find('.o_form_button_save').click(); + + assert.verifySteps(['name_create'], + "should wait for the name_create before creating the record"); + + def.resolve(); + + assert.verifySteps(['name_create', 'create']); + + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + form.destroy(); + done(); + }); + }); + + QUnit.test('list: quick create then save directly', function (assert) { + var done = assert.async(); + assert.expect(8); + + var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; + + var def = $.Deferred(); + var newRecordID; + var list = createView({ + View: ListView, + model: 'partner', + data: this.data, + arch: '' + + '' + + '', + mockRPC: function (route, args) { + var result = this._super.apply(this, arguments); + if (args.method === 'name_create') { + assert.step('name_create'); + return def.then(_.constant(result)).then(function (nameGet) { + newRecordID = nameGet[0]; + return nameGet; + }); + } + if (args.method === 'create') { + assert.step('create'); + assert.strictEqual(args.args[0].trululu, newRecordID, + "should create with the correct m2o id"); + } + return result; + }, + }); + + list.$buttons.find('.o_list_button_add').click(); + + var $dropdown = list.$('.o_field_many2one input').autocomplete('widget'); + list.$('.o_field_many2one input').val('b').trigger('keydown'); + concurrency.delay(0).then(function () { + $dropdown.find('li:first()').click(); // quick create 'b' + list.$buttons.find('.o_list_button_add').click(); + + assert.verifySteps(['name_create'], + "should wait for the name_create before creating the record"); + assert.strictEqual(list.$('.o_data_row').length, 4, + "should wait for the name_create before adding the new row"); + + def.resolve(); + + assert.verifySteps(['name_create', 'create']); + assert.strictEqual(list.$('.o_data_row:nth(1) .o_data_cell').text(), 'b', + "created row should have the correct m2o value"); + assert.strictEqual(list.$('.o_data_row').length, 5, + "should have added the fifth row"); + + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + list.destroy(); + done(); + }); + }); + + QUnit.test('list in form: quick create then save directly', function (assert) { + var done = assert.async(); + assert.expect(6); + + var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; + + var def = $.Deferred(); + var newRecordID; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + mockRPC: function (route, args) { + var result = this._super.apply(this, arguments); + if (args.method === 'name_create') { + assert.step('name_create'); + return def.then(_.constant(result)).then(function (nameGet) { + newRecordID = nameGet[0]; + return nameGet; + }); + } + if (args.method === 'create') { + assert.step('create'); + assert.strictEqual(args.args[0].p[0][2].trululu, newRecordID, + "should create with the correct m2o id"); + } + return result; + }, + }); + + form.$('.o_field_x2many_list_row_add a').click(); + + var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); + form.$('.o_field_many2one input').val('b').trigger('keydown'); + concurrency.delay(0).then(function () { + $dropdown.find('li:first()').click(); // quick create 'b' + form.$buttons.find('.o_form_button_save').click(); + + assert.verifySteps(['name_create'], + "should wait for the name_create before creating the record"); + + def.resolve(); + + assert.verifySteps(['name_create', 'create']); + assert.strictEqual(form.$('.o_data_row:first .o_data_cell').text(), 'b', + "first row should have the correct m2o value"); + + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + form.destroy(); + done(); + }); + }); + + QUnit.test('list in form: quick create then add a new line directly', function (assert) { + // required many2one inside a one2many list: directly after quick creating + // a new many2one value (before the name_create returns), click on add an item: + // at this moment, the many2one has still no value, and as it is required, + // the row is discarded if a saveLine is requested. However, it should + // wait for the name_create to return before trying to save the line. + var done = assert.async(); + assert.expect(8); + + this.data.partner.onchanges = { + trululu: function () { }, + }; + + var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; + + var def = $.Deferred(); + var newRecordID; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + mockRPC: function (route, args) { + var result = this._super.apply(this, arguments); + if (args.method === 'name_create') { + return def.then(_.constant(result)).then(function (nameGet) { + newRecordID = nameGet[0]; + return nameGet; + }); + } + if (args.method === 'create') { + assert.deepEqual(args.args[0].p[0][2].trululu, newRecordID); + } + return result; + }, + }); + + form.$('.o_field_x2many_list_row_add a').click(); + + var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); + form.$('.o_field_many2one input').val('b').trigger('keydown'); + concurrency.delay(0).then(function () { + $dropdown.find('li:first()').click(); // quick create 'b' + form.$('.o_field_x2many_list_row_add a').click(); + + assert.strictEqual(form.$('.o_data_row').length, 1, + "there should still be only one row"); + assert.ok(form.$('.o_data_row').hasClass('o_selected_row'), + "the row should still be in edition"); + + def.resolve(); + + assert.strictEqual(form.$('.o_data_row:first .o_data_cell').text(), 'b', + "first row should have the correct m2o value"); + assert.strictEqual(form.$('.o_data_row').length, 2, + "there should now be 2 rows"); + assert.ok(form.$('.o_data_row:nth(1)').hasClass('o_selected_row'), + "the second row should be in edition"); + + form.$buttons.find('.o_form_button_save').click(); + + assert.strictEqual(form.$('.o_data_row').length, 1, + "there should be 1 row saved (the second one was empty and invalid)"); + assert.strictEqual(form.$('.o_data_row .o_data_cell').text(), 'b', + "should have the correct m2o value"); + + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + form.destroy(); + done(); + }); + }); + + QUnit.test('list in form: create with one2many with many2one', function (assert) { + assert.expect(1); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + mockRPC: function (route, args) { + if (args.method === 'default_get') { + return $.when({ p: [[0, 0, { display_name: 'new record' }]] }); + } else if (args.method === 'name_get') { + // This should not be called at all and thus is not accounted for + // in the assert.expect. If this is called, you broke this test. + assert.notOk(_.str.startsWith(args.args[0][0], 'virtual_'), + "should not call name_get for the m2o inside o2m which has no value"); + } + return this._super.apply(this, arguments); + }, + }); + + assert.strictEqual($('td.o_data_cell:first').text(), 'new record', + "should have created the new record in the o2m with the correct name"); + + form.destroy(); + }); + + QUnit.test('list in form: create with one2many with many2one (version 2)', function (assert) { + // This test simulates the exact same scenario as the previous one, + // except that the value for the many2one is explicitely set to false, + // which is stupid, but this happens, so we have to handle it + assert.expect(1); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + mockRPC: function (route, args) { + if (args.method === 'default_get') { + return $.when({ p: [[0, 0, { display_name: 'new record', trululu: false }]] }); + } else if (args.method === 'name_get') { + // This should not be called at all and thus is not accounted for + // in the assert.expect. If this is called, you broke this test. + assert.notOk(_.str.startsWith(args.args[0][0], 'virtual_'), + "should not call name_get for the m2o inside o2m which has no value"); + } + return this._super.apply(this, arguments); + }, + }); + + assert.strictEqual($('td.o_data_cell:first').text(), 'new record', + "should have created the new record in the o2m with the correct name"); + + form.destroy(); + }); + + QUnit.test('item not dropped on discard with empty required field (default_get)', function (assert) { + // This test simulates discarding a record that has been created with + // one of its required field that is empty. When we discard the changes + // on this empty field, it should not assume that this record should be + // abandonned, since it has been added (even though it is a new record). + assert.expect(8); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + mockRPC: function (route, args) { + if (args.method === 'default_get') { + return $.when({ p: [[0, 0, { display_name: 'new record', trululu: false }]] }); + } + return this._super.apply(this, arguments); + }, + }); + + assert.strictEqual($('tr.o_data_row').length, 1, + "should have created the new record in the o2m"); + assert.strictEqual($('td.o_data_cell').first().text(), "new record", + "should have the correct displayed name"); + + var requiredElement = $('td.o_data_cell.o_required_modifier'); + assert.strictEqual(requiredElement.length, 1, + "should have a required field on this record"); + assert.strictEqual(requiredElement.text(), "", + "should have empty string in the required field on this record"); + + requiredElement.click(); + // discard by clicking on body + $('body').click(); + + assert.strictEqual($('tr.o_data_row').length, 1, + "should still have the record in the o2m"); + assert.strictEqual($('td.o_data_cell').first().text(), "new record", + "should still have the correct displayed name"); + + // update selector of required field element + requiredElement = $('td.o_data_cell.o_required_modifier'); + assert.strictEqual(requiredElement.length, 1, + "should still have the required field on this record"); + assert.strictEqual(requiredElement.text(), "", + "should still have empty string in the required field on this record"); + form.destroy(); + }); + + QUnit.test('list in form: name_get with unique ids (default_get)', function (assert) { + assert.expect(2); + + this.data.partner.records[0].display_name = "MyTrululu"; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + mockRPC: function (route, args) { + if (args.method === 'default_get') { + return $.when({ + p: [ + [0, 0, { trululu: 1 }], + [0, 0, { trululu: 1 }] + ] + }); + } + if (args.method === 'name_get') { + assert.deepEqual(args.args[0], _.uniq(args.args[0]), + "should not have duplicates in name_get rpc"); + } + return this._super.apply(this, arguments); + }, + }); + + assert.strictEqual(form.$('td.o_data_cell').text(), "MyTrululuMyTrululu", + "both records should have the correct display_name for trululu field"); + + form.destroy(); + }); + + QUnit.test('list in form: show name of many2one fields in multi-page (default_get)', function (assert) { + assert.expect(4); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + mockRPC: function (route, args) { + if (args.method === 'default_get') { + return $.when({ + p: [ + [0, 0, { display_name: 'record1', trululu: 1 }], + [0, 0, { display_name: 'record2', trululu: 2 }] + ] + }); + } + return this._super.apply(this, arguments); + }, + }); + + assert.strictEqual(form.$('td.o_data_cell').first().text(), + "record1", "should show display_name of 1st record"); + assert.strictEqual(form.$('td.o_data_cell').first().next().text(), + "first record", "should show display_name of trululu of 1st record"); + + form.$('button.o_pager_next').click(); + + assert.strictEqual(form.$('td.o_data_cell').first().text(), + "record2", "should show display_name of 2nd record"); + assert.strictEqual(form.$('td.o_data_cell').first().next().text(), + "second record", "should show display_name of trululu of 2nd record"); + + form.destroy(); + }); + + QUnit.test('list in form: item not dropped on discard with empty required field (onchange in default_get)', function (assert) { + // variant of the test "list in form: discard newly added element with + // empty required field (default_get)", in which the `default_get` + // performs an `onchange` at the same time. This `onchange` may create + // some records, which should not be abandoned on discard, similarly + // to records created directly by `default_get` + assert.expect(7); + + var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; + + this.data.partner.onchanges = { + product_id: function (obj) { + if (obj.product_id === 37) { + obj.p = [[0, 0, { display_name: "entry", trululu: false }]]; + } + }, + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + mockRPC: function (route, args) { + if (args.method === 'default_get') { + return $.when({ + product_id: 37, + }); + } + return this._super.apply(this, arguments); + }, + }); + + // check that there is a record in the editable list with empty string as required field + assert.strictEqual(form.$('.o_data_row').length, 1, + "should have a row in the editable list"); + assert.strictEqual($('td.o_data_cell').first().text(), "entry", + "should have the correct displayed name"); + var requiredField = $('td.o_data_cell.o_required_modifier'); + assert.strictEqual(requiredField.length, 1, + "should have a required field on this record"); + assert.strictEqual(requiredField.text(), "", + "should have empty string in the required field on this record"); + + // click on empty required field in editable list record + requiredField.click(); + // click off so that the required field still stay empty + $('body').click(); + + // record should not be dropped + assert.strictEqual(form.$('.o_data_row').length, 1, + "should not have dropped record in the editable list"); + assert.strictEqual($('td.o_data_cell').first().text(), "entry", + "should still have the correct displayed name"); + assert.strictEqual($('td.o_data_cell.o_required_modifier').text(), "", + "should still have empty string in the required field"); + + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + form.destroy(); + }); + + QUnit.test('list in form: item not dropped on discard with empty required field (onchange on list after default_get)', function (assert) { + // discarding a record from an `onchange` in a `default_get` should not + // abandon the record. This should not be the case for following + // `onchange`, except if an onchange make some changes on the list: + // in particular, if an onchange make changes on the list such that + // a record is added, this record should not be dropped on discard + assert.expect(8); + + var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; + + this.data.partner.onchanges = { + product_id: function (obj) { + if (obj.product_id === 37) { + obj.p = [[0, 0, { display_name: "entry", trululu: false }]]; + } + }, + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + }); + + // check no record in list + assert.strictEqual(form.$('.o_data_row').length, 0, + "should have no row in the editable list"); + + // select product_id to force on_change in editable list + form.$('.o_field_widget[name="product_id"] .o_input').click(); + $('.ui-menu-item').first().click(); + + // check that there is a record in the editable list with empty string as required field + assert.strictEqual(form.$('.o_data_row').length, 1, + "should have a row in the editable list"); + assert.strictEqual($('td.o_data_cell').first().text(), "entry", + "should have the correct displayed name"); + var requiredField = $('td.o_data_cell.o_required_modifier'); + assert.strictEqual(requiredField.length, 1, + "should have a required field on this record"); + assert.strictEqual(requiredField.text(), "", + "should have empty string in the required field on this record"); + + // click on empty required field in editable list record + requiredField.click(); + // click off so that the required field still stay empty + $('body').click(); + + // record should not be dropped + assert.strictEqual(form.$('.o_data_row').length, 1, + "should not have dropped record in the editable list"); + assert.strictEqual($('td.o_data_cell').first().text(), "entry", + "should still have the correct displayed name"); + assert.strictEqual($('td.o_data_cell.o_required_modifier').text(), "", + "should still have empty string in the required field"); + + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + form.destroy(); + }); + + QUnit.test('item dropped on discard with empty required field with "Add an item" (invalid on "ADD")', function (assert) { + // when a record in a list is added with "Add an item", it should + // always be dropped on discard if some required field are empty + // at the record creation. + assert.expect(6); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + }); + + // Click on "Add an item" + form.$('.o_field_x2many_list_row_add a').click(); + var charField = form.$('.o_field_widget.o_field_char[name="display_name"]'); + var requiredField = form.$('.o_field_widget.o_required_modifier[name="trululu"]'); + charField.val("some text"); + assert.strictEqual(charField.length, 1, + "should have a char field 'display_name' on this record"); + assert.notOk(charField.hasClass('o_required_modifier'), + "the char field should not be required on this record"); + assert.strictEqual(charField.val(), "some text", + "should have entered text in the char field on this record"); + assert.strictEqual(requiredField.length, 1, + "should have a required field 'trululu' on this record"); + assert.strictEqual(requiredField.val().trim(), "", + "should have empty string in the required field on this record"); + + // click on empty required field in editable list record + requiredField.click(); + // click off so that the required field still stay empty + $('body').click(); + + // record should be dropped + assert.strictEqual(form.$('.o_data_row').length, 0, + "should have dropped record in the editable list"); + + form.destroy(); + }); + + QUnit.test('item not dropped on discard with empty required field with "Add an item" (invalid on "UPDATE")', function (assert) { + // when a record in a list is added with "Add an item", it should + // be temporarily added to the list when it is valid (e.g. required + // fields are non-empty). If the record is updated so that the required + // field is empty, and it is discarded, then the record should not be + // dropped. + assert.expect(8); + + var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + }); + + assert.strictEqual(form.$('.o_data_row').length, 0, + "should initially not have any record in the list"); + + // Click on "Add an item" + form.$('.o_field_x2many_list_row_add a').click(); + assert.strictEqual(form.$('.o_data_row').length, 1, + "should have a temporary record in the list"); + + var $inputEditMode = form.$('.o_field_widget.o_required_modifier[name="trululu"] input'); + assert.strictEqual($inputEditMode.length, 1, + "should have a required field 'trululu' on this record"); + assert.strictEqual($inputEditMode.val(), "", + "should have empty string in the required field on this record"); + + // add something to required field and leave edit mode of the record + $inputEditMode.click(); + $('li.ui-menu-item').first().click(); + $('body').click(); // leave edit mode on the line + + var $inputReadonlyMode = form.$('.o_data_cell.o_required_modifier'); + assert.strictEqual(form.$('.o_data_row').length, 1, + "should not have dropped valid record when leaving edit mode"); + assert.strictEqual($inputReadonlyMode.text(), "first record", + "should have put some content in the required field on this record"); + + // remove the required field and leave edit mode of the record + $('.o_data_row').click(); // enter edit mode on the line + $inputEditMode.click(); + $inputEditMode.val(""); + $('body').click(); + + assert.strictEqual(form.$('.o_data_row').length, 1, + "should not have dropped record in the list on discard (invalid on UPDATE)"); + assert.strictEqual($inputReadonlyMode.text(), "first record", + "should keep previous valid required field content on this record"); + + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + form.destroy(); + }); + + QUnit.test('list in form: default_get with x2many create', function (assert) { + assert.expect(5); + + var displayName = 'brandon is the new timmy'; + this.data.partner.onchanges.timmy = function (obj) { + assert.deepEqual( + obj.timmy, + [ + [6, false, []], + [0, obj.timmy[1][1], { display_name: displayName, name: 'brandon' }] + ], + "should have properly created the x2many command list"); + obj.int_field = obj.timmy.length; + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + mockRPC: function (route, args) { + if (args.method === 'default_get') { + return $.when({ timmy: [[0, 0, { display_name: 'brandon is the new timmy', name: 'brandon' }]] }); + } + if (args.method === 'create') { + assert.deepEqual(args.args[0], { + int_field: 2, + timmy: [ + [6, false, []], + [0, args.args[0].timmy[1][1], { display_name: displayName, name: 'brandon' }], + ], + }, "should send the correct values to create"); + } + return this._super.apply(this, arguments); + }, + }); + + assert.strictEqual($('td.o_data_cell:first').text(), 'brandon is the new timmy', + "should have created the new record in the m2m with the correct name"); + assert.strictEqual($('input.o_field_integer').val(), '2', + "should have called and executed the onchange properly"); + + // edit the subrecord and save + displayName = 'new value'; + form.$('.o_data_cell').click(); + form.$('.o_data_cell input').val(displayName).trigger('input'); + form.$buttons.find('.o_form_button_save').click(); + + form.destroy(); + }); + + QUnit.test('list in form: default_get with x2many create and onchange', function (assert) { + assert.expect(2); + + this.data.partner.onchanges.turtles = function (obj) { + assert.deepEqual( + obj.turtles, + [ + [4, 2, false], + [1, 2, { turtle_foo: 'blip' }], + [4, 3, false], + [1, 3, { turtle_foo: 'kawa' }] + ], + "should have properly created the x2many command list"); + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + mockRPC: function (route, args) { + if (args.method === 'default_get') { + return $.when({ turtles: [[6, 0, [2, 3]]] }); + } + if (args.method === 'create') { + // it would be even better if we did not send the current + // unchanged state with the command 1, but this seems more + // difficult. + assert.deepEqual(args.args[0].turtles, [ + [4, 2, false], + [1, 2, { turtle_foo: 'blip' }], + [4, 3, false], + [1, 3, { turtle_foo: 'kawa' }] + ], 'should send proper commands to create method'); + } + return this._super.apply(this, arguments); + }, + }); + + form.$buttons.find('.o_form_button_save').click(); + + form.destroy(); + }); + + QUnit.test('list in form: call button in sub view', function (assert) { + assert.expect(6); + + this.data.partner.records[0].p = [2]; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/product/get_formview_id') { + return $.when(false); + } + return this._super.apply(this, arguments); + }, + intercepts: { + execute_action: function (event) { + assert.strictEqual(event.data.env.model, 'product', + 'should call with correct model in env'); + assert.strictEqual(event.data.env.currentID, 37, + 'should call with correct currentID in env'); + assert.deepEqual(event.data.env.resIDs, [37], + 'should call with correct resIDs in env'); + }, + }, + archs: { + 'product,false,form': '
' + + '
' + + '
' + + '
', + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('td.o_data_cell:first').click(); // edit first one2many line + form.$('.o_external_button').click(); // open product sub view in modal + $('button:contains("Just do it !")').click(); // click on action button + $('button:contains("Just don\'t do it !")').click(); // click on object button + + form.destroy(); + }); + + QUnit.test('X2Many sequence list in modal', function (assert) { + assert.expect(5); + + this.data.partner.fields.sequence = { string: 'Sequence', type: 'integer' }; + this.data.partner.records[0].sequence = 1; + this.data.partner.records[1].sequence = 2; + this.data.partner.onchanges = { + sequence: function (obj) { + if (obj.id === 2) { + obj.sequence = 1; + assert.step('onchange sequence'); + } + }, + }; + + this.data.product.fields.turtle_ids = { string: 'Turtles', type: 'one2many', relation: 'turtle' }; + this.data.product.records[0].turtle_ids = [1]; + + this.data.turtle.fields.partner_types_ids = { string: "Partner", type: "one2many", relation: 'partner' }; + this.data.turtle.fields.type_id = { string: "Partner Type", type: "many2one", relation: 'partner_type' }; + + this.data.partner_type.fields.partner_ids = { string: "Partner", type: "one2many", relation: 'partner' }; + this.data.partner_type.records[0].partner_ids = [1, 2]; + + testUtils.createAsyncView({ + View: FormView, + model: 'product', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + archs: { + 'partner_type,false,form': '
', + 'partner,false,list': '' + + '' + + '' + + '', + }, + res_id: 37, + mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/product/read') { + return $.when([{ id: 37, name: 'xphone', display_name: 'leonardo', turtle_ids: [1] }]); + } + if (route === '/web/dataset/call_kw/turtle/read') { + return $.when([{ id: 1, type_id: [12, 'gold'] }]); + } + if (route === '/web/dataset/call_kw/partner_type/get_formview_id') { + return $.when(false); + } + if (route === '/web/dataset/call_kw/partner_type/read') { + return $.when([{ id: 12, partner_ids: [1, 2], display_name: 'gold' }]); + } + if (route === '/web/dataset/call_kw/partner_type/write') { + assert.step('partner_type write'); + } + return this._super.apply(this, arguments); + }, + }).then(function (form) { + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_data_cell').click(); + form.$('.o_external_button').click(); + + var $modal = $('.modal'); + assert.equal($modal.length, 1, + 'There should be 1 modal opened'); + + var $handles = $modal.find('.ui-sortable-handle'); + assert.equal($handles.length, 2, + 'There should be 2 sequence handlers'); + + testUtils.dragAndDrop($handles.eq(1), $modal.find('tbody tr').first(), + { position: 'top' }); + + // Saving the modal and then the original model + $modal.find('.modal-footer .btn-primary').click(); + form.$buttons.find('.o_form_button_save').click(); + + assert.verifySteps(['onchange sequence', 'partner_type write']); + + form.destroy(); + }); + }); + + QUnit.test('autocompletion in a many2one, in form view with a domain', function (assert) { + assert.expect(1); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '', + res_id: 1, + viewOptions: { + domain: [['trululu', '=', 4]] + }, + mockRPC: function (route, args) { + if (args.method === 'name_search') { + assert.deepEqual(args.kwargs.args, [], "should not have a domain"); + } + return this._super(route, args); + } + }); + form.$buttons.find('.o_form_button_edit').click(); + + form.$('input').click(); + form.destroy(); + }); + + QUnit.test('autocompletion in a many2one, in form view with a date field', function (assert) { + assert.expect(1); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '', + res_id: 2, + mockRPC: function (route, args) { + if (args.method === 'name_search') { + assert.deepEqual(args.kwargs.args, [["bar", "=", true]], "should not have a domain"); + } + return this._super(route, args); + }, + }); + form.$buttons.find('.o_form_button_edit').click(); + + form.$('input:eq(2)').click(); + form.destroy(); + }); + + QUnit.test('creating record with many2one with option always_reload', function (assert) { + assert.expect(2); + + this.data.partner.fields.trululu.default = 1; + this.data.partner.onchanges = { + trululu: function (obj) { + obj.trululu = 2; //[2, "second record"]; + }, + }; + + var count = 0; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '', + mockRPC: function (route, args) { + count++; + if (args.method === 'name_get' && args.args[0][0] === 2) { + return $.when([[2, "hello world\nso much noise"]]); + } + return this._super(route, args); + }, + }); + + assert.strictEqual(count, 3, "should have done 3 rpcs (default_get, onchange, name_get)"); + assert.strictEqual(form.$('input').val(), 'hello world', + "should have taken the correct display name"); + form.destroy(); + }); + + QUnit.test('selecting a many2one, then discarding', function (assert) { + assert.expect(3); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '', + res_id: 1, + }); + assert.strictEqual(form.$('a').text(), '', 'the tag a should be empty'); + form.$buttons.find('.o_form_button_edit').click(); + + form.$('.o_field_many2one input').click(); + form.$('.o_field_many2one input').autocomplete('widget').find('a').first().click(); + + + assert.strictEqual(form.$('input').val(), "xphone", "should have selected xphone"); + + form.$buttons.find('.o_form_button_cancel').click(); + assert.strictEqual(form.$('a').text(), '', 'the tag a should be empty'); + form.destroy(); + }); + + QUnit.test('domain and context are correctly used when doing a name_search in a m2o', function (assert) { + assert.expect(4); + + this.data.partner.records[0].timmy = [12]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: + '
' + + '' + + '' + + '' + + '' + + '', + res_id: 1, + session: { user_context: { hey: "ho" } }, + mockRPC: function (route, args) { + if (args.method === 'name_search' && args.model === 'product') { + assert.deepEqual( + args.kwargs.args, + [['foo', '=', 'bar'], ['foo', '=', 'yop']], + 'the field attr domain should have been used for the RPC (and evaluated)'); + assert.deepEqual( + args.kwargs.context, + { hey: "ho", hello: "world", test: "yop" }, + 'the field attr context should have been used for the ' + + 'RPC (evaluated and merged with the session one)'); + return $.when([]); + } + if (args.method === 'name_search' && args.model === 'partner') { + assert.deepEqual(args.kwargs.args, [['id', 'in', [12]]], + 'the field attr domain should have been used for the RPC (and evaluated)'); + assert.deepEqual(args.kwargs.context, { hey: 'ho', timmy: [[6, false, [12]]] }, + 'the field attr context should have been used for the RPC (and evaluated)'); + return $.when([]); + } + return this._super.apply(this, arguments); + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_widget[name=product_id] input').click(); + + form.$('.o_field_widget[name=trululu] input').click(); + + form.destroy(); + }); + + QUnit.test('quick create on a many2one', function (assert) { + assert.expect(1); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '
', + mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/product/name_create') { + assert.strictEqual(args.args[0], 'new partner', + "should name create a new product"); + } + return this._super.apply(this, arguments); + }, + }); + + form.$('.o_field_many2one input').focus(); + form.$('.o_field_many2one input').val('new partner').trigger('keyup').trigger('focusout'); + + $('.modal .modal-footer .btn-primary').first().click(); + + form.destroy(); + }); + + QUnit.test('slow create on a many2one', function (assert) { + assert.expect(7); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: + '
' + + '' + + '' + + '' + + '
', + archs: { + 'product,false,form': + '
' + + '' + + '', + }, + }); + + // cancel the many2one creation with Cancel button + form.$('.o_field_many2one input').focus().val('new product').trigger('keyup').trigger('blur'); + assert.strictEqual($('.modal').length, 1, "there should be one opened modal"); + $('.modal .modal-footer .btn:contains(Cancel)').click(); + assert.strictEqual(form.$('.o_field_many2one input').val(), "", + 'the many2one should not set a value as its creation has been cancelled (with Cancel button)'); + + // cancel the many2one creation with Close button + form.$('.o_field_many2one input').focus().val('new product').trigger('keyup').trigger('blur'); + $('.modal .modal-header button').click(); + assert.strictEqual(form.$('.o_field_many2one input').val(), "", + 'the many2one should not set a value as its creation has been cancelled (with Close button)'); + + // select a new value then cancel the creation of the new one --> restore the previous + form.$('.o_field_many2one input').click(); + form.$('.o_field_many2one input').autocomplete('widget').find('a').first().click(); + assert.strictEqual(form.$('input').val(), "xphone", "should have selected xphone"); + form.$('.o_field_many2one input').focus().val('new product').trigger('keyup').trigger('blur'); + assert.strictEqual($('.modal').length, 1, "there should be one opened modal"); + $('.modal .modal-footer .btn:contains(Cancel)').click(); + assert.strictEqual(form.$('.o_field_many2one input').val(), "xphone", + 'should have restored the many2one with its previous selected value (xphone)'); + + // confirm the many2one creation + form.$('.o_field_many2one input').focus(); + form.$('.o_field_many2one input').val('new partner').trigger('keyup').trigger('blur'); + $('.modal .modal-footer .btn-primary').click(); + assert.strictEqual($('.modal .o_form_view').length, 1, + 'a new modal should be opened and contain a form view'); + + form.destroy(); + }); + + QUnit.test('no_create option on a many2one', function (assert) { + assert.expect(1); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '
', + }); + + form.$('.o_field_many2one input').focus(); + form.$('.o_field_many2one input').val('new partner').trigger('keyup').trigger('focusout'); + + assert.strictEqual($('.modal').length, 0, "should not display the create modal"); + form.destroy(); + }); + + QUnit.test('can_create and can_write option on a many2one', function (assert) { + assert.expect(5); + + this.data.product.options = { + can_create: "false", + can_write: "false", + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '
', + archs: { + 'product,false,form': '
', + }, + mockRPC: function (route) { + if (route === '/web/dataset/call_kw/product/get_formview_id') { + return $.when(false); + } + return this._super.apply(this, arguments); + }, + }); + + form.$('.o_field_many2one input').click(); + assert.strictEqual($('.ui-autocomplete .o_m2o_dropdown_option:contains(Create)').length, 0, + "there shouldn't be any option to search and create"); + + $('.ui-autocomplete li:contains(xpad)').mouseenter().click(); + assert.strictEqual(form.$('.o_field_many2one input').val(), "xpad", + "the correct record should be selected"); + assert.strictEqual(form.$('.o_field_many2one .o_external_button').length, 1, + "there should be an external button displayed"); + + form.$('.o_field_many2one .o_external_button').click(); + assert.strictEqual($('.modal .o_form_view.o_form_readonly').length, 1, + "there should be a readonly form view opened"); + + $('.modal .o_form_button_cancel').click(); + + form.$('.o_field_many2one input').val('new product').trigger('keyup').trigger('focusout'); + + assert.strictEqual($('.modal').length, 0, "should not display the create modal"); + form.destroy(); + }); + + QUnit.test('pressing enter in a m2o in an editable list', function (assert) { + assert.expect(9); + var done = assert.async(); + var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; + + var list = createView({ + View: ListView, + model: 'partner', + data: this.data, + arch: '', + }); + + list.$('td.o_data_cell:first').click(); + assert.strictEqual(list.$('.o_selected_row').length, 1, + "should have a row in edit mode"); + + // we now write 'a' and press enter to check that the selection is + // working, and prevent the navigation + list.$('td.o_data_cell input:first').val('a').trigger('input'); + concurrency.delay(0).then(function () { + var $input = list.$('td.o_data_cell input:first'); + var $dropdown = $input.autocomplete('widget'); + assert.ok($dropdown.is(':visible'), "autocomplete dropdown should be visible"); + + // we now trigger ENTER to select first choice + $input.trigger($.Event('keydown', { + which: $.ui.keyCode.ENTER, + keyCode: $.ui.keyCode.ENTER, + })); + assert.strictEqual($input[0], document.activeElement, + "input should still be focused"); + + // we now trigger again ENTER to make sure we can move to next line + $input.trigger($.Event('keydown', { + which: $.ui.keyCode.ENTER, + keyCode: $.ui.keyCode.ENTER, + })); + + assert.notOk(document.contains($input[0]), + "input should no longer be in dom"); + assert.ok(list.$('tr.o_data_row:eq(1)').hasClass('o_selected_row'), + "second row should now be selected"); + + // we now write again 'a' in the cell to select xpad. We will now + // test with the tab key + list.$('td.o_data_cell input:first').val('a').trigger('input'); + return concurrency.delay(0); + }).then(function () { + var $input = list.$('td.o_data_cell input:first'); + var $dropdown = $input.autocomplete('widget'); + assert.ok($dropdown.is(':visible'), "autocomplete dropdown should be visible"); + $input.trigger($.Event('keydown', { + which: $.ui.keyCode.TAB, + keyCode: $.ui.keyCode.TAB, + })); + assert.strictEqual($input[0], document.activeElement, + "input should still be focused"); + + // we now trigger again ENTER to make sure we can move to next line + $input.trigger($.Event('keydown', { + which: $.ui.keyCode.TAB, + keyCode: $.ui.keyCode.TAB, + })); + + assert.notOk(document.contains($input[0]), + "input should no longer be in dom"); + assert.ok(list.$('tr.o_data_row:eq(2)').hasClass('o_selected_row'), + "third row should now be selected"); + list.destroy(); + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + done(); + }); + }); + + QUnit.test('pressing ENTER on a \'no_quick_create\' many2one should open a M2ODialog', function (assert) { + var done = assert.async(); + assert.expect(2); + + var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '', + archs: { + 'partner,false,form': '
', + }, + }); + + var $input = form.$('.o_field_many2one input'); + $input.val("Something that does not exist").trigger('input'); + $('.ui-autocomplete .ui-menu-item a:contains(Create and)').trigger('mouseenter'); + concurrency.delay(0).then(function () { + $input.trigger($.Event('keydown', { + which: $.ui.keyCode.ENTER, + keyCode: $.ui.keyCode.ENTER, + })); + $input.trigger($.Event('keypress', { + which: $.ui.keyCode.ENTER, + keyCode: $.ui.keyCode.ENTER, + })); + $input.trigger($.Event('keyup', { + which: $.ui.keyCode.ENTER, + keyCode: $.ui.keyCode.ENTER, + })); + concurrency.delay(0).then(function () { + $input.blur(); + assert.strictEqual($('.modal').length, 1, + "should have one modal in body"); + // Check that discarding clears $input + $('.modal .o_form_button_cancel').click(); + assert.strictEqual($input.val(), '', + "the field should be empty"); + form.destroy(); + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + done(); + }); + }); + }); + + QUnit.test('select a value by pressing TAB on a many2one with onchange', function (assert) { + var done = assert.async(); + assert.expect(3); + + this.data.partner.onchanges.trululu = function () { }; + + var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; + var def = $.Deferred(); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '', + mockRPC: function (route, args) { + var result = this._super.apply(this, arguments); + if (args.method === 'onchange') { + return $.when(def).then(_.constant(result)); + } + return result; + }, + res_id: 1, + viewOptions: { + mode: 'edit', + }, + }); + + var $input = form.$('.o_field_many2one input'); + $input.val("first").trigger('input'); + concurrency.delay(0).then(function () { + var evOptions = { + which: $.ui.keyCode.TAB, + keyCode: $.ui.keyCode.TAB, + }; + $input.trigger($.Event('keydown', evOptions)); + $input.trigger($.Event('keypress', evOptions)); + $input.trigger($.Event('keyup', evOptions)); + + // simulate a focusout (e.g. because the user clicks outside) + // before the onchange returns + form.$('.o_field_char').focus(); + + assert.strictEqual($('.modal').length, 0, + "there shouldn't be any modal in body"); + + // unlock the onchange + def.resolve(); + + assert.strictEqual($input.val(), 'first record', + "first record should have been selected"); + assert.strictEqual($('.modal').length, 0, + "there shouldn't be any modal in body"); + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + form.destroy(); + done(); + }); + }); + + QUnit.test('many2one in editable list + onchange, with enter [REQUIRE FOCUS]', function (assert) { + assert.expect(6); + var done = assert.async(); + var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; + + this.data.partner.onchanges.product_id = function (obj) { + obj.int_field = obj.product_id || 0; + }; + + var def = $.Deferred(); + + var list = createView({ + View: ListView, + model: 'partner', + data: this.data, + arch: '', + mockRPC: function (route, args) { + if (args.method) { + assert.step(args.method); + } + var result = this._super.apply(this, arguments); + if (args.method === 'onchange') { + return def.then(_.constant(result)); + } + return result; + }, + }); + + list.$('td.o_data_cell:first').click(); + list.$('td.o_data_cell input:first').val('a').trigger('input'); + concurrency.delay(0).then(function () { + var $input = list.$('td.o_data_cell input:first'); + $input.trigger($.Event('keydown', { + which: $.ui.keyCode.ENTER, + keyCode: $.ui.keyCode.ENTER, + })); + $input.trigger($.Event('keyup', { + which: $.ui.keyCode.ENTER, + keyCode: $.ui.keyCode.ENTER, + })); + def.resolve(); + $input.trigger($.Event('keydown', { + which: $.ui.keyCode.ENTER, + keyCode: $.ui.keyCode.ENTER, + })); + assert.strictEqual($('.modal').length, 0, "should not have any modal in DOM"); + assert.verifySteps(['name_search', 'onchange', 'write', 'read']); + list.destroy(); + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + done(); + }); + }); + + QUnit.test('many2one in editable list + onchange, with enter, part 2 [REQUIRE FOCUS]', function (assert) { + // this is the same test as the previous one, but the onchange is just + // resolved slightly later + assert.expect(6); + var done = assert.async(); + var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; + + this.data.partner.onchanges.product_id = function (obj) { + obj.int_field = obj.product_id || 0; + }; + + var def = $.Deferred(); + + var list = createView({ + View: ListView, + model: 'partner', + data: this.data, + arch: '', + mockRPC: function (route, args) { + if (args.method) { + assert.step(args.method); + } + var result = this._super.apply(this, arguments); + if (args.method === 'onchange') { + return def.then(_.constant(result)); + } + return result; + }, + }); + + list.$('td.o_data_cell:first').click(); + list.$('td.o_data_cell input:first').val('a').trigger('input'); + concurrency.delay(0).then(function () { + var $input = list.$('td.o_data_cell input:first'); + $input.trigger($.Event('keydown', { + which: $.ui.keyCode.ENTER, + keyCode: $.ui.keyCode.ENTER, + })); + $input.trigger($.Event('keyup', { + which: $.ui.keyCode.ENTER, + keyCode: $.ui.keyCode.ENTER, + })); + $input.trigger($.Event('keydown', { + which: $.ui.keyCode.ENTER, + keyCode: $.ui.keyCode.ENTER, + })); + def.resolve(); + assert.strictEqual($('.modal').length, 0, "should not have any modal in DOM"); + assert.verifySteps(['name_search', 'onchange', 'write', 'read']); + list.destroy(); + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + done(); + }); + }); + + QUnit.test('many2one: domain updated by an onchange', function (assert) { + assert.expect(2); + + this.data.partner.onchanges = { + int_field: function () { }, + }; + + var domain = []; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'onchange') { + domain = [['id', 'in', [10]]]; + return $.when({ + domain: { + trululu: domain, + unexisting_field: domain, + } + }); + } + if (args.method === 'name_search') { + assert.deepEqual(args.kwargs.args, domain, + "sent domain should be correct"); + } + return this._super(route, args); + }, + viewOptions: { + mode: 'edit', + }, + }); + + // trigger a name_search (domain should be []) + form.$('.o_field_widget[name=trululu] input').click(); + // close the dropdown + form.$('.o_field_widget[name=trululu] input').click(); + // trigger an onchange that will update the domain + form.$('.o_field_widget[name=int_field]').val(2).trigger('input'); + // trigger a name_search (domain should be [['id', 'in', [10]]]) + form.$('.o_field_widget[name=trululu] input').click(); + + form.destroy(); + }); + + QUnit.test('many2one in one2many: domain updated by an onchange', function (assert) { + assert.expect(3); + + this.data.partner.onchanges = { + trululu: function () { }, + }; + + var domain = []; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'onchange') { + return $.when({ + domain: { + trululu: domain, + }, + }); + } + if (args.method === 'name_search') { + assert.deepEqual(args.kwargs.args, domain, + "sent domain should be correct"); + } + return this._super(route, args); + }, + viewOptions: { + mode: 'edit', + }, + }); + + // add a first row with a specific domain for the m2o + domain = [['id', 'in', [10]]]; // domain for subrecord 1 + form.$('.o_field_x2many_list_row_add a').click(); // triggers the onchange + form.$('.o_field_widget[name=trululu] input').click(); // triggers the name_search + + // add a second row with another domain for the m2o + domain = [['id', 'in', [5]]]; // domain for subrecord 2 + form.$('.o_field_x2many_list_row_add a').click(); // triggers the onchange + form.$('.o_field_widget[name=trululu] input').click(); // triggers the name_search + + // check again the first row to ensure that the domain hasn't change + domain = [['id', 'in', [10]]]; // domain for subrecord 1 should have been kept + form.$('.o_data_row:first .o_data_cell').click(); + form.$('.o_field_widget[name=trululu] input').click(); // triggers the name_search + + form.destroy(); + }); + + QUnit.test('updating a many2one from a many2many', function (assert) { + assert.expect(4); + + this.data.turtle.records[1].turtle_trululu = 1; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + archs: { + 'partner,false,form': '
', + }, + mockRPC: function (route, args) { + if (args.method === 'get_formview_id') { + assert.deepEqual(args.args[0], [1], "should call get_formview_id with correct id"); + return $.when(false); + } + return this._super(route, args); + }, + }); + + // Opening the modal + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_data_row td:contains(first record)').click(); + form.$('.o_external_button').click(); + assert.strictEqual($('.modal').length, 1, + "should have one modal in body"); + + // Changing the 'trululu' value + $('.modal input[name="display_name"]').val('test').trigger('input'); + $('.modal button.btn-primary').click(); + + // Test whether the value has changed + assert.strictEqual($('.modal').length, 0, + "the modal should be closed"); + assert.equal(form.$('.o_data_cell:contains(test)').text(), 'test', + "the partner name should have been updated to 'test'"); + + form.destroy(); + }); + + QUnit.test('x2many list sorted by many2one', function (assert) { + assert.expect(3); + + this.data.partner.records[0].p = [1, 2, 4]; + this.data.partner.fields.trululu.sortable = true; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + assert.strictEqual(form.$('.o_data_row .o_list_number').text(), '124', + "should have correct order initially"); + + form.$('.o_list_view thead th:nth(1)').click(); + + assert.strictEqual(form.$('.o_data_row .o_list_number').text(), '412', + "should have correct order (ASC)"); + + form.$('.o_list_view thead th:nth(1)').click(); + + assert.strictEqual(form.$('.o_data_row .o_list_number').text(), '214', + "should have correct order (DESC)"); + + form.destroy(); + }); + + QUnit.test('one2many with extra field from server not in form', function (assert) { + assert.expect(6); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + archs: { + 'partner,false,form': '
' + + '' + + ''}, + mockRPC: function(route, args) { + if (route === '/web/dataset/call_kw/partner/write') { + args.args[1].p[0][2].datetime = '2018-04-05 12:00:00'; + } + return this._super.apply(this, arguments); + } + }); + + form.$buttons.find('.o_form_button_edit').click(); + + var x2mList = form.$('.o_field_x2many_list[name=p]'); + + // Add a record in the list + x2mList.find('.o_field_x2many_list_row_add a').click(); + + var modal = $('.modal-lg'); + + var nameInput = modal.find('input.o_input[name=display_name]'); + nameInput.val('michelangelo').trigger('input'); + + // Save the record in the modal (though it is still virtual) + modal.find('.btn-primary').first().click(); + + assert.equal(x2mList.find('.o_data_row').length, 1, + 'There should be 1 records in the x2m list'); + + var newlyAdded = x2mList.find('.o_data_row').eq(0); + + assert.equal(newlyAdded.find('.o_data_cell').first().text(), '', + 'The create_date field should be empty'); + assert.equal(newlyAdded.find('.o_data_cell').eq(1).text(), 'michelangelo', + 'The display name field should have the right value'); + + // Save the whole thing + form.$buttons.find('.o_form_button_save').click(); + + x2mList = form.$('.o_field_x2many_list[name=p]'); + + // Redo asserts in RO mode after saving + assert.equal(x2mList.find('.o_data_row').length, 1, + 'There should be 1 records in the x2m list'); + + newlyAdded = x2mList.find('.o_data_row').eq(0); + + assert.equal(newlyAdded.find('.o_data_cell').first().text(), '04/05/2018 12:00:00', + 'The create_date field should have the right value'); + assert.equal(newlyAdded.find('.o_data_cell').eq(1).text(), 'michelangelo', + 'The display name field should have the right value'); + + form.destroy(); + }); + + QUnit.test('one2many with extra field from server not in (inline) form', function (assert) { + assert.expect(1); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + res_id: 1, + viewOptions: { + mode: 'edit', + }, + }); + + var x2mList = form.$('.o_field_x2many_list[name=p]'); + + // Add a record in the list + x2mList.find('.o_field_x2many_list_row_add a').click(); + + var modal = $('.modal-lg'); + + var nameInput = modal.find('input.o_input[name=display_name]'); + nameInput.val('michelangelo').trigger('input'); + + // Save the record in the modal (though it is still virtual) + modal.find('.btn-primary').first().click(); + + assert.equal(x2mList.find('.o_data_row').length, 1, + 'There should be 1 records in the x2m list'); + + form.destroy(); + }); + + QUnit.test('one2many with extra X2many field from server not in inline form', function (assert) { + assert.expect(1); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + res_id: 1, + viewOptions: { + mode: 'edit', + }, + }); + + var x2mList = form.$('.o_field_x2many_list[name=p]'); + + // Add a first record in the list + x2mList.find('.o_field_x2many_list_row_add a').click(); + + // Save & New + $('.modal-lg').find('.btn-primary').eq(1).click(); + + // Save & Close + $('.modal-lg').find('.btn-primary').eq(0).click(); + + assert.equal(x2mList.find('.o_data_row').length, 2, + 'There should be 2 records in the x2m list'); + + form.destroy(); + }); + + QUnit.test('one2many invisible depends on parent field', function (assert) { + assert.expect(4); + + this.data.partner.records[0].p = [2]; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch:'
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + assert.strictEqual(form.$('th').length, 2, + "should be 2 columns in the one2many"); + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_many2one[name="product_id"] input').click(); + $('li.ui-menu-item a:contains(xpad)').trigger('mouseenter').click(); + assert.strictEqual(form.$('th').length, 1, + "should be 1 column when the product_id is set"); + form.$('.o_field_many2one[name="product_id"] input').val('').trigger('keyup'); + assert.strictEqual(form.$('th').length, 2, + "should be 2 columns in the one2many when product_id is not set"); + form.$('.o_field_boolean[name="bar"] input').click(); + assert.strictEqual(form.$('th').length, 1, + "should be 1 column after the value change"); + form.destroy(); + }); + + QUnit.test('one2many column visiblity depends on onchange of parent field', function (assert) { + assert.expect(3); + + this.data.partner.records[0].p = [2]; + this.data.partner.records[0].bar = false; + + this.data.partner.onchanges.p = function (obj) { + // set bar to true when line is added + if (obj.p.length > 1 && obj.p[1][2].foo === 'New line') { + obj.bar = true; + } + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch:'
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + res_id: 1, + }); + + // bar is false so there should be 1 column + assert.strictEqual(form.$('th').length, 1, + "should be only 1 column ('foo') in the one2many"); + assert.strictEqual(form.$('.o_list_view .o_data_row').length, 1, "should contain one row"); + + form.$buttons.find('.o_form_button_edit').click(); + + // add a new o2m record + form.$('.o_field_x2many_list_row_add a').click(); + form.$('.o_field_one2many input:first').focus(); + form.$('.o_field_one2many input:first').val('New line').trigger('input'); + form.$el.click(); // click outside to save a line + + assert.strictEqual(form.$('th').length, 2, "should be 2 columns('foo' + 'int_field')"); + + form.destroy(); + }); + + QUnit.test('one2many column_invisible on view not inline', function (assert) { + assert.expect(4); + + this.data.partner.records[0].p = [2]; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch:'
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + archs: { + 'partner,false,list': '' + + '' + + '' + + '', + }, + }); + assert.strictEqual(form.$('th').length, 2, + "should be 2 columns in the one2many"); + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_many2one[name="product_id"] input').click(); + $('li.ui-menu-item a:contains(xpad)').trigger('mouseenter').click(); + assert.strictEqual(form.$('th').length, 1, + "should be 1 column when the product_id is set"); + form.$('.o_field_many2one[name="product_id"] input').val('').trigger('keyup'); + assert.strictEqual(form.$('th').length, 2, + "should be 2 columns in the one2many when product_id is not set"); + form.$('.o_field_boolean[name="bar"] input').click(); + assert.strictEqual(form.$('th').length, 1, + "should be 1 column after the value change"); + form.destroy(); + }); + }); +}); +}); diff --git a/addons/web/static/tests/fields/relational_fields/field_many_to_many_tests.js b/addons/web/static/tests/fields/relational_fields/field_many_to_many_tests.js deleted file mode 100644 index e782a27cb4a..00000000000 --- a/addons/web/static/tests/fields/relational_fields/field_many_to_many_tests.js +++ /dev/null @@ -1,1160 +0,0 @@ -odoo.define('web.field_many_to_many_tests', function (require) { -"use strict"; - -var FormView = require('web.FormView'); -var testUtils = require('web.test_utils'); -var createView = testUtils.createView; - -QUnit.module('fields', {}, function () { - - QUnit.module('relational_fields', { - beforeEach: function () { - this.data = { - partner: { - fields: { - display_name: { string: "Displayed name", type: "char" }, - foo: { string: "Foo", type: "char", default: "My little Foo Value" }, - int_field: { string: "int_field", type: "integer", sortable: true }, - turtles: { string: "one2many turtle field", type: "one2many", relation: 'turtle', relation_field: 'turtle_trululu' }, - timmy: { string: "pokemon", type: "many2many", relation: 'partner_type' }, - color: { - type: "selection", - selection: [['red', "Red"], ['black', "Black"]], - default: 'red', - string: "Color", - }, - user_id: { string: "User", type: 'many2one', relation: 'user' }, - reference: { - string: "Reference Field", type: 'reference', selection: [ - ["product", "Product"], ["partner_type", "Partner Type"], ["partner", "Partner"]] - }, - }, - records: [{ - id: 1, - display_name: "first record", - foo: "yop", - int_field: 10, - turtles: [2], - timmy: [], - user_id: 17, - reference: 'product,37', - }, { - id: 2, - display_name: "second record", - foo: "blip", - int_field: 9, - timmy: [], - user_id: 17, - }, { - id: 4, - display_name: "aaa", - }], - onchanges: {}, - }, - product: { - fields: { - name: { string: "Product Name", type: "char" } - }, - records: [{ - id: 37, - display_name: "xphone", - }, { - id: 41, - display_name: "xpad", - }] - }, - partner_type: { - fields: { - name: { string: "Partner Type", type: "char" }, - color: { string: "Color index", type: "integer" }, - }, - records: [ - { id: 12, display_name: "gold", color: 2 }, - { id: 14, display_name: "silver", color: 5 }, - ] - }, - turtle: { - fields: { - display_name: { string: "Displayed name", type: "char" }, - turtle_foo: { string: "Foo", type: "char" }, - turtle_bar: { string: "Bar", type: "boolean", default: true }, - partner_ids: { string: "Partner", type: "many2many", relation: 'partner' }, - }, - records: [{ - id: 1, - display_name: "leonardo", - turtle_foo: "yop", - partner_ids: [], - }, { - id: 2, - display_name: "donatello", - turtle_foo: "blip", - partner_ids: [2, 4], - }, { - id: 3, - display_name: "raphael", - turtle_foo: "kawa", - partner_ids: [], - }], - onchanges: {}, - }, - user: { - fields: { - name: { string: "Name", type: "char" }, - }, - records: [{ - id: 17, - name: "Aline", - }, { - id: 19, - name: "Christine", - }] - }, - }; - } - }, function () { - QUnit.module('FieldMany2Many'); - - QUnit.test('many2many kanban: edition', function (assert) { - assert.expect(32); - - this.data.partner.records[0].timmy = [12, 14]; - this.data.partner_type.records.push({ id: 15, display_name: "red", color: 6 }); - this.data.partner_type.records.push({ id: 18, display_name: "yellow", color: 4 }); - this.data.partner_type.records.push({ id: 21, display_name: "blue", color: 1 }); - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '', - archs: { - 'partner_type,false,form': '
', - 'partner_type,false,list': '', - 'partner_type,false,search': '' + - '' + - '', - }, - res_id: 1, - mockRPC: function (route, args) { - if (route === '/web/dataset/call_kw/partner_type/write') { - assert.strictEqual(args.args[1].display_name, "new name", "should write 'new_name'"); - } - if (route === '/web/dataset/call_kw/partner_type/create') { - assert.strictEqual(args.args[0].display_name, "A new type", "should create 'A new type'"); - } - if (route === '/web/dataset/call_kw/partner/write') { - var commands = args.args[1].timmy; - assert.strictEqual(commands.length, 1, "should have generated one command"); - assert.strictEqual(commands[0][0], 6, "generated command should be REPLACE WITH"); - // get the created type's id - var createdType = _.findWhere(this.data.partner_type.records, { - display_name: "A new type" - }); - var ids = _.sortBy([12, 15, 18].concat(createdType.id), _.identity.bind(_)); - assert.ok(_.isEqual(_.sortBy(commands[0][2], _.identity.bind(_)), ids), - "new value should be " + ids); - } - return this._super.apply(this, arguments); - }, - }); - - // the SelectCreateDialog requests the session, so intercept its custom - // event to specify a fake session to prevent it from crashing - testUtils.intercept(form, 'get_session', function (event) { - event.data.callback({ user_context: {} }); - }); - - assert.ok(!form.$('.o_kanban_view .delete_icon').length, - 'delete icon should not be visible in readonly'); - assert.ok(!form.$('.o_field_many2many .o-kanban-button-new').length, - '"Add" button should not be visible in readonly'); - - form.$buttons.find('.o_form_button_edit').click(); - - assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 2, - 'should contain 2 records'); - assert.strictEqual(form.$('.o_kanban_record:first() span').text(), 'gold', - 'display_name of subrecord should be the one in DB'); - assert.ok(form.$('.o_kanban_view .delete_icon').length, - 'delete icon should be visible in edit'); - assert.ok(form.$('.o_field_many2many .o-kanban-button-new').length, - '"Add" button should be visible in edit'); - assert.strictEqual(form.$('.o_field_many2many .o-kanban-button-new').text().trim(), "Add", - 'Create button should have "Add" label'); - - // edit existing subrecord - form.$('.oe_kanban_global_click:first()').click(); - - $('.modal .o_form_view input').val('new name').trigger('input'); - $('.modal .modal-footer .btn-primary').click(); // save - assert.strictEqual(form.$('.o_kanban_record:first() span').text(), 'new name', - 'value of subrecord should have been updated'); - - // add subrecords - // -> single select - form.$('.o_field_many2many .o-kanban-button-new').click(); - assert.ok($('.modal .o_list_view').length, "should have opened a list view in a modal"); - assert.strictEqual($('.modal .o_list_view tbody .o_list_record_selector').length, 3, - "list view should contain 3 records"); - $('.modal .o_list_view tbody tr:contains(red)').click(); // select red - assert.ok(!$('.modal .o_list_view').length, "should have closed the modal"); - assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 3, - 'kanban should now contain 3 records'); - assert.ok(form.$('.o_kanban_record:contains(red)').length, - 'record "red" should be in the kanban'); - - // -> multiple select - form.$('.o_field_many2many .o-kanban-button-new').click(); - assert.ok($('.modal .o_select_button').prop('disabled'), "select button should be disabled"); - assert.strictEqual($('.modal .o_list_view tbody .o_list_record_selector').length, 2, - "list view should contain 2 records"); - $('.modal .o_list_view thead .o_list_record_selector input').click(); // select all - $('.modal .o_select_button').click(); // validate selection - assert.ok(!$('.modal .o_select_button').prop('disabled'), "select button should be enabled"); - assert.ok(!$('.modal .o_list_view').length, "should have closed the modal"); - assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 5, - 'kanban should now contain 5 records'); - // -> created record - form.$('.o_field_many2many .o-kanban-button-new').click(); - $('.modal .modal-footer .btn-primary:nth(1)').click(); // click on 'Create' - assert.ok($('.modal .o_form_view.o_form_editable').length, - "should have opened a form view in edit mode, in a modal"); - $('.modal .o_form_view input').val('A new type').trigger('input'); - $('.modal:nth(1) footer .btn-primary:first()').click(); // click on 'Save & Close' - assert.ok(!$('.modal').length, "should have closed both modals"); - assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 6, - 'kanban should now contain 6 records'); - assert.ok(form.$('.o_kanban_record:contains(A new type)').length, - 'the newly created type should be in the kanban'); - - // delete subrecords - form.$('.o_kanban_record:contains(silver)').click(); - assert.strictEqual($('.modal .modal-footer .o_btn_remove').length, 1, - 'There should be a modal having Remove Button'); - $('.modal .modal-footer .o_btn_remove').click(); // Click Remove Button of Formview Dialog - assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 5, - 'should contain 5 records'); - assert.ok(!form.$('.o_kanban_record:contains(silver)').length, - 'the removed record should not be in kanban anymore'); - - form.$('.o_kanban_record:contains(blue) .delete_icon').click(); - assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 4, - 'should contain 4 records'); - assert.ok(!form.$('.o_kanban_record:contains(blue)').length, - 'the removed record should not be in kanban anymore'); - - // save the record - form.$buttons.find('.o_form_button_save').click(); - form.destroy(); - }); - - QUnit.test('many2many kanban(editable): properly handle create_text node option', function (assert) { - assert.expect(1); - - this.data.partner.records[0].timmy = [12]; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '
' + - '' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
', - res_id: 1, - }); - - form.$buttons.find('.o_form_button_edit').click(); - assert.strictEqual(form.$('.o_field_many2many[name="timmy"] .o-kanban-button-new').text().trim(), - "Add timmy", "In M2M Kanban, Add button should have 'Add timmy' label"); - - form.destroy(); - }); - - QUnit.test('many2many kanban: create action disabled', function (assert) { - assert.expect(4); - - this.data.partner.records[0].timmy = [12, 14]; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '', - archs: { - 'partner_type,false,list': '', - 'partner_type,false,search': '' + - '' + - '', - }, - res_id: 1, - session: { user_context: {} }, - }); - - assert.ok(!form.$('.o-kanban-button-new').length, - '"Add" button should not be available in readonly'); - - form.$buttons.find('.o_form_button_edit').click(); - - assert.ok(form.$('.o-kanban-button-new').length, - '"Add" button should be available in edit'); - assert.ok(form.$('.o_kanban_view .delete_icon').length, - 'delete icon should be visible in edit'); - - form.$('.o-kanban-button-new').click(); // click on 'Add' - assert.strictEqual($('.modal .modal-footer .btn-primary').length, 1, // only button 'Select' - '"Create" button should not be available in the modal'); - - form.destroy(); - }); - - QUnit.test('many2many list (non editable): edition', function (assert) { - assert.expect(27); - - this.data.partner.records[0].timmy = [12, 14]; - this.data.partner_type.records.push({ id: 15, display_name: "bronze", color: 6 }); - this.data.partner_type.fields.float_field = { string: 'Float', type: 'float' }; - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '', - archs: { - 'partner_type,false,list': '', - 'partner_type,false,search': '', - }, - res_id: 1, - mockRPC: function (route, args) { - if (args.method !== 'load_views') { - assert.step(_.last(route.split('/'))); - } - if (args.method === 'write' && args.model === 'partner') { - assert.deepEqual(args.args[1].timmy, [ - [6, false, [12, 15]], - ]); - } - return this._super.apply(this, arguments); - }, - }); - - assert.ok(!form.$('.o_list_record_remove').length, - 'delete icon should not be visible in readonly'); - assert.ok(!form.$('.o_field_x2many_list_row_add').length, - '"Add an item" should not be visible in readonly'); - - form.$buttons.find('.o_form_button_edit').click(); - - assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 2, - 'should contain 2 records'); - assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'gold', - 'display_name of first subrecord should be the one in DB'); - assert.ok(form.$('.o_list_record_remove').length, - 'delete icon should be visible in edit'); - assert.ok(form.$('.o_field_x2many_list_row_add').length, - '"Add an item" should be visible in edit'); - - // edit existing subrecord - form.$('.o_list_view tbody tr:first()').click(); - - $('.modal .o_form_view input').val('new name').trigger('input'); - $('.modal .modal-footer .btn-primary').click(); // save - assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name', - 'value of subrecord should have been updated'); - - // add new subrecords - form.$('.o_field_x2many_list_row_add a').click(); - assert.strictEqual($('.modal .o_list_view').length, 1, - "a modal should be open"); - assert.strictEqual($('.modal .o_list_view .o_data_row').length, 1, - "the list should contain one row"); - $('.modal .o_list_view .o_data_row').click(); // select a record - assert.strictEqual($('.modal .o_list_view').length, 0, - "the modal should be closed"); - assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 3, - 'should contain 3 subrecords'); - - // remove subrecords - form.$('.o_list_record_remove:nth(1)').click(); - assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 2, - 'should contain 2 subrecords'); - assert.strictEqual(form.$('.o_list_view .o_data_row td:first').text(), 'new name', - 'the updated row still has the correct values'); - - // save - form.$buttons.find('.o_form_button_save').click(); - assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 2, - 'should contain 2 subrecords'); - assert.strictEqual(form.$('.o_list_view .o_data_row td:first').text(), - 'new name', 'the updated row still has the correct values'); - - assert.verifySteps([ - 'read', // main record - 'read', // relational field - 'read', // relational record in dialog - 'write', // save relational record from dialog - 'read', // relational field (updated) - 'search_read', // list view in dialog - 'read', // relational field (updated) - 'write', // save main record - 'read', // main record - 'read', // relational field - ]); - - form.destroy(); - }); - - QUnit.test('many2many list (editable): edition', function (assert) { - assert.expect(31); - - this.data.partner.records[0].timmy = [12, 14]; - this.data.partner_type.records.push({ id: 15, display_name: "bronze", color: 6 }); - this.data.partner_type.fields.float_field = { string: 'Float', type: 'float' }; - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '
', - archs: { - 'partner_type,false,list': '', - 'partner_type,false,search': '', - }, - mockRPC: function (route, args) { - if (args.method !== 'load_views') { - assert.step(_.last(route.split('/'))); - } - if (args.method === 'write') { - assert.deepEqual(args.args[1].timmy, [ - [6, false, [12, 15]], - [1, 12, { display_name: 'new name' }], - ]); - } - return this._super.apply(this, arguments); - }, - res_id: 1, - }); - - assert.ok(!form.$('.o_list_record_remove').length, - 'delete icon should not be visible in readonly'); - assert.ok(!form.$('.o_field_x2many_list_row_add').length, - '"Add an item" should not be visible in readonly'); - - form.$buttons.find('.o_form_button_edit').click(); - - assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 2, - 'should contain 2 records'); - assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'gold', - 'display_name of first subrecord should be the one in DB'); - assert.ok(form.$('.o_list_record_remove').length, - 'delete icon should be visible in edit'); - assert.ok(form.$('td.o_list_record_remove button').first().hasClass('fa fa-times'), - "should have X icons to remove (unlink) records"); - assert.ok(form.$('.o_field_x2many_list_row_add').length, - '"Add an item" should not visible in edit'); - - // edit existing subrecord - form.$('.o_list_view tbody td:first()').click(); - assert.ok(!$('.modal').length, - 'in edit, clicking on a subrecord should not open a dialog'); - assert.ok(form.$('.o_list_view tbody tr:first()').hasClass('o_selected_row'), - 'first row should be in edition'); - form.$('.o_list_view input:first()').val('new name').trigger('input'); - assert.ok(form.$('.o_list_view .o_data_row:first').hasClass('o_selected_row'), - 'first row should still be in edition'); - assert.strictEqual(form.$('.o_list_view input[name=display_name]').get(0), - document.activeElement, 'edited field should still have the focus'); - form.$el.click(); // click outside the list to validate the row - assert.ok(!form.$('.o_list_view tbody tr:first').hasClass('o_selected_row'), - 'first row should not be in edition anymore'); - assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name', - 'value of subrecord should have been updated'); - assert.verifySteps(['read', 'read']); - - // add new subrecords - form.$('.o_field_x2many_list_row_add a').click(); - assert.strictEqual($('.modal .o_list_view').length, 1, - "a modal should be open"); - assert.strictEqual($('.modal .o_list_view .o_data_row').length, 1, - "the list should contain one row"); - $('.modal .o_list_view .o_data_row').click(); // select a record - assert.strictEqual($('.modal .o_list_view').length, 0, - "the modal should be closed"); - assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 3, - 'should contain 3 subrecords'); - - // remove subrecords - form.$('.o_list_record_remove:nth(1)').click(); - assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 2, - 'should contain 2 subrecord'); - assert.strictEqual(form.$('.o_list_view tbody .o_data_row td:first').text(), - 'new name', 'the updated row still has the correct values'); - - // save - form.$buttons.find('.o_form_button_save').click(); - assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 2, - 'should contain 2 subrecords'); - assert.strictEqual(form.$('.o_list_view .o_data_row td:first').text(), - 'new name', 'the updated row still has the correct values'); - - assert.verifySteps([ - 'read', // main record - 'read', // relational field - 'search_read', // list view in dialog - 'read', // relational field (updated) - 'write', // save main record - 'read', // main record - 'read', // relational field - ]); - - form.destroy(); - }); - - QUnit.test('many2many: create & delete attributes', function (assert) { - assert.expect(4); - - this.data.partner.records[0].timmy = [12, 14]; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '
', - res_id: 1, - }); - - form.$buttons.find('.o_form_button_edit').click(); - - assert.strictEqual(form.$('.o_field_x2many_list_row_add').length, 1, "should have the 'Add an item' link"); - assert.strictEqual(form.$('.o_list_record_remove').length, 2, "should have the 'Add an item' link"); - - form.destroy(); - - form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '
', - res_id: 1, - }); - - form.$buttons.find('.o_form_button_edit').click(); - - assert.strictEqual(form.$('.o_field_x2many_list_row_add').length, 0, "should not have the 'Add an item' link"); - assert.strictEqual(form.$('.o_list_record_remove').length, 0, "should not have the 'Add an item' link"); - - form.destroy(); - }); - - QUnit.test('many2many list: create action disabled', function (assert) { - assert.expect(2); - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '
', - res_id: 1, - }); - - assert.ok(!form.$('.o_field_x2many_list_row_add').length, - '"Add an item" link should not be available in readonly'); - - form.$buttons.find('.o_form_button_edit').click(); - - assert.ok(!form.$('.o_field_x2many_list_row_add').length, - '"Add an item" link should not be available in edit either'); - - form.destroy(); - }); - - QUnit.test('many2many list: list of id as default value', function (assert) { - assert.expect(1); - - this.data.partner.fields.turtles.default = [2, 3]; - this.data.partner.fields.turtles.type = "many2many"; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '
', - }); - - assert.strictEqual(form.$('td.o_data_cell').text(), "blipkawa", - "should have loaded default data"); - - form.destroy(); - }); - - QUnit.test('many2many checkboxes with default values', function (assert) { - assert.expect(7); - - this.data.partner.fields.turtles.default = [3]; - this.data.partner.fields.turtles.type = "many2many"; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '
', - mockRPC: function (route, args) { - if (args.method === 'create') { - assert.deepEqual(args.args[0].turtles, [[6, false, [1]]], - "correct values should have been sent to create"); - } - return this._super.apply(this, arguments); - } - }); - - assert.notOk(form.$('.custom-checkbox input').eq(0).prop('checked'), - "first checkbox should not be checked"); - assert.notOk(form.$('.custom-checkbox input').eq(1).prop('checked'), - "second checkbox should not be checked"); - assert.ok(form.$('.custom-checkbox input').eq(2).prop('checked'), - "third checkbox should be checked"); - - form.$('.custom-checkbox input:checked').click(); // Uncheck default record - form.$('.custom-checkbox input').first().click(); // Check first record - form.$('.custom-checkbox input').first().click(); // Uncheck first record - form.$('.custom-checkbox input').first().click(); // Recheck first record - - assert.ok(form.$('.custom-checkbox input').eq(0).prop('checked'), - "first checkbox should be checked"); - assert.notOk(form.$('.custom-checkbox input').eq(1).prop('checked'), - "second checkbox should not be checked"); - assert.notOk(form.$('.custom-checkbox input').eq(2).prop('checked'), - "third checkbox should not be checked"); - - form.$buttons.find('.o_form_button_save').click(); - - form.destroy(); - }); - - QUnit.test('many2many checkboxes with default values', function (assert) { - assert.expect(7); - - this.data.partner.fields.turtles.default = [3]; - this.data.partner.fields.turtles.type = "many2many"; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '
', - mockRPC: function (route, args) { - if (args.method === 'create') { - assert.deepEqual(args.args[0].turtles, [[6, false, [1]]], - "correct values should have been sent to create"); - } - return this._super.apply(this, arguments); - } - }); - - assert.notOk(form.$('.custom-checkbox input').eq(0).prop('checked'), - "first checkbox should not be checked"); - assert.notOk(form.$('.custom-checkbox input').eq(1).prop('checked'), - "second checkbox should not be checked"); - assert.ok(form.$('.custom-checkbox input').eq(2).prop('checked'), - "third checkbox should be checked"); - - form.$('.custom-checkbox input:checked').click(); // Uncheck default record - form.$('.custom-checkbox input').first().click(); // Check first record - form.$('.custom-checkbox input').first().click(); // Uncheck first record - form.$('.custom-checkbox input').first().click(); // Recheck first record - - assert.ok(form.$('.custom-checkbox input').eq(0).prop('checked'), - "first checkbox should be checked"); - assert.notOk(form.$('.custom-checkbox input').eq(1).prop('checked'), - "second checkbox should not be checked"); - assert.notOk(form.$('.custom-checkbox input').eq(2).prop('checked'), - "third checkbox should not be checked"); - - form.$buttons.find('.o_form_button_save').click(); - - form.destroy(); - }); - - QUnit.test('many2many list with x2many: add a record', function (assert) { - assert.expect(18); - - this.data.partner_type.fields.m2m = { - string: "M2M", type: "many2many", relation: 'turtle', - }; - this.data.partner_type.records[0].m2m = [1, 2]; - this.data.partner_type.records[1].m2m = [2, 3]; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '', - res_id: 1, - archs: { - 'partner_type,false,list': '' + - '' + - '' + - '', - 'partner_type,false,search': '' + - '' + - '', - }, - mockRPC: function (route, args) { - if (args.method !== 'load_views') { - assert.step(_.last(route.split('/')) + ' on ' + args.model); - } - if (args.model === 'turtle') { - assert.step(args.args[0]); // the read ids - } - return this._super.apply(this, arguments); - }, - viewOptions: { - mode: 'edit', - }, - }); - - form.$('.o_field_x2many_list_row_add a').click(); - $('.modal .o_data_row:first').click(); // add a first record to the relation - - assert.strictEqual(form.$('.o_data_row').length, 1, - "the record should have been added to the relation"); - assert.strictEqual(form.$('.o_data_row:first .o_badge_text').text(), 'leonardodonatello', - "inner m2m should have been fetched and correctly displayed"); - - form.$('.o_field_x2many_list_row_add a').click(); - $('.modal .o_data_row:first').click(); // add a second record to the relation - - assert.strictEqual(form.$('.o_data_row').length, 2, - "the second record should have been added to the relation"); - assert.strictEqual(form.$('.o_data_row:nth(1) .o_badge_text').text(), 'donatelloraphael', - "inner m2m should have been fetched and correctly displayed"); - - assert.verifySteps([ - 'read on partner', - 'search_read on partner_type', - 'read on turtle', - [1, 2, 3], - 'read on partner_type', - 'read on turtle', - [1, 2], - 'search_read on partner_type', - 'read on turtle', - [2, 3], - 'read on partner_type', - 'read on turtle', - [2, 3], - ]); - - form.destroy(); - }); - - QUnit.test('many2many with a domain', function (assert) { - // The domain specified on the field should not be replaced by the potential - // domain the user writes in the dialog, they should rather be concatenated - assert.expect(2); - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '', - res_id: 1, - archs: { - 'partner_type,false,list': '' + - '' + - '', - 'partner_type,false,search': '' + - '' + - '', - }, - viewOptions: { - mode: 'edit', - }, - }); - - form.$('.o_field_x2many_list_row_add a').click(); - assert.strictEqual($('.modal .o_data_row').length, 1, - "should contain only one row (gold)"); - - $('.modal .o_searchview_input').trigger({ type: 'keypress', which: 115 }); // s - $('.modal .o_searchview_input').trigger({ type: 'keydown', which: 13 }); // enter - - assert.strictEqual($('.modal .o_data_row').length, 0, "should contain no row"); - - form.destroy(); - }); - - QUnit.test('many2many list with onchange and edition of a record', function (assert) { - assert.expect(8); - - this.data.partner.fields.turtles.type = "many2many"; - this.data.partner.onchanges.turtles = function () { }; - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '
', - res_id: 1, - archs: { - 'turtle,false,form': '
', - }, - mockRPC: function (route, args) { - assert.step(args.method); - return this._super.apply(this, arguments); - }, - }); - - form.$buttons.find('.o_form_button_edit').click(); - form.$('td.o_data_cell:first').click(); - - $('.modal-body input[type="checkbox"]').click(); - $('.modal .modal-footer .btn-primary').first().click(); - - // there is nothing left to save -> should not do a 'write' RPC - form.$buttons.find('.o_form_button_save').click(); - - assert.verifySteps([ - 'read', // read initial record (on partner) - 'read', // read many2many turtles - 'load_views', // load arch of turtles form view - 'read', // read missing field when opening record in modal form view - 'write', // when saving the modal - 'onchange', // onchange should be triggered on partner - 'read', // reload many2many - ]); - - form.destroy(); - }); - - QUnit.test('onchange with 40+ commands for a many2many', function (assert) { - // this test ensures that the basic_model correctly handles more LINK_TO - // commands than the limit of the dataPoint (40 for x2many kanban) - assert.expect(23); - - // create a lot of partner_types that will be linked by the onchange - var commands = [[5]]; - for (var i = 0; i < 45; i++) { - var id = 100 + i; - this.data.partner_type.records.push({ id: id, display_name: "type " + id }); - commands.push([4, id]); - } - this.data.partner.onchanges = { - foo: function (obj) { - obj.timmy = commands; - }, - }; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '
' + - '
' + - '
' + - '
' + - '
' + - '', - res_id: 1, - mockRPC: function (route, args) { - assert.step(args.method); - if (args.method === 'write') { - assert.strictEqual(args.args[1].timmy[0][0], 6, - "should send a command 6"); - assert.strictEqual(args.args[1].timmy[0][2].length, 45, - "should replace with 45 ids"); - } - return this._super.apply(this, arguments); - }, - viewOptions: { - mode: 'edit', - }, - }); - - assert.verifySteps(['read']); - - form.$('.o_field_widget[name=foo]').val('trigger onchange').trigger('input'); - - assert.verifySteps(['read', 'onchange', 'read']); - assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(), - '1-40 / 45', "pager should be correct"); - assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40, - 'there should be 40 records displayed on page 1'); - - form.$('.o_pager_next').click(); - assert.verifySteps(['read', 'onchange', 'read', 'read']); - assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(), - '41-45 / 45', "pager should be correct"); - assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 5, - 'there should be 5 records displayed on page 2'); - - form.$buttons.find('.o_form_button_save').click(); - - assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(), - '1-40 / 45', "pager should be correct"); - assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40, - 'there should be 40 records displayed on page 1'); - - form.$('.o_pager_next').click(); - assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(), - '41-45 / 45', "pager should be correct"); - assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 5, - 'there should be 5 records displayed on page 2'); - - form.$('.o_pager_next').click(); // back to page 1 - assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(), - '1-40 / 45', "pager should be correct"); - assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40, - 'there should be 40 records displayed on page 1'); - - form.destroy(); - }); - - QUnit.test('default_get, onchange, onchange on m2m', function (assert) { - assert.expect(1); - - this.data.partner.onchanges.int_field = function (obj) { - if (obj.int_field === 2) { - assert.deepEqual(obj.timmy, [ - [6, false, [12]], - [1, 12, { display_name: 'gold' }] - ]); - } - obj.timmy = [ - [5], - [1, 12, { display_name: 'gold' }] - ]; - }; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
', - }); - - form.$('.o_field_widget[name=int_field]').val(2).trigger('input'); - form.destroy(); - }); - - QUnit.test('widget many2many_tags', function (assert) { - assert.expect(1); - this.data.turtle.records[0].partner_ids = [2]; - - var form = createView({ - View: FormView, - model: 'turtle', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '
', - res_id: 1, - }); - - assert.deepEqual( - form.$('.o_field_many2manytags.o_field_widget .badge .o_badge_text').attr('title'), - 'second record', 'the title should be filled in' - ); - - form.destroy(); - }); - - QUnit.test('many2many list add *many* records, remove, re-add', function (assert) { - assert.expect(5); - - this.data.partner.fields.timmy.domain = [['color', '=', 2]]; - this.data.partner.fields.timmy.onChange = true; - this.data.partner_type.fields.product_ids = { string: "Product", type: "many2many", relation: 'product' }; - - for (var i = 0; i < 50; i++) { - var new_record_partner_type = { id: 100 + i, display_name: "batch" + i, color: 2 }; - this.data.partner_type.records.push(new_record_partner_type); - } - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '
', - res_id: 1, - archs: { - 'partner_type,false,list': '', - 'partner_type,false,search': '', - }, - mockRPC: function (route, args) { - if (args.method === 'get_formview_id') { - assert.deepEqual(args.args[0], [1], "should call get_formview_id with correct id"); - return $.when(false); - } - return this._super(route, args); - }, - }); - - // First round: add 51 records in batch - form.$buttons.find('.btn.btn-primary.o_form_button_edit').click(); - form.$('.o_field_x2many_list_row_add a').click(); - - var $modal = $('.modal-lg'); - - assert.equal($modal.length, 1, - 'There should be one modal'); - - $modal.find('thead input[type=checkbox]').click(); //select all the records we created in batch + 'gold' - - $modal.find('.btn.btn-primary.o_select_button').click(); - - var m2m_records = form.$('.o_field_many2many.o_field_widget.o_field_x2many.o_field_x2many_list .o_data_cell:not(.o_many2many_tags_cell)'); - assert.equal(m2m_records.length, 51, - 'We should have added all the records present in the search view to the m2m field'); // the 50 in batch + 'gold' - - form.$buttons.find('.btn.btn-primary.o_form_button_save').click(); - - // Secound round: remove one record - form.$buttons.find('.btn.btn-primary.o_form_button_edit').click(); - var trash_buttons = form.$('.o_field_many2many.o_field_widget.o_field_x2many.o_field_x2many_list .o_list_record_remove'); - - trash_buttons.first().click(); - - var pager_limit = form.$('.o_field_many2many.o_field_widget.o_field_x2many.o_field_x2many_list .o_pager_limit'); - assert.equal(pager_limit.text(), '50', - 'We should have 50 records in the m2m field'); - - // Third round: re-add 1 records - form.$('.o_field_x2many_list_row_add a').click(); - - $modal = $('.modal-lg'); - - assert.equal($modal.length, 1, - 'There should be one modal'); - - $modal.find('thead input[type=checkbox]').click(); - - $modal.find('.btn.btn-primary.o_select_button').click(); - - pager_limit = form.$('.o_field_many2many.o_field_widget.o_field_x2many.o_field_x2many_list .o_pager_limit'); - assert.equal(pager_limit.text(), '51', - 'We should have 51 records in the m2m field'); - - form.destroy(); - }); - }); -}); -}); diff --git a/addons/web/static/tests/fields/relational_fields/field_many_to_one_tests.js b/addons/web/static/tests/fields/relational_fields/field_many_to_one_tests.js deleted file mode 100644 index 72044b99d2f..00000000000 --- a/addons/web/static/tests/fields/relational_fields/field_many_to_one_tests.js +++ /dev/null @@ -1,3025 +0,0 @@ -odoo.define('web.field_many_to_one_tests', function (require) { -"use strict"; - -var BasicModel = require('web.BasicModel'); -var concurrency = require('web.concurrency'); -var FormView = require('web.FormView'); -var ListView = require('web.ListView'); -var relationalFields = require('web.relational_fields'); -var StandaloneFieldManagerMixin = require('web.StandaloneFieldManagerMixin'); -var testUtils = require('web.test_utils'); -var Widget = require('web.Widget'); - -var createView = testUtils.createView; - -QUnit.module('fields', {}, function () { - - QUnit.module('relational_fields', { - beforeEach: function () { - this.data = { - partner: { - fields: { - display_name: { string: "Displayed name", type: "char" }, - foo: { string: "Foo", type: "char", default: "My little Foo Value" }, - bar: { string: "Bar", type: "boolean", default: true }, - int_field: { string: "int_field", type: "integer", sortable: true }, - p: { string: "one2many field", type: "one2many", relation: 'partner', relation_field: 'trululu' }, - turtles: { string: "one2many turtle field", type: "one2many", relation: 'turtle', relation_field: 'turtle_trululu' }, - trululu: { string: "Trululu", type: "many2one", relation: 'partner' }, - timmy: { string: "pokemon", type: "many2many", relation: 'partner_type' }, - product_id: { string: "Product", type: "many2one", relation: 'product' }, - color: { - type: "selection", - selection: [['red', "Red"], ['black', "Black"]], - default: 'red', - string: "Color", - }, - date: { string: "Some Date", type: "date" }, - datetime: { string: "Datetime Field", type: 'datetime' }, - user_id: { string: "User", type: 'many2one', relation: 'user' }, - reference: { - string: "Reference Field", type: 'reference', selection: [ - ["product", "Product"], ["partner_type", "Partner Type"], ["partner", "Partner"]] - }, - }, - records: [{ - id: 1, - display_name: "first record", - bar: true, - foo: "yop", - int_field: 10, - p: [], - turtles: [2], - timmy: [], - trululu: 4, - user_id: 17, - reference: 'product,37', - }, { - id: 2, - display_name: "second record", - bar: true, - foo: "blip", - int_field: 9, - p: [], - timmy: [], - trululu: 1, - product_id: 37, - date: "2017-01-25", - datetime: "2016-12-12 10:55:05", - user_id: 17, - }, { - id: 4, - display_name: "aaa", - bar: false, - }], - onchanges: {}, - }, - product: { - fields: { - name: { string: "Product Name", type: "char" } - }, - records: [{ - id: 37, - display_name: "xphone", - }, { - id: 41, - display_name: "xpad", - }] - }, - partner_type: { - fields: { - name: { string: "Partner Type", type: "char" }, - color: { string: "Color index", type: "integer" }, - }, - records: [ - { id: 12, display_name: "gold", color: 2 }, - { id: 14, display_name: "silver", color: 5 }, - ] - }, - turtle: { - fields: { - display_name: { string: "Displayed name", type: "char" }, - turtle_foo: { string: "Foo", type: "char" }, - turtle_bar: { string: "Bar", type: "boolean", default: true }, - turtle_int: { string: "int", type: "integer", sortable: true }, - turtle_trululu: { string: "Trululu", type: "many2one", relation: 'partner' }, - turtle_ref: { - string: "Reference", type: 'reference', selection: [ - ["product", "Product"], ["partner", "Partner"]] - }, - product_id: { string: "Product", type: "many2one", relation: 'product', required: true }, - partner_ids: { string: "Partner", type: "many2many", relation: 'partner' }, - }, - records: [{ - id: 1, - display_name: "leonardo", - turtle_bar: true, - turtle_foo: "yop", - partner_ids: [], - }, { - id: 2, - display_name: "donatello", - turtle_bar: true, - turtle_foo: "blip", - turtle_int: 9, - partner_ids: [2, 4], - }, { - id: 3, - display_name: "raphael", - product_id: 37, - turtle_bar: false, - turtle_foo: "kawa", - turtle_int: 21, - partner_ids: [], - turtle_ref: 'product,37', - }], - onchanges: {}, - }, - user: { - fields: { - name: { string: "Name", type: "char" }, - partner_ids: { string: "one2many partners field", type: "one2many", relation: 'partner', relation_field: 'user_id' }, - }, - records: [{ - id: 17, - name: "Aline", - partner_ids: [1, 2], - }, { - id: 19, - name: "Christine", - }] - }, - }; - } - }, function () { - QUnit.module('FieldMany2One'); - - QUnit.test('many2ones in form views', function (assert) { - assert.expect(5); - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '
', - archs: { - 'partner,false,form': '
', - }, - res_id: 1, - mockRPC: function (route, args) { - if (args.method === 'get_formview_action') { - assert.deepEqual(args.args[0], [4], "should call get_formview_action with correct id"); - return $.when({ - res_id: 17, - type: 'ir.actions.act_window', - target: 'current', - res_model: 'res.partner' - }); - } - if (args.method === 'get_formview_id') { - assert.deepEqual(args.args[0], [4], "should call get_formview_id with correct id"); - return $.when(false); - } - return this._super(route, args); - }, - }); - - testUtils.intercept(form, 'do_action', function (event) { - assert.strictEqual(event.data.action.res_id, 17, - "should do a do_action with correct parameters"); - }); - - assert.strictEqual(form.$('a.o_form_uri:contains(aaa)').length, 1, - "should contain a link"); - form.$('a.o_form_uri').click(); // click on the link in readonly mode (should trigger do_action) - - form.$buttons.find('.o_form_button_edit').click(); - - form.$('.o_external_button').click(); // click on the external button (should do an RPC) - - assert.strictEqual($('.modal .modal-title').text().trim(), 'Open: custom label', - "dialog title should display the custom string label"); - - // TODO: test that we can edit the record in the dialog, and that the value is correctly - // updated on close - form.destroy(); - }); - - QUnit.test('editing a many2one, but not changing anything', function (assert) { - assert.expect(2); - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '
', - archs: { - 'partner,false,form': '
', - }, - res_id: 1, - mockRPC: function (route, args) { - if (args.method === 'get_formview_id') { - assert.deepEqual(args.args[0], [4], "should call get_formview_id with correct id"); - return $.when(false); - } - return this._super(route, args); - }, - viewOptions: { - ids: [1, 2], - }, - }); - - form.$buttons.find('.o_form_button_edit').click(); - - // click on the external button (should do an RPC) - form.$('.o_external_button').click(); - // save and close modal - $('.modal .modal-footer .btn-primary:first').click(); - // save form - form.$buttons.find('.o_form_button_save').click(); - // click next on pager - form.pager.$('.o_pager_next').click(); - - // this checks that the view did not ask for confirmation that the - // record is dirty - assert.strictEqual(form.pager.$el.text().trim(), '2 / 2', - 'pager should be at second page'); - form.destroy(); - }); - - QUnit.test('context in many2one and default get', function (assert) { - assert.expect(1); - - this.data.partner.fields.int_field.default = 14; - this.data.partner.fields.trululu.default = 2; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '', - mockRPC: function (route, args) { - if (args.method === 'name_get') { - assert.strictEqual(args.kwargs.context.blip, 14, - 'context should have been properly sent to the nameget rpc'); - } - return this._super(route, args); - }, - }); - form.destroy(); - }); - - QUnit.test('editing a many2one (with form view opened with external button)', function (assert) { - assert.expect(1); - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '
', - archs: { - 'partner,false,form': '
', - }, - res_id: 1, - mockRPC: function (route, args) { - if (args.method === 'get_formview_id') { - return $.when(false); - } - return this._super(route, args); - }, - viewOptions: { - ids: [1, 2], - }, - }); - - form.$buttons.find('.o_form_button_edit').click(); - - // click on the external button (should do an RPC) - form.$('.o_external_button').click(); - - $('.modal input[name="foo"]').val('brandon').trigger('input'); - - // save and close modal - $('.modal .modal-footer .btn-primary:first').click(); - // save form - form.$buttons.find('.o_form_button_save').click(); - // click next on pager - form.pager.$('.o_pager_next').click(); - - // this checks that the view did not ask for confirmation that the - // record is dirty - assert.strictEqual(form.pager.$el.text().trim(), '2 / 2', - 'pager should be at second page'); - form.destroy(); - }); - - QUnit.test('many2ones in form views with show_adress', function (assert) { - assert.expect(4); - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '
', - mockRPC: function (route, args) { - if (args.method === 'name_get') { - return this._super(route, args).then(function (result) { - result[0][1] += '\nStreet\nCity ZIP'; - return result; - }); - } - return this._super(route, args); - }, - res_id: 1, - }); - - assert.strictEqual($('a.o_form_uri').html(), 'aaa
Street
City ZIP', - "input should have a multi-line content in readonly due to show_address"); - form.$buttons.find('.o_form_button_edit').click(); - assert.strictEqual(form.$('button.o_external_button:visible').length, 1, - "should have an open record button"); - - form.$('input.o_input').click(); - - assert.strictEqual(form.$('button.o_external_button:visible').length, 1, - "should still have an open record button"); - form.$('input.o_input').trigger('focusout'); - assert.strictEqual($('.modal button:contains(Create and edit)').length, 0, - "there should not be a quick create modal"); - - form.destroy(); - }); - - QUnit.test('show_address works in a view embedded in a view of another type', function (assert) { - assert.expect(1); - - this.data.turtle.records[1].turtle_trululu = 2; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '', - res_id: 1, - archs: { - "turtle,false,form": '
' + - '' + - '' + - '', - "turtle,false,list": '' + - '' + - '', - }, - mockRPC: function (route, args) { - if (args.method === 'name_get') { - return this._super(route, args).then(function (result) { - if (args.model === 'partner' && args.kwargs.context.show_address) { - result[0][1] += '\nrue morgue\nparis 75013'; - } - return result; - }); - } - return this._super(route, args); - }, - }); - // click the turtle field, opens a modal with the turtle form view - form.$('.o_data_row:first td.o_data_cell').click(); - - assert.strictEqual($('[name="turtle_trululu"]').text(), "second recordrue morgueparis 75013", - "The partner's address should be displayed"); - form.destroy(); - }); - - QUnit.test('many2one data is reloaded if there is a context to take into account', function (assert) { - assert.expect(1); - - this.data.turtle.records[1].turtle_trululu = 2; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '', - res_id: 1, - archs: { - "turtle,false,form": '
' + - '' + - '' + - '', - "turtle,false,list": '' + - '' + - '' + - '', - }, - mockRPC: function (route, args) { - if (args.method === 'name_get') { - return this._super(route, args).then(function (result) { - if (args.model === 'partner' && args.kwargs.context.show_address) { - result[0][1] += '\nrue morgue\nparis 75013'; - } - return result; - }); - } - return this._super(route, args); - }, - }); - // click the turtle field, opens a modal with the turtle form view - form.$('.o_data_row:first').click(); - - assert.strictEqual($('.modal [name=turtle_trululu]').text(), "second recordrue morgueparis 75013", - "The partner's address should be displayed"); - form.destroy(); - }); - - QUnit.test('many2ones in form views with search more', function (assert) { - assert.expect(3); - this.data.partner.records.push({ - id: 5, - display_name: "Partner 4", - }, { - id: 6, - display_name: "Partner 5", - }, { - id: 7, - display_name: "Partner 6", - }, { - id: 8, - display_name: "Partner 7", - }, { - id: 9, - display_name: "Partner 8", - }, { - id: 10, - display_name: "Partner 9", - }); - this.data.partner.fields.datetime.searchable = true; - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '
', - archs: { - 'partner,false,list': '', - 'partner,false,search': '', - }, - res_id: 1, - }); - - form.$buttons.find('.o_form_button_edit').click(); - var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); - form.$('.o_field_many2one input').click(); - $dropdown.find('.o_m2o_dropdown_option:contains(Search)').mouseenter().click(); // Open Search More - - assert.strictEqual($('tr.o_data_row').length, 9, "should display 9 records"); - - $('button:contains(Filters)').click(); - $('.o_add_custom_filter:visible').click(); // Add a custom filter, datetime field is selected - assert.strictEqual($('.o_filter_condition select.o_searchview_extended_prop_field').val(), 'datetime', - "datetime field should be selected"); - $('.o_apply_filter').click(); - - assert.strictEqual($('tr.o_data_row').length, 0, "should display 0 records"); - form.destroy(); - }); - - QUnit.test('onchanges on many2ones trigger when editing record in form view', function (assert) { - assert.expect(10); - - this.data.partner.onchanges.user_id = function () { }; - this.data.user.fields.other_field = { string: "Other Field", type: "char" }; - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '
', - archs: { - 'user,false,form': '
', - }, - res_id: 1, - mockRPC: function (route, args) { - assert.step(args.method); - if (args.method === 'get_formview_id') { - return $.when(false); - } - if (args.method === 'onchange') { - assert.strictEqual(args.args[1].user_id, 17, - "onchange is triggered with correct user_id"); - } - return this._super(route, args); - }, - }); - - // open the many2one in form view and change something - form.$buttons.find('.o_form_button_edit').click(); - form.$('.o_external_button').click(); - $('.modal-body input[name="other_field"]').val('wood').trigger('input'); - - // save the modal and make sure an onchange is triggered - $('.modal .modal-footer .btn-primary').first().click(); - assert.verifySteps(['read', 'get_formview_id', 'load_views', 'read', 'write', 'onchange', 'read']); - - // save the main record, and check that no extra rpcs are done (record - // is not dirty, only a related record was modified) - form.$buttons.find('.o_form_button_save').click(); - assert.verifySteps(['read', 'get_formview_id', 'load_views', 'read', 'write', 'onchange', 'read']); - form.destroy(); - }); - - QUnit.test('many2one fields with option "no_open"', function (assert) { - assert.expect(3); - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '
', - res_id: 1, - }); - - assert.strictEqual(form.$('span.o_field_widget[name=trululu]').length, 1, - "should be displayed inside a span (sanity check)"); - assert.strictEqual(form.$('span.o_form_uri').length, 0, "should not have an anchor"); - - form.$buttons.find('.o_form_button_edit').click(); - assert.strictEqual(form.$('.o_field_widget[name=trululu] .o_external_button').length, - 0, "should not have the button to open the record"); - - form.destroy(); - }); - - QUnit.test('many2one in edit mode', function (assert) { - assert.expect(16); - - // create 10 partners to have the 'Search More' option in the autocomplete dropdown - for (var i = 0; i < 10; i++) { - var id = 20 + i; - this.data.partner.records.push({ id: id, display_name: "Partner " + id }); - } - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '
', - res_id: 1, - archs: { - 'partner,false,list': '', - 'partner,false,search': '' + - '' + - '', - }, - mockRPC: function (route, args) { - if (route === '/web/dataset/call_kw/partner/write') { - assert.strictEqual(args.args[1].trululu, 20, "should write the correct id"); - } - return this._super.apply(this, arguments); - }, - }); - - // the SelectCreateDialog requests the session, so intercept its custom - // event to specify a fake session to prevent it from crashing - testUtils.intercept(form, 'get_session', function (event) { - event.data.callback({ user_context: {} }); - }); - - form.$buttons.find('.o_form_button_edit').click(); - var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); - - form.$('.o_field_many2one input').click(); - assert.ok($dropdown.is(':visible'), - 'clicking on the m2o input should open the dropdown if it is not open yet'); - assert.strictEqual($dropdown.find('li:not(.o_m2o_dropdown_option)').length, 7, - 'autocomplete should contains 7 suggestions'); - assert.strictEqual($dropdown.find('li.o_m2o_dropdown_option').length, 2, - 'autocomplete should contain "Search More" and Create and Edit..."'); - - form.$('.o_field_many2one input').click(); - assert.ok(!$dropdown.is(':visible'), - 'clicking on the m2o input should close the dropdown if it is open'); - - // change the value of the m2o with a suggestion of the dropdown - form.$('.o_field_many2one input').click(); - $dropdown.find('li:first()').click(); - assert.ok(!$dropdown.is(':visible'), 'clicking on a value should close the dropdown'); - assert.strictEqual(form.$('.o_field_many2one input').val(), 'first record', - 'value of the m2o should have been correctly updated'); - - // change the value of the m2o with a record in the 'Search More' modal - form.$('.o_field_many2one input').click(); - // click on 'Search More' (mouseenter required by ui-autocomplete) - $dropdown.find('.o_m2o_dropdown_option:contains(Search)').mouseenter().click(); - assert.ok($('.modal .o_list_view').length, "should have opened a list view in a modal"); - assert.ok(!$('.modal .o_list_view .o_list_record_selector').length, - "there should be no record selector in the list view"); - assert.ok(!$('.modal .modal-footer .o_select_button').length, - "there should be no 'Select' button in the footer"); - assert.ok($('.modal tbody tr').length > 10, "list should contain more than 10 records"); - // filter the list using the searchview - $('.modal .o_searchview_input').trigger({ type: 'keypress', which: 80 }); // P - $('.modal .o_searchview_input').trigger({ type: 'keydown', which: 13 }); // enter - assert.strictEqual($('.modal tbody tr').length, 10, - "list should be restricted to records containing a P (10 records)"); - // choose a record - $('.modal tbody tr:contains(Partner 20)').click(); // choose record 'Partner 20' - assert.ok(!$('.modal').length, "should have closed the modal"); - assert.ok(!$dropdown.is(':visible'), 'should have closed the dropdown'); - assert.strictEqual(form.$('.o_field_many2one input').val(), 'Partner 20', - 'value of the m2o should have been correctly updated'); - - // save - form.$buttons.find('.o_form_button_save').click(); - assert.strictEqual(form.$('a.o_form_uri').text(), 'Partner 20', - "should display correct value after save"); - - form.destroy(); - }); - - QUnit.test('many2one in non edit mode', function (assert) { - assert.expect(3); - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '', - res_id: 1, - }); - - assert.strictEqual(form.$('a.o_form_uri').length, 1, - "should display 1 m2o link in form"); - assert.strictEqual(form.$('a.o_form_uri').attr('href'), "#id=4&model=partner", - "href should contain id and model"); - - // Remove value from many2one and then save, there should not have href with id and model on m2o anchor - form.$buttons.find('.o_form_button_edit').click(); - form.$('.o_field_many2one input').val('').trigger('keyup').trigger('focusout'); - form.$buttons.find('.o_form_button_save').click(); - - assert.strictEqual(form.$('a.o_form_uri').attr('href'), "#", - "href should have #"); - - form.destroy(); - }); - - QUnit.test('many2one searches with correct value', function (assert) { - var done = assert.async(); - assert.expect(6); - - var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '
', - res_id: 1, - mockRPC: function (route, args) { - if (args.method === 'name_search') { - assert.step('search: ' + args.kwargs.name); - } - return this._super.apply(this, arguments); - }, - viewOptions: { - mode: 'edit', - }, - }); - - assert.strictEqual(form.$('.o_field_many2one input').val(), 'aaa', - "should be initially set to 'aaa'"); - - form.$('.o_field_many2one input').click(); // should search with '' - // unset the many2one -> should search again with '' - form.$('.o_field_many2one input').val('').trigger('keydown'); - concurrency.delay(0).then(function () { - // write 'p' -> should search with 'p' - form.$('.o_field_many2one input').val('p').trigger('keydown').trigger('keyup'); - - return concurrency.delay(0); - }).then(function () { - // close and re-open the dropdown -> should search with 'p' again - form.$('.o_field_many2one input').click(); - form.$('.o_field_many2one input').click(); - - assert.verifySteps(['search: ', 'search: ', 'search: p', 'search: p']); - - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - form.destroy(); - done(); - }); - }); - - QUnit.test('many2one field with option always_reload', function (assert) { - assert.expect(4); - var count = 0; - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '', - res_id: 2, - mockRPC: function (route, args) { - if (args.method === 'name_get') { - count++; - return $.when([[1, "first record\nand some address"]]); - } - return this._super(route, args); - }, - }); - - assert.strictEqual(count, 1, "an extra name_get should have been done"); - assert.ok(form.$('a:contains(and some address)').length, - "should display additional result"); - - form.$buttons.find('.o_form_button_edit').click(); - - assert.strictEqual(form.$('input').val(), "first record", - "actual field value should be displayed to be edited"); - - form.$buttons.find('.o_form_button_save').click(); - - assert.ok(form.$('a:contains(and some address)').length, - "should still display additional result"); - form.destroy(); - }); - - QUnit.test('many2one field and list navigation', function (assert) { - assert.expect(3); - - var list = createView({ - View: ListView, - model: 'partner', - data: this.data, - arch: '', - }); - - // edit first input, to trigger autocomplete - list.$('.o_data_row .o_data_cell').first().click(); - list.$('.o_data_cell input').val('').trigger('input'); - - // press keydown, to select first choice - list.$('.o_data_cell input').focus().trigger($.Event('keydown', { - which: $.ui.keyCode.DOWN, - keyCode: $.ui.keyCode.DOWN, - })); - - // we now check that the dropdown is open (and that the focus did not go - // to the next line) - var $dropdown = list.$('.o_field_many2one input').autocomplete('widget'); - assert.ok($dropdown.is(':visible'), "dropdown should be visible"); - assert.ok(list.$('.o_data_row:eq(0)').hasClass('o_selected_row'), - 'first data row should still be selected'); - assert.ok(!list.$('.o_data_row:eq(1)').hasClass('o_selected_row'), - 'second data row should not be selected'); - - list.destroy(); - }); - - QUnit.test('standalone many2one field', function (assert) { - assert.expect(4); - var done = assert.async(); - - var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; - - var fixture = $('#qunit-fixture'); - var self = this; - - var model = testUtils.createModel({ - Model: BasicModel, - data: this.data, - }); - - model.makeRecord('coucou', [{ - name: 'partner_id', - relation: 'partner', - type: 'many2one', - value: [1, 'first partner'], - }]).then(function (recordID) { - var record = model.get(recordID); - // create a new widget that uses the StandaloneFieldManagerMixin - var StandaloneWidget = Widget.extend(StandaloneFieldManagerMixin, { - init: function (parent) { - this._super.apply(this, arguments); - StandaloneFieldManagerMixin.init.call(this, parent); - }, - }); - var parent = new StandaloneWidget(model); - testUtils.addMockEnvironment(parent, { - data: self.data, - mockRPC: function (route, args) { - assert.step(args.method); - return this._super.apply(this, arguments); - }, - }); - - var relField = new relationalFields.FieldMany2One(parent, 'partner_id', record, { - mode: 'edit', - noOpen: true, - }); - - relField.appendTo(fixture); - $('input.o_input').val('xyzzrot').trigger('input'); - - concurrency.delay(0).then(function () { - var $dropdown = $('input.o_input').autocomplete('widget'); - $dropdown.find('.o_m2o_dropdown_option:contains(Create)') - .first().mouseenter().click(); - - assert.strictEqual(relField.$('.o_external_button').length, 0, - "should not have the button to open the record"); - assert.verifySteps(['name_search', 'name_create']); - - parent.destroy(); - model.destroy(); - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - done(); - }); - }); - }); - - // QUnit.test('onchange on a many2one to a different model', function (assert) { - // This test is commented because the mock server does not give the correct response. - // It should return a couple [id, display_name], but I don't know the logic used - // by the server, so it's hard to emulate it correctly - // assert.expect(2); - - // this.data.partner.records[0].product_id = 41; - // this.data.partner.onchanges = { - // foo: function(obj) { - // obj.product_id = 37; - // }, - // }; - - // var form = createView({ - // View: FormView, - // model: 'partner', - // data: this.data, - // arch: '
' + - // '' + - // '' + - // '', - // res_id: 1, - // }); - // form.$buttons.find('.o_form_button_edit').click(); - // assert.strictEqual(form.$('input').eq(1).val(), 'xpad', "initial product_id val should be xpad"); - - // form.$('input').eq(0).val("let us trigger an onchange").trigger('input'); - - // assert.strictEqual(form.$('input').eq(1).val(), 'xphone', "onchange should have been applied"); - // }); - - QUnit.test('form: quick create then save directly', function (assert) { - var done = assert.async(); - assert.expect(5); - - var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; - - var def = $.Deferred(); - var newRecordID; - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '', - mockRPC: function (route, args) { - var result = this._super.apply(this, arguments); - if (args.method === 'name_create') { - assert.step('name_create'); - return def.then(_.constant(result)).then(function (nameGet) { - newRecordID = nameGet[0]; - return nameGet; - }); - } - if (args.method === 'create') { - assert.step('create'); - assert.strictEqual(args.args[0].trululu, newRecordID, - "should create with the correct m2o id"); - } - return result; - }, - }); - - var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); - form.$('.o_field_many2one input').val('b').trigger('keydown'); - concurrency.delay(0).then(function () { - $dropdown.find('li:first()').click(); // quick create 'b' - form.$buttons.find('.o_form_button_save').click(); - - assert.verifySteps(['name_create'], - "should wait for the name_create before creating the record"); - - def.resolve(); - - assert.verifySteps(['name_create', 'create']); - - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - form.destroy(); - done(); - }); - }); - - QUnit.test('list: quick create then save directly', function (assert) { - var done = assert.async(); - assert.expect(8); - - var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; - - var def = $.Deferred(); - var newRecordID; - var list = createView({ - View: ListView, - model: 'partner', - data: this.data, - arch: '' + - '' + - '', - mockRPC: function (route, args) { - var result = this._super.apply(this, arguments); - if (args.method === 'name_create') { - assert.step('name_create'); - return def.then(_.constant(result)).then(function (nameGet) { - newRecordID = nameGet[0]; - return nameGet; - }); - } - if (args.method === 'create') { - assert.step('create'); - assert.strictEqual(args.args[0].trululu, newRecordID, - "should create with the correct m2o id"); - } - return result; - }, - }); - - list.$buttons.find('.o_list_button_add').click(); - - var $dropdown = list.$('.o_field_many2one input').autocomplete('widget'); - list.$('.o_field_many2one input').val('b').trigger('keydown'); - concurrency.delay(0).then(function () { - $dropdown.find('li:first()').click(); // quick create 'b' - list.$buttons.find('.o_list_button_add').click(); - - assert.verifySteps(['name_create'], - "should wait for the name_create before creating the record"); - assert.strictEqual(list.$('.o_data_row').length, 4, - "should wait for the name_create before adding the new row"); - - def.resolve(); - - assert.verifySteps(['name_create', 'create']); - assert.strictEqual(list.$('.o_data_row:nth(1) .o_data_cell').text(), 'b', - "created row should have the correct m2o value"); - assert.strictEqual(list.$('.o_data_row').length, 5, - "should have added the fifth row"); - - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - list.destroy(); - done(); - }); - }); - - QUnit.test('list in form: quick create then save directly', function (assert) { - var done = assert.async(); - assert.expect(6); - - var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; - - var def = $.Deferred(); - var newRecordID; - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
', - mockRPC: function (route, args) { - var result = this._super.apply(this, arguments); - if (args.method === 'name_create') { - assert.step('name_create'); - return def.then(_.constant(result)).then(function (nameGet) { - newRecordID = nameGet[0]; - return nameGet; - }); - } - if (args.method === 'create') { - assert.step('create'); - assert.strictEqual(args.args[0].p[0][2].trululu, newRecordID, - "should create with the correct m2o id"); - } - return result; - }, - }); - - form.$('.o_field_x2many_list_row_add a').click(); - - var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); - form.$('.o_field_many2one input').val('b').trigger('keydown'); - concurrency.delay(0).then(function () { - $dropdown.find('li:first()').click(); // quick create 'b' - form.$buttons.find('.o_form_button_save').click(); - - assert.verifySteps(['name_create'], - "should wait for the name_create before creating the record"); - - def.resolve(); - - assert.verifySteps(['name_create', 'create']); - assert.strictEqual(form.$('.o_data_row:first .o_data_cell').text(), 'b', - "first row should have the correct m2o value"); - - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - form.destroy(); - done(); - }); - }); - - QUnit.test('list in form: quick create then add a new line directly', function (assert) { - // required many2one inside a one2many list: directly after quick creating - // a new many2one value (before the name_create returns), click on add an item: - // at this moment, the many2one has still no value, and as it is required, - // the row is discarded if a saveLine is requested. However, it should - // wait for the name_create to return before trying to save the line. - var done = assert.async(); - assert.expect(8); - - this.data.partner.onchanges = { - trululu: function () { }, - }; - - var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; - - var def = $.Deferred(); - var newRecordID; - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
', - mockRPC: function (route, args) { - var result = this._super.apply(this, arguments); - if (args.method === 'name_create') { - return def.then(_.constant(result)).then(function (nameGet) { - newRecordID = nameGet[0]; - return nameGet; - }); - } - if (args.method === 'create') { - assert.deepEqual(args.args[0].p[0][2].trululu, newRecordID); - } - return result; - }, - }); - - form.$('.o_field_x2many_list_row_add a').click(); - - var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); - form.$('.o_field_many2one input').val('b').trigger('keydown'); - concurrency.delay(0).then(function () { - $dropdown.find('li:first()').click(); // quick create 'b' - form.$('.o_field_x2many_list_row_add a').click(); - - assert.strictEqual(form.$('.o_data_row').length, 1, - "there should still be only one row"); - assert.ok(form.$('.o_data_row').hasClass('o_selected_row'), - "the row should still be in edition"); - - def.resolve(); - - assert.strictEqual(form.$('.o_data_row:first .o_data_cell').text(), 'b', - "first row should have the correct m2o value"); - assert.strictEqual(form.$('.o_data_row').length, 2, - "there should now be 2 rows"); - assert.ok(form.$('.o_data_row:nth(1)').hasClass('o_selected_row'), - "the second row should be in edition"); - - form.$buttons.find('.o_form_button_save').click(); - - assert.strictEqual(form.$('.o_data_row').length, 1, - "there should be 1 row saved (the second one was empty and invalid)"); - assert.strictEqual(form.$('.o_data_row .o_data_cell').text(), 'b', - "should have the correct m2o value"); - - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - form.destroy(); - done(); - }); - }); - - QUnit.test('list in form: create with one2many with many2one', function (assert) { - assert.expect(1); - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
', - mockRPC: function (route, args) { - if (args.method === 'default_get') { - return $.when({ p: [[0, 0, { display_name: 'new record' }]] }); - } else if (args.method === 'name_get') { - // This should not be called at all and thus is not accounted for - // in the assert.expect. If this is called, you broke this test. - assert.notOk(_.str.startsWith(args.args[0][0], 'virtual_'), - "should not call name_get for the m2o inside o2m which has no value"); - } - return this._super.apply(this, arguments); - }, - }); - - assert.strictEqual($('td.o_data_cell:first').text(), 'new record', - "should have created the new record in the o2m with the correct name"); - - form.destroy(); - }); - - QUnit.test('list in form: create with one2many with many2one (version 2)', function (assert) { - // This test simulates the exact same scenario as the previous one, - // except that the value for the many2one is explicitely set to false, - // which is stupid, but this happens, so we have to handle it - assert.expect(1); - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
', - mockRPC: function (route, args) { - if (args.method === 'default_get') { - return $.when({ p: [[0, 0, { display_name: 'new record', trululu: false }]] }); - } else if (args.method === 'name_get') { - // This should not be called at all and thus is not accounted for - // in the assert.expect. If this is called, you broke this test. - assert.notOk(_.str.startsWith(args.args[0][0], 'virtual_'), - "should not call name_get for the m2o inside o2m which has no value"); - } - return this._super.apply(this, arguments); - }, - }); - - assert.strictEqual($('td.o_data_cell:first').text(), 'new record', - "should have created the new record in the o2m with the correct name"); - - form.destroy(); - }); - - QUnit.test('item not dropped on discard with empty required field (default_get)', function (assert) { - // This test simulates discarding a record that has been created with - // one of its required field that is empty. When we discard the changes - // on this empty field, it should not assume that this record should be - // abandonned, since it has been added (even though it is a new record). - assert.expect(8); - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
', - mockRPC: function (route, args) { - if (args.method === 'default_get') { - return $.when({ p: [[0, 0, { display_name: 'new record', trululu: false }]] }); - } - return this._super.apply(this, arguments); - }, - }); - - assert.strictEqual($('tr.o_data_row').length, 1, - "should have created the new record in the o2m"); - assert.strictEqual($('td.o_data_cell').first().text(), "new record", - "should have the correct displayed name"); - - var requiredElement = $('td.o_data_cell.o_required_modifier'); - assert.strictEqual(requiredElement.length, 1, - "should have a required field on this record"); - assert.strictEqual(requiredElement.text(), "", - "should have empty string in the required field on this record"); - - requiredElement.click(); - // discard by clicking on body - $('body').click(); - - assert.strictEqual($('tr.o_data_row').length, 1, - "should still have the record in the o2m"); - assert.strictEqual($('td.o_data_cell').first().text(), "new record", - "should still have the correct displayed name"); - - // update selector of required field element - requiredElement = $('td.o_data_cell.o_required_modifier'); - assert.strictEqual(requiredElement.length, 1, - "should still have the required field on this record"); - assert.strictEqual(requiredElement.text(), "", - "should still have empty string in the required field on this record"); - form.destroy(); - }); - - QUnit.test('list in form: name_get with unique ids (default_get)', function (assert) { - assert.expect(2); - - this.data.partner.records[0].display_name = "MyTrululu"; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
', - mockRPC: function (route, args) { - if (args.method === 'default_get') { - return $.when({ - p: [ - [0, 0, { trululu: 1 }], - [0, 0, { trululu: 1 }] - ] - }); - } - if (args.method === 'name_get') { - assert.deepEqual(args.args[0], _.uniq(args.args[0]), - "should not have duplicates in name_get rpc"); - } - return this._super.apply(this, arguments); - }, - }); - - assert.strictEqual(form.$('td.o_data_cell').text(), "MyTrululuMyTrululu", - "both records should have the correct display_name for trululu field"); - - form.destroy(); - }); - - QUnit.test('list in form: show name of many2one fields in multi-page (default_get)', function (assert) { - assert.expect(4); - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
', - mockRPC: function (route, args) { - if (args.method === 'default_get') { - return $.when({ - p: [ - [0, 0, { display_name: 'record1', trululu: 1 }], - [0, 0, { display_name: 'record2', trululu: 2 }] - ] - }); - } - return this._super.apply(this, arguments); - }, - }); - - assert.strictEqual(form.$('td.o_data_cell').first().text(), - "record1", "should show display_name of 1st record"); - assert.strictEqual(form.$('td.o_data_cell').first().next().text(), - "first record", "should show display_name of trululu of 1st record"); - - form.$('button.o_pager_next').click(); - - assert.strictEqual(form.$('td.o_data_cell').first().text(), - "record2", "should show display_name of 2nd record"); - assert.strictEqual(form.$('td.o_data_cell').first().next().text(), - "second record", "should show display_name of trululu of 2nd record"); - - form.destroy(); - }); - - QUnit.test('list in form: item not dropped on discard with empty required field (onchange in default_get)', function (assert) { - // variant of the test "list in form: discard newly added element with - // empty required field (default_get)", in which the `default_get` - // performs an `onchange` at the same time. This `onchange` may create - // some records, which should not be abandoned on discard, similarly - // to records created directly by `default_get` - assert.expect(7); - - var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; - - this.data.partner.onchanges = { - product_id: function (obj) { - if (obj.product_id === 37) { - obj.p = [[0, 0, { display_name: "entry", trululu: false }]]; - } - }, - }; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '', - mockRPC: function (route, args) { - if (args.method === 'default_get') { - return $.when({ - product_id: 37, - }); - } - return this._super.apply(this, arguments); - }, - }); - - // check that there is a record in the editable list with empty string as required field - assert.strictEqual(form.$('.o_data_row').length, 1, - "should have a row in the editable list"); - assert.strictEqual($('td.o_data_cell').first().text(), "entry", - "should have the correct displayed name"); - var requiredField = $('td.o_data_cell.o_required_modifier'); - assert.strictEqual(requiredField.length, 1, - "should have a required field on this record"); - assert.strictEqual(requiredField.text(), "", - "should have empty string in the required field on this record"); - - // click on empty required field in editable list record - requiredField.click(); - // click off so that the required field still stay empty - $('body').click(); - - // record should not be dropped - assert.strictEqual(form.$('.o_data_row').length, 1, - "should not have dropped record in the editable list"); - assert.strictEqual($('td.o_data_cell').first().text(), "entry", - "should still have the correct displayed name"); - assert.strictEqual($('td.o_data_cell.o_required_modifier').text(), "", - "should still have empty string in the required field"); - - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - form.destroy(); - }); - - QUnit.test('list in form: item not dropped on discard with empty required field (onchange on list after default_get)', function (assert) { - // discarding a record from an `onchange` in a `default_get` should not - // abandon the record. This should not be the case for following - // `onchange`, except if an onchange make some changes on the list: - // in particular, if an onchange make changes on the list such that - // a record is added, this record should not be dropped on discard - assert.expect(8); - - var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; - - this.data.partner.onchanges = { - product_id: function (obj) { - if (obj.product_id === 37) { - obj.p = [[0, 0, { display_name: "entry", trululu: false }]]; - } - }, - }; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '', - }); - - // check no record in list - assert.strictEqual(form.$('.o_data_row').length, 0, - "should have no row in the editable list"); - - // select product_id to force on_change in editable list - form.$('.o_field_widget[name="product_id"] .o_input').click(); - $('.ui-menu-item').first().click(); - - // check that there is a record in the editable list with empty string as required field - assert.strictEqual(form.$('.o_data_row').length, 1, - "should have a row in the editable list"); - assert.strictEqual($('td.o_data_cell').first().text(), "entry", - "should have the correct displayed name"); - var requiredField = $('td.o_data_cell.o_required_modifier'); - assert.strictEqual(requiredField.length, 1, - "should have a required field on this record"); - assert.strictEqual(requiredField.text(), "", - "should have empty string in the required field on this record"); - - // click on empty required field in editable list record - requiredField.click(); - // click off so that the required field still stay empty - $('body').click(); - - // record should not be dropped - assert.strictEqual(form.$('.o_data_row').length, 1, - "should not have dropped record in the editable list"); - assert.strictEqual($('td.o_data_cell').first().text(), "entry", - "should still have the correct displayed name"); - assert.strictEqual($('td.o_data_cell.o_required_modifier').text(), "", - "should still have empty string in the required field"); - - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - form.destroy(); - }); - - QUnit.test('item dropped on discard with empty required field with "Add an item" (invalid on "ADD")', function (assert) { - // when a record in a list is added with "Add an item", it should - // always be dropped on discard if some required field are empty - // at the record creation. - assert.expect(6); - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '
', - }); - - // Click on "Add an item" - form.$('.o_field_x2many_list_row_add a').click(); - var charField = form.$('.o_field_widget.o_field_char[name="display_name"]'); - var requiredField = form.$('.o_field_widget.o_required_modifier[name="trululu"]'); - charField.val("some text"); - assert.strictEqual(charField.length, 1, - "should have a char field 'display_name' on this record"); - assert.notOk(charField.hasClass('o_required_modifier'), - "the char field should not be required on this record"); - assert.strictEqual(charField.val(), "some text", - "should have entered text in the char field on this record"); - assert.strictEqual(requiredField.length, 1, - "should have a required field 'trululu' on this record"); - assert.strictEqual(requiredField.val().trim(), "", - "should have empty string in the required field on this record"); - - // click on empty required field in editable list record - requiredField.click(); - // click off so that the required field still stay empty - $('body').click(); - - // record should be dropped - assert.strictEqual(form.$('.o_data_row').length, 0, - "should have dropped record in the editable list"); - - form.destroy(); - }); - - QUnit.test('item not dropped on discard with empty required field with "Add an item" (invalid on "UPDATE")', function (assert) { - // when a record in a list is added with "Add an item", it should - // be temporarily added to the list when it is valid (e.g. required - // fields are non-empty). If the record is updated so that the required - // field is empty, and it is discarded, then the record should not be - // dropped. - assert.expect(8); - - var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '
', - }); - - assert.strictEqual(form.$('.o_data_row').length, 0, - "should initially not have any record in the list"); - - // Click on "Add an item" - form.$('.o_field_x2many_list_row_add a').click(); - assert.strictEqual(form.$('.o_data_row').length, 1, - "should have a temporary record in the list"); - - var $inputEditMode = form.$('.o_field_widget.o_required_modifier[name="trululu"] input'); - assert.strictEqual($inputEditMode.length, 1, - "should have a required field 'trululu' on this record"); - assert.strictEqual($inputEditMode.val(), "", - "should have empty string in the required field on this record"); - - // add something to required field and leave edit mode of the record - $inputEditMode.click(); - $('li.ui-menu-item').first().click(); - $('body').click(); // leave edit mode on the line - - var $inputReadonlyMode = form.$('.o_data_cell.o_required_modifier'); - assert.strictEqual(form.$('.o_data_row').length, 1, - "should not have dropped valid record when leaving edit mode"); - assert.strictEqual($inputReadonlyMode.text(), "first record", - "should have put some content in the required field on this record"); - - // remove the required field and leave edit mode of the record - $('.o_data_row').click(); // enter edit mode on the line - $inputEditMode.click(); - $inputEditMode.val(""); - $('body').click(); - - assert.strictEqual(form.$('.o_data_row').length, 1, - "should not have dropped record in the list on discard (invalid on UPDATE)"); - assert.strictEqual($inputReadonlyMode.text(), "first record", - "should keep previous valid required field content on this record"); - - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - form.destroy(); - }); - - QUnit.test('list in form: default_get with x2many create', function (assert) { - assert.expect(5); - - var displayName = 'brandon is the new timmy'; - this.data.partner.onchanges.timmy = function (obj) { - assert.deepEqual( - obj.timmy, - [ - [6, false, []], - [0, obj.timmy[1][1], { display_name: displayName, name: 'brandon' }] - ], - "should have properly created the x2many command list"); - obj.int_field = obj.timmy.length; - }; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
', - mockRPC: function (route, args) { - if (args.method === 'default_get') { - return $.when({ timmy: [[0, 0, { display_name: 'brandon is the new timmy', name: 'brandon' }]] }); - } - if (args.method === 'create') { - assert.deepEqual(args.args[0], { - int_field: 2, - timmy: [ - [6, false, []], - [0, args.args[0].timmy[1][1], { display_name: displayName, name: 'brandon' }], - ], - }, "should send the correct values to create"); - } - return this._super.apply(this, arguments); - }, - }); - - assert.strictEqual($('td.o_data_cell:first').text(), 'brandon is the new timmy', - "should have created the new record in the m2m with the correct name"); - assert.strictEqual($('input.o_field_integer').val(), '2', - "should have called and executed the onchange properly"); - - // edit the subrecord and save - displayName = 'new value'; - form.$('.o_data_cell').click(); - form.$('.o_data_cell input').val(displayName).trigger('input'); - form.$buttons.find('.o_form_button_save').click(); - - form.destroy(); - }); - - QUnit.test('list in form: default_get with x2many create and onchange', function (assert) { - assert.expect(2); - - this.data.partner.onchanges.turtles = function (obj) { - assert.deepEqual( - obj.turtles, - [ - [4, 2, false], - [1, 2, { turtle_foo: 'blip' }], - [4, 3, false], - [1, 3, { turtle_foo: 'kawa' }] - ], - "should have properly created the x2many command list"); - }; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
', - mockRPC: function (route, args) { - if (args.method === 'default_get') { - return $.when({ turtles: [[6, 0, [2, 3]]] }); - } - if (args.method === 'create') { - // it would be even better if we did not send the current - // unchanged state with the command 1, but this seems more - // difficult. - assert.deepEqual(args.args[0].turtles, [ - [4, 2, false], - [1, 2, { turtle_foo: 'blip' }], - [4, 3, false], - [1, 3, { turtle_foo: 'kawa' }] - ], 'should send proper commands to create method'); - } - return this._super.apply(this, arguments); - }, - }); - - form.$buttons.find('.o_form_button_save').click(); - - form.destroy(); - }); - - QUnit.test('list in form: call button in sub view', function (assert) { - assert.expect(6); - - this.data.partner.records[0].p = [2]; - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
', - res_id: 1, - mockRPC: function (route, args) { - if (route === '/web/dataset/call_kw/product/get_formview_id') { - return $.when(false); - } - return this._super.apply(this, arguments); - }, - intercepts: { - execute_action: function (event) { - assert.strictEqual(event.data.env.model, 'product', - 'should call with correct model in env'); - assert.strictEqual(event.data.env.currentID, 37, - 'should call with correct currentID in env'); - assert.deepEqual(event.data.env.resIDs, [37], - 'should call with correct resIDs in env'); - }, - }, - archs: { - 'product,false,form': '
' + - '
' + - '
' + - '
', - }, - }); - - form.$buttons.find('.o_form_button_edit').click(); - form.$('td.o_data_cell:first').click(); // edit first one2many line - form.$('.o_external_button').click(); // open product sub view in modal - $('button:contains("Just do it !")').click(); // click on action button - $('button:contains("Just don\'t do it !")').click(); // click on object button - - form.destroy(); - }); - - QUnit.test('X2Many sequence list in modal', function (assert) { - assert.expect(5); - - this.data.partner.fields.sequence = { string: 'Sequence', type: 'integer' }; - this.data.partner.records[0].sequence = 1; - this.data.partner.records[1].sequence = 2; - this.data.partner.onchanges = { - sequence: function (obj) { - if (obj.id === 2) { - obj.sequence = 1; - assert.step('onchange sequence'); - } - }, - }; - - this.data.product.fields.turtle_ids = { string: 'Turtles', type: 'one2many', relation: 'turtle' }; - this.data.product.records[0].turtle_ids = [1]; - - this.data.turtle.fields.partner_types_ids = { string: "Partner", type: "one2many", relation: 'partner' }; - this.data.turtle.fields.type_id = { string: "Partner Type", type: "many2one", relation: 'partner_type' }; - - this.data.partner_type.fields.partner_ids = { string: "Partner", type: "one2many", relation: 'partner' }; - this.data.partner_type.records[0].partner_ids = [1, 2]; - - testUtils.createAsyncView({ - View: FormView, - model: 'product', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '', - archs: { - 'partner_type,false,form': '
', - 'partner,false,list': '' + - '' + - '' + - '', - }, - res_id: 37, - mockRPC: function (route, args) { - if (route === '/web/dataset/call_kw/product/read') { - return $.when([{ id: 37, name: 'xphone', display_name: 'leonardo', turtle_ids: [1] }]); - } - if (route === '/web/dataset/call_kw/turtle/read') { - return $.when([{ id: 1, type_id: [12, 'gold'] }]); - } - if (route === '/web/dataset/call_kw/partner_type/get_formview_id') { - return $.when(false); - } - if (route === '/web/dataset/call_kw/partner_type/read') { - return $.when([{ id: 12, partner_ids: [1, 2], display_name: 'gold' }]); - } - if (route === '/web/dataset/call_kw/partner_type/write') { - assert.step('partner_type write'); - } - return this._super.apply(this, arguments); - }, - }).then(function (form) { - form.$buttons.find('.o_form_button_edit').click(); - form.$('.o_data_cell').click(); - form.$('.o_external_button').click(); - - var $modal = $('.modal'); - assert.equal($modal.length, 1, - 'There should be 1 modal opened'); - - var $handles = $modal.find('.ui-sortable-handle'); - assert.equal($handles.length, 2, - 'There should be 2 sequence handlers'); - - testUtils.dragAndDrop($handles.eq(1), $modal.find('tbody tr').first(), - { position: 'top' }); - - // Saving the modal and then the original model - $modal.find('.modal-footer .btn-primary').click(); - form.$buttons.find('.o_form_button_save').click(); - - assert.verifySteps(['onchange sequence', 'partner_type write']); - - form.destroy(); - }); - }); - - QUnit.test('autocompletion in a many2one, in form view with a domain', function (assert) { - assert.expect(1); - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '', - res_id: 1, - viewOptions: { - domain: [['trululu', '=', 4]] - }, - mockRPC: function (route, args) { - if (args.method === 'name_search') { - assert.deepEqual(args.kwargs.args, [], "should not have a domain"); - } - return this._super(route, args); - } - }); - form.$buttons.find('.o_form_button_edit').click(); - - form.$('input').click(); - form.destroy(); - }); - - QUnit.test('autocompletion in a many2one, in form view with a date field', function (assert) { - assert.expect(1); - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '', - res_id: 2, - mockRPC: function (route, args) { - if (args.method === 'name_search') { - assert.deepEqual(args.kwargs.args, [["bar", "=", true]], "should not have a domain"); - } - return this._super(route, args); - }, - }); - form.$buttons.find('.o_form_button_edit').click(); - - form.$('input:eq(2)').click(); - form.destroy(); - }); - - QUnit.test('creating record with many2one with option always_reload', function (assert) { - assert.expect(2); - - this.data.partner.fields.trululu.default = 1; - this.data.partner.onchanges = { - trululu: function (obj) { - obj.trululu = 2; //[2, "second record"]; - }, - }; - - var count = 0; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '', - mockRPC: function (route, args) { - count++; - if (args.method === 'name_get' && args.args[0][0] === 2) { - return $.when([[2, "hello world\nso much noise"]]); - } - return this._super(route, args); - }, - }); - - assert.strictEqual(count, 3, "should have done 3 rpcs (default_get, onchange, name_get)"); - assert.strictEqual(form.$('input').val(), 'hello world', - "should have taken the correct display name"); - form.destroy(); - }); - - QUnit.test('selecting a many2one, then discarding', function (assert) { - assert.expect(3); - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '', - res_id: 1, - }); - assert.strictEqual(form.$('a').text(), '', 'the tag a should be empty'); - form.$buttons.find('.o_form_button_edit').click(); - - form.$('.o_field_many2one input').click(); - form.$('.o_field_many2one input').autocomplete('widget').find('a').first().click(); - - - assert.strictEqual(form.$('input').val(), "xphone", "should have selected xphone"); - - form.$buttons.find('.o_form_button_cancel').click(); - assert.strictEqual(form.$('a').text(), '', 'the tag a should be empty'); - form.destroy(); - }); - - QUnit.test('domain and context are correctly used when doing a name_search in a m2o', function (assert) { - assert.expect(4); - - this.data.partner.records[0].timmy = [12]; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: - '
' + - '' + - '' + - '' + - '' + - '', - res_id: 1, - session: { user_context: { hey: "ho" } }, - mockRPC: function (route, args) { - if (args.method === 'name_search' && args.model === 'product') { - assert.deepEqual( - args.kwargs.args, - [['foo', '=', 'bar'], ['foo', '=', 'yop']], - 'the field attr domain should have been used for the RPC (and evaluated)'); - assert.deepEqual( - args.kwargs.context, - { hey: "ho", hello: "world", test: "yop" }, - 'the field attr context should have been used for the ' + - 'RPC (evaluated and merged with the session one)'); - return $.when([]); - } - if (args.method === 'name_search' && args.model === 'partner') { - assert.deepEqual(args.kwargs.args, [['id', 'in', [12]]], - 'the field attr domain should have been used for the RPC (and evaluated)'); - assert.deepEqual(args.kwargs.context, { hey: 'ho', timmy: [[6, false, [12]]] }, - 'the field attr context should have been used for the RPC (and evaluated)'); - return $.when([]); - } - return this._super.apply(this, arguments); - }, - }); - - form.$buttons.find('.o_form_button_edit').click(); - form.$('.o_field_widget[name=product_id] input').click(); - - form.$('.o_field_widget[name=trululu] input').click(); - - form.destroy(); - }); - - QUnit.test('quick create on a many2one', function (assert) { - assert.expect(1); - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '
', - mockRPC: function (route, args) { - if (route === '/web/dataset/call_kw/product/name_create') { - assert.strictEqual(args.args[0], 'new partner', - "should name create a new product"); - } - return this._super.apply(this, arguments); - }, - }); - - form.$('.o_field_many2one input').focus(); - form.$('.o_field_many2one input').val('new partner').trigger('keyup').trigger('focusout'); - - $('.modal .modal-footer .btn-primary').first().click(); - - form.destroy(); - }); - - QUnit.test('slow create on a many2one', function (assert) { - assert.expect(7); - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: - '
' + - '' + - '' + - '' + - '
', - archs: { - 'product,false,form': - '
' + - '' + - '', - }, - }); - - // cancel the many2one creation with Cancel button - form.$('.o_field_many2one input').focus().val('new product').trigger('keyup').trigger('blur'); - assert.strictEqual($('.modal').length, 1, "there should be one opened modal"); - $('.modal .modal-footer .btn:contains(Cancel)').click(); - assert.strictEqual(form.$('.o_field_many2one input').val(), "", - 'the many2one should not set a value as its creation has been cancelled (with Cancel button)'); - - // cancel the many2one creation with Close button - form.$('.o_field_many2one input').focus().val('new product').trigger('keyup').trigger('blur'); - $('.modal .modal-header button').click(); - assert.strictEqual(form.$('.o_field_many2one input').val(), "", - 'the many2one should not set a value as its creation has been cancelled (with Close button)'); - - // select a new value then cancel the creation of the new one --> restore the previous - form.$('.o_field_many2one input').click(); - form.$('.o_field_many2one input').autocomplete('widget').find('a').first().click(); - assert.strictEqual(form.$('input').val(), "xphone", "should have selected xphone"); - form.$('.o_field_many2one input').focus().val('new product').trigger('keyup').trigger('blur'); - assert.strictEqual($('.modal').length, 1, "there should be one opened modal"); - $('.modal .modal-footer .btn:contains(Cancel)').click(); - assert.strictEqual(form.$('.o_field_many2one input').val(), "xphone", - 'should have restored the many2one with its previous selected value (xphone)'); - - // confirm the many2one creation - form.$('.o_field_many2one input').focus(); - form.$('.o_field_many2one input').val('new partner').trigger('keyup').trigger('blur'); - $('.modal .modal-footer .btn-primary').click(); - assert.strictEqual($('.modal .o_form_view').length, 1, - 'a new modal should be opened and contain a form view'); - - form.destroy(); - }); - - QUnit.test('no_create option on a many2one', function (assert) { - assert.expect(1); - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '
', - }); - - form.$('.o_field_many2one input').focus(); - form.$('.o_field_many2one input').val('new partner').trigger('keyup').trigger('focusout'); - - assert.strictEqual($('.modal').length, 0, "should not display the create modal"); - form.destroy(); - }); - - QUnit.test('can_create and can_write option on a many2one', function (assert) { - assert.expect(5); - - this.data.product.options = { - can_create: "false", - can_write: "false", - }; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '
', - archs: { - 'product,false,form': '
', - }, - mockRPC: function (route) { - if (route === '/web/dataset/call_kw/product/get_formview_id') { - return $.when(false); - } - return this._super.apply(this, arguments); - }, - }); - - form.$('.o_field_many2one input').click(); - assert.strictEqual($('.ui-autocomplete .o_m2o_dropdown_option:contains(Create)').length, 0, - "there shouldn't be any option to search and create"); - - $('.ui-autocomplete li:contains(xpad)').mouseenter().click(); - assert.strictEqual(form.$('.o_field_many2one input').val(), "xpad", - "the correct record should be selected"); - assert.strictEqual(form.$('.o_field_many2one .o_external_button').length, 1, - "there should be an external button displayed"); - - form.$('.o_field_many2one .o_external_button').click(); - assert.strictEqual($('.modal .o_form_view.o_form_readonly').length, 1, - "there should be a readonly form view opened"); - - $('.modal .o_form_button_cancel').click(); - - form.$('.o_field_many2one input').val('new product').trigger('keyup').trigger('focusout'); - - assert.strictEqual($('.modal').length, 0, "should not display the create modal"); - form.destroy(); - }); - - QUnit.test('pressing enter in a m2o in an editable list', function (assert) { - assert.expect(9); - var done = assert.async(); - var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; - - var list = createView({ - View: ListView, - model: 'partner', - data: this.data, - arch: '', - }); - - list.$('td.o_data_cell:first').click(); - assert.strictEqual(list.$('.o_selected_row').length, 1, - "should have a row in edit mode"); - - // we now write 'a' and press enter to check that the selection is - // working, and prevent the navigation - list.$('td.o_data_cell input:first').val('a').trigger('input'); - concurrency.delay(0).then(function () { - var $input = list.$('td.o_data_cell input:first'); - var $dropdown = $input.autocomplete('widget'); - assert.ok($dropdown.is(':visible'), "autocomplete dropdown should be visible"); - - // we now trigger ENTER to select first choice - $input.trigger($.Event('keydown', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - assert.strictEqual($input[0], document.activeElement, - "input should still be focused"); - - // we now trigger again ENTER to make sure we can move to next line - $input.trigger($.Event('keydown', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - - assert.notOk(document.contains($input[0]), - "input should no longer be in dom"); - assert.ok(list.$('tr.o_data_row:eq(1)').hasClass('o_selected_row'), - "second row should now be selected"); - - // we now write again 'a' in the cell to select xpad. We will now - // test with the tab key - list.$('td.o_data_cell input:first').val('a').trigger('input'); - return concurrency.delay(0); - }).then(function () { - var $input = list.$('td.o_data_cell input:first'); - var $dropdown = $input.autocomplete('widget'); - assert.ok($dropdown.is(':visible'), "autocomplete dropdown should be visible"); - $input.trigger($.Event('keydown', { - which: $.ui.keyCode.TAB, - keyCode: $.ui.keyCode.TAB, - })); - assert.strictEqual($input[0], document.activeElement, - "input should still be focused"); - - // we now trigger again ENTER to make sure we can move to next line - $input.trigger($.Event('keydown', { - which: $.ui.keyCode.TAB, - keyCode: $.ui.keyCode.TAB, - })); - - assert.notOk(document.contains($input[0]), - "input should no longer be in dom"); - assert.ok(list.$('tr.o_data_row:eq(2)').hasClass('o_selected_row'), - "third row should now be selected"); - list.destroy(); - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - done(); - }); - }); - - QUnit.test('pressing ENTER on a \'no_quick_create\' many2one should open a M2ODialog', function (assert) { - var done = assert.async(); - assert.expect(2); - - var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '', - archs: { - 'partner,false,form': '
', - }, - }); - - var $input = form.$('.o_field_many2one input'); - $input.val("Something that does not exist").trigger('input'); - $('.ui-autocomplete .ui-menu-item a:contains(Create and)').trigger('mouseenter'); - concurrency.delay(0).then(function () { - $input.trigger($.Event('keydown', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - $input.trigger($.Event('keypress', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - $input.trigger($.Event('keyup', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - concurrency.delay(0).then(function () { - $input.blur(); - assert.strictEqual($('.modal').length, 1, - "should have one modal in body"); - // Check that discarding clears $input - $('.modal .o_form_button_cancel').click(); - assert.strictEqual($input.val(), '', - "the field should be empty"); - form.destroy(); - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - done(); - }); - }); - }); - - QUnit.test('select a value by pressing TAB on a many2one with onchange', function (assert) { - var done = assert.async(); - assert.expect(3); - - this.data.partner.onchanges.trululu = function () { }; - - var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; - var def = $.Deferred(); - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '', - mockRPC: function (route, args) { - var result = this._super.apply(this, arguments); - if (args.method === 'onchange') { - return $.when(def).then(_.constant(result)); - } - return result; - }, - res_id: 1, - viewOptions: { - mode: 'edit', - }, - }); - - var $input = form.$('.o_field_many2one input'); - $input.val("first").trigger('input'); - concurrency.delay(0).then(function () { - var evOptions = { - which: $.ui.keyCode.TAB, - keyCode: $.ui.keyCode.TAB, - }; - $input.trigger($.Event('keydown', evOptions)); - $input.trigger($.Event('keypress', evOptions)); - $input.trigger($.Event('keyup', evOptions)); - - // simulate a focusout (e.g. because the user clicks outside) - // before the onchange returns - form.$('.o_field_char').focus(); - - assert.strictEqual($('.modal').length, 0, - "there shouldn't be any modal in body"); - - // unlock the onchange - def.resolve(); - - assert.strictEqual($input.val(), 'first record', - "first record should have been selected"); - assert.strictEqual($('.modal').length, 0, - "there shouldn't be any modal in body"); - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - form.destroy(); - done(); - }); - }); - - QUnit.test('many2one in editable list + onchange, with enter [REQUIRE FOCUS]', function (assert) { - assert.expect(6); - var done = assert.async(); - var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; - - this.data.partner.onchanges.product_id = function (obj) { - obj.int_field = obj.product_id || 0; - }; - - var def = $.Deferred(); - - var list = createView({ - View: ListView, - model: 'partner', - data: this.data, - arch: '', - mockRPC: function (route, args) { - if (args.method) { - assert.step(args.method); - } - var result = this._super.apply(this, arguments); - if (args.method === 'onchange') { - return def.then(_.constant(result)); - } - return result; - }, - }); - - list.$('td.o_data_cell:first').click(); - list.$('td.o_data_cell input:first').val('a').trigger('input'); - concurrency.delay(0).then(function () { - var $input = list.$('td.o_data_cell input:first'); - $input.trigger($.Event('keydown', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - $input.trigger($.Event('keyup', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - def.resolve(); - $input.trigger($.Event('keydown', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - assert.strictEqual($('.modal').length, 0, "should not have any modal in DOM"); - assert.verifySteps(['name_search', 'onchange', 'write', 'read']); - list.destroy(); - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - done(); - }); - }); - - QUnit.test('many2one in editable list + onchange, with enter, part 2 [REQUIRE FOCUS]', function (assert) { - // this is the same test as the previous one, but the onchange is just - // resolved slightly later - assert.expect(6); - var done = assert.async(); - var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; - - this.data.partner.onchanges.product_id = function (obj) { - obj.int_field = obj.product_id || 0; - }; - - var def = $.Deferred(); - - var list = createView({ - View: ListView, - model: 'partner', - data: this.data, - arch: '', - mockRPC: function (route, args) { - if (args.method) { - assert.step(args.method); - } - var result = this._super.apply(this, arguments); - if (args.method === 'onchange') { - return def.then(_.constant(result)); - } - return result; - }, - }); - - list.$('td.o_data_cell:first').click(); - list.$('td.o_data_cell input:first').val('a').trigger('input'); - concurrency.delay(0).then(function () { - var $input = list.$('td.o_data_cell input:first'); - $input.trigger($.Event('keydown', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - $input.trigger($.Event('keyup', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - $input.trigger($.Event('keydown', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - def.resolve(); - assert.strictEqual($('.modal').length, 0, "should not have any modal in DOM"); - assert.verifySteps(['name_search', 'onchange', 'write', 'read']); - list.destroy(); - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - done(); - }); - }); - - QUnit.test('many2one: domain updated by an onchange', function (assert) { - assert.expect(2); - - this.data.partner.onchanges = { - int_field: function () { }, - }; - - var domain = []; - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '', - res_id: 1, - mockRPC: function (route, args) { - if (args.method === 'onchange') { - domain = [['id', 'in', [10]]]; - return $.when({ - domain: { - trululu: domain, - unexisting_field: domain, - } - }); - } - if (args.method === 'name_search') { - assert.deepEqual(args.kwargs.args, domain, - "sent domain should be correct"); - } - return this._super(route, args); - }, - viewOptions: { - mode: 'edit', - }, - }); - - // trigger a name_search (domain should be []) - form.$('.o_field_widget[name=trululu] input').click(); - // close the dropdown - form.$('.o_field_widget[name=trululu] input').click(); - // trigger an onchange that will update the domain - form.$('.o_field_widget[name=int_field]').val(2).trigger('input'); - // trigger a name_search (domain should be [['id', 'in', [10]]]) - form.$('.o_field_widget[name=trululu] input').click(); - - form.destroy(); - }); - - QUnit.test('many2one in one2many: domain updated by an onchange', function (assert) { - assert.expect(3); - - this.data.partner.onchanges = { - trululu: function () { }, - }; - - var domain = []; - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '
', - res_id: 1, - mockRPC: function (route, args) { - if (args.method === 'onchange') { - return $.when({ - domain: { - trululu: domain, - }, - }); - } - if (args.method === 'name_search') { - assert.deepEqual(args.kwargs.args, domain, - "sent domain should be correct"); - } - return this._super(route, args); - }, - viewOptions: { - mode: 'edit', - }, - }); - - // add a first row with a specific domain for the m2o - domain = [['id', 'in', [10]]]; // domain for subrecord 1 - form.$('.o_field_x2many_list_row_add a').click(); // triggers the onchange - form.$('.o_field_widget[name=trululu] input').click(); // triggers the name_search - - // add a second row with another domain for the m2o - domain = [['id', 'in', [5]]]; // domain for subrecord 2 - form.$('.o_field_x2many_list_row_add a').click(); // triggers the onchange - form.$('.o_field_widget[name=trululu] input').click(); // triggers the name_search - - // check again the first row to ensure that the domain hasn't change - domain = [['id', 'in', [10]]]; // domain for subrecord 1 should have been kept - form.$('.o_data_row:first .o_data_cell').click(); - form.$('.o_field_widget[name=trululu] input').click(); // triggers the name_search - - form.destroy(); - }); - - QUnit.test('updating a many2one from a many2many', function (assert) { - assert.expect(4); - - this.data.turtle.records[1].turtle_trululu = 1; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
', - res_id: 1, - archs: { - 'partner,false,form': '
', - }, - mockRPC: function (route, args) { - if (args.method === 'get_formview_id') { - assert.deepEqual(args.args[0], [1], "should call get_formview_id with correct id"); - return $.when(false); - } - return this._super(route, args); - }, - }); - - // Opening the modal - form.$buttons.find('.o_form_button_edit').click(); - form.$('.o_data_row td:contains(first record)').click(); - form.$('.o_external_button').click(); - assert.strictEqual($('.modal').length, 1, - "should have one modal in body"); - - // Changing the 'trululu' value - $('.modal input[name="display_name"]').val('test').trigger('input'); - $('.modal button.btn-primary').click(); - - // Test whether the value has changed - assert.strictEqual($('.modal').length, 0, - "the modal should be closed"); - assert.equal(form.$('.o_data_cell:contains(test)').text(), 'test', - "the partner name should have been updated to 'test'"); - - form.destroy(); - }); - - QUnit.test('x2many list sorted by many2one', function (assert) { - assert.expect(3); - - this.data.partner.records[0].p = [1, 2, 4]; - this.data.partner.fields.trululu.sortable = true; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '
', - res_id: 1, - }); - - assert.strictEqual(form.$('.o_data_row .o_list_number').text(), '124', - "should have correct order initially"); - - form.$('.o_list_view thead th:nth(1)').click(); - - assert.strictEqual(form.$('.o_data_row .o_list_number').text(), '412', - "should have correct order (ASC)"); - - form.$('.o_list_view thead th:nth(1)').click(); - - assert.strictEqual(form.$('.o_data_row .o_list_number').text(), '214', - "should have correct order (DESC)"); - - form.destroy(); - }); - - QUnit.test('one2many with extra field from server not in form', function (assert) { - assert.expect(6); - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '
', - res_id: 1, - archs: { - 'partner,false,form': '
' + - '' + - ''}, - mockRPC: function(route, args) { - if (route === '/web/dataset/call_kw/partner/write') { - args.args[1].p[0][2].datetime = '2018-04-05 12:00:00'; - } - return this._super.apply(this, arguments); - } - }); - - form.$buttons.find('.o_form_button_edit').click(); - - var x2mList = form.$('.o_field_x2many_list[name=p]'); - - // Add a record in the list - x2mList.find('.o_field_x2many_list_row_add a').click(); - - var modal = $('.modal-lg'); - - var nameInput = modal.find('input.o_input[name=display_name]'); - nameInput.val('michelangelo').trigger('input'); - - // Save the record in the modal (though it is still virtual) - modal.find('.btn-primary').first().click(); - - assert.equal(x2mList.find('.o_data_row').length, 1, - 'There should be 1 records in the x2m list'); - - var newlyAdded = x2mList.find('.o_data_row').eq(0); - - assert.equal(newlyAdded.find('.o_data_cell').first().text(), '', - 'The create_date field should be empty'); - assert.equal(newlyAdded.find('.o_data_cell').eq(1).text(), 'michelangelo', - 'The display name field should have the right value'); - - // Save the whole thing - form.$buttons.find('.o_form_button_save').click(); - - x2mList = form.$('.o_field_x2many_list[name=p]'); - - // Redo asserts in RO mode after saving - assert.equal(x2mList.find('.o_data_row').length, 1, - 'There should be 1 records in the x2m list'); - - newlyAdded = x2mList.find('.o_data_row').eq(0); - - assert.equal(newlyAdded.find('.o_data_cell').first().text(), '04/05/2018 12:00:00', - 'The create_date field should have the right value'); - assert.equal(newlyAdded.find('.o_data_cell').eq(1).text(), 'michelangelo', - 'The display name field should have the right value'); - - form.destroy(); - }); - - QUnit.test('one2many with extra field from server not in (inline) form', function (assert) { - assert.expect(1); - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '', - res_id: 1, - viewOptions: { - mode: 'edit', - }, - }); - - var x2mList = form.$('.o_field_x2many_list[name=p]'); - - // Add a record in the list - x2mList.find('.o_field_x2many_list_row_add a').click(); - - var modal = $('.modal-lg'); - - var nameInput = modal.find('input.o_input[name=display_name]'); - nameInput.val('michelangelo').trigger('input'); - - // Save the record in the modal (though it is still virtual) - modal.find('.btn-primary').first().click(); - - assert.equal(x2mList.find('.o_data_row').length, 1, - 'There should be 1 records in the x2m list'); - - form.destroy(); - }); - - QUnit.test('one2many with extra X2many field from server not in inline form', function (assert) { - assert.expect(1); - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '', - res_id: 1, - viewOptions: { - mode: 'edit', - }, - }); - - var x2mList = form.$('.o_field_x2many_list[name=p]'); - - // Add a first record in the list - x2mList.find('.o_field_x2many_list_row_add a').click(); - - // Save & New - $('.modal-lg').find('.btn-primary').eq(1).click(); - - // Save & Close - $('.modal-lg').find('.btn-primary').eq(0).click(); - - assert.equal(x2mList.find('.o_data_row').length, 2, - 'There should be 2 records in the x2m list'); - - form.destroy(); - }); - - QUnit.test('one2many invisible depends on parent field', function (assert) { - assert.expect(4); - - this.data.partner.records[0].p = [2]; - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch:'
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
', - res_id: 1, - }); - assert.strictEqual(form.$('th').length, 2, - "should be 2 columns in the one2many"); - form.$buttons.find('.o_form_button_edit').click(); - form.$('.o_field_many2one[name="product_id"] input').click(); - $('li.ui-menu-item a:contains(xpad)').trigger('mouseenter').click(); - assert.strictEqual(form.$('th').length, 1, - "should be 1 column when the product_id is set"); - form.$('.o_field_many2one[name="product_id"] input').val('').trigger('keyup'); - assert.strictEqual(form.$('th').length, 2, - "should be 2 columns in the one2many when product_id is not set"); - form.$('.o_field_boolean[name="bar"] input').click(); - assert.strictEqual(form.$('th').length, 1, - "should be 1 column after the value change"); - form.destroy(); - }); - - QUnit.test('one2many column visiblity depends on onchange of parent field', function (assert) { - assert.expect(3); - - this.data.partner.records[0].p = [2]; - this.data.partner.records[0].bar = false; - - this.data.partner.onchanges.p = function (obj) { - // set bar to true when line is added - if (obj.p.length > 1 && obj.p[1][2].foo === 'New line') { - obj.bar = true; - } - }; - - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch:'
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '', - res_id: 1, - }); - - // bar is false so there should be 1 column - assert.strictEqual(form.$('th').length, 1, - "should be only 1 column ('foo') in the one2many"); - assert.strictEqual(form.$('.o_list_view .o_data_row').length, 1, "should contain one row"); - - form.$buttons.find('.o_form_button_edit').click(); - - // add a new o2m record - form.$('.o_field_x2many_list_row_add a').click(); - form.$('.o_field_one2many input:first').focus(); - form.$('.o_field_one2many input:first').val('New line').trigger('input'); - form.$el.click(); // click outside to save a line - - assert.strictEqual(form.$('th').length, 2, "should be 2 columns('foo' + 'int_field')"); - - form.destroy(); - }); - - QUnit.test('one2many column_invisible on view not inline', function (assert) { - assert.expect(4); - - this.data.partner.records[0].p = [2]; - var form = createView({ - View: FormView, - model: 'partner', - data: this.data, - arch:'
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
', - res_id: 1, - archs: { - 'partner,false,list': '' + - '' + - '' + - '', - }, - }); - assert.strictEqual(form.$('th').length, 2, - "should be 2 columns in the one2many"); - form.$buttons.find('.o_form_button_edit').click(); - form.$('.o_field_many2one[name="product_id"] input').click(); - $('li.ui-menu-item a:contains(xpad)').trigger('mouseenter').click(); - assert.strictEqual(form.$('th').length, 1, - "should be 1 column when the product_id is set"); - form.$('.o_field_many2one[name="product_id"] input').val('').trigger('keyup'); - assert.strictEqual(form.$('th').length, 2, - "should be 2 columns in the one2many when product_id is not set"); - form.$('.o_field_boolean[name="bar"] input').click(); - assert.strictEqual(form.$('th').length, 1, - "should be 1 column after the value change"); - form.destroy(); - }); - }); -}); -}); diff --git a/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js b/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js new file mode 100644 index 00000000000..7181b5267b1 --- /dev/null +++ b/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js @@ -0,0 +1,8176 @@ +odoo.define('web.field_one_to_many_tests', function (require) { +"use strict"; + +var AbstractField = require('web.AbstractField'); +var concurrency = require('web.concurrency'); +var FormView = require('web.FormView'); +var ListRenderer = require('web.ListRenderer'); +var relationalFields = require('web.relational_fields'); +var testUtils = require('web.test_utils'); +var fieldUtils = require('web.field_utils'); + +var createView = testUtils.createView; + +QUnit.module('fields', {}, function () { + + QUnit.module('relational_fields', { + beforeEach: function () { + this.data = { + partner: { + fields: { + display_name: { string: "Displayed name", type: "char" }, + foo: { string: "Foo", type: "char", default: "My little Foo Value" }, + bar: { string: "Bar", type: "boolean", default: true }, + int_field: { string: "int_field", type: "integer", sortable: true }, + qux: { string: "Qux", type: "float", digits: [16, 1] }, + p: { string: "one2many field", type: "one2many", relation: 'partner', relation_field: 'trululu' }, + turtles: { string: "one2many turtle field", type: "one2many", relation: 'turtle', relation_field: 'turtle_trululu' }, + trululu: { string: "Trululu", type: "many2one", relation: 'partner' }, + timmy: { string: "pokemon", type: "many2many", relation: 'partner_type' }, + product_id: { string: "Product", type: "many2one", relation: 'product' }, + color: { + type: "selection", + selection: [['red', "Red"], ['black', "Black"]], + default: 'red', + string: "Color", + }, + date: { string: "Some Date", type: "date" }, + datetime: { string: "Datetime Field", type: 'datetime' }, + user_id: { string: "User", type: 'many2one', relation: 'user' }, + reference: { + string: "Reference Field", type: 'reference', selection: [ + ["product", "Product"], ["partner_type", "Partner Type"], ["partner", "Partner"]] + }, + }, + records: [{ + id: 1, + display_name: "first record", + bar: true, + foo: "yop", + int_field: 10, + qux: 0.44, + p: [], + turtles: [2], + timmy: [], + trululu: 4, + user_id: 17, + reference: 'product,37', + }, { + id: 2, + display_name: "second record", + bar: true, + foo: "blip", + int_field: 9, + qux: 13, + p: [], + timmy: [], + trululu: 1, + product_id: 37, + date: "2017-01-25", + datetime: "2016-12-12 10:55:05", + user_id: 17, + }, { + id: 4, + display_name: "aaa", + bar: false, + }], + onchanges: {}, + }, + product: { + fields: { + name: { string: "Product Name", type: "char" } + }, + records: [{ + id: 37, + display_name: "xphone", + }, { + id: 41, + display_name: "xpad", + }] + }, + partner_type: { + fields: { + name: { string: "Partner Type", type: "char" }, + color: { string: "Color index", type: "integer" }, + }, + records: [ + { id: 12, display_name: "gold", color: 2 }, + { id: 14, display_name: "silver", color: 5 }, + ] + }, + turtle: { + fields: { + display_name: { string: "Displayed name", type: "char" }, + turtle_foo: { string: "Foo", type: "char" }, + turtle_bar: { string: "Bar", type: "boolean", default: true }, + turtle_int: { string: "int", type: "integer", sortable: true }, + turtle_qux: { string: "Qux", type: "float", digits: [16, 1], required: true, default: 1.5 }, + turtle_description: { string: "Description", type: "text" }, + turtle_trululu: { string: "Trululu", type: "many2one", relation: 'partner' }, + turtle_ref: { + string: "Reference", type: 'reference', selection: [ + ["product", "Product"], ["partner", "Partner"]] + }, + product_id: { string: "Product", type: "many2one", relation: 'product', required: true }, + partner_ids: { string: "Partner", type: "many2many", relation: 'partner' }, + }, + records: [{ + id: 1, + display_name: "leonardo", + turtle_bar: true, + turtle_foo: "yop", + partner_ids: [], + }, { + id: 2, + display_name: "donatello", + turtle_bar: true, + turtle_foo: "blip", + turtle_int: 9, + partner_ids: [2, 4], + }, { + id: 3, + display_name: "raphael", + product_id: 37, + turtle_bar: false, + turtle_foo: "kawa", + turtle_int: 21, + turtle_qux: 9.8, + partner_ids: [], + turtle_ref: 'product,37', + }], + onchanges: {}, + }, + user: { + fields: { + name: { string: "Name", type: "char" }, + partner_ids: { string: "one2many partners field", type: "one2many", relation: 'partner', relation_field: 'user_id' }, + }, + records: [{ + id: 17, + name: "Aline", + partner_ids: [1, 2], + }, { + id: 19, + name: "Christine", + }] + }, + }; + } + }, function () { + QUnit.module('FieldOne2Many'); + + QUnit.test('New record with a o2m also with 2 new records, ordered, and resequenced', function (assert) { + assert.expect(3); + + // Needed to have two new records in a single stroke + this.data.partner.onchanges = { + foo: function (obj) { + obj.p = [ + [5], + [0, 0, { trululu: false }], + [0, 0, { trululu: false }], + ] + } + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + viewOptions: { + mode: 'create', + }, + mockRPC: function (route, args) { + assert.step(args.method + ' ' + args.model) + return this._super(route, args); + }, + }); + + // change the int_field through drag and drop + // that way, we'll trigger the sorting and the name_get + // of the lines of "p" + testUtils.dragAndDrop( + form.$('.ui-sortable-handle').eq(1), + form.$('tbody tr').first(), + { position: 'top' } + ); + + // Only those two should have been called + // name_get on trululu would trigger an traceback + assert.verifySteps(['default_get partner', 'onchange partner']); + + form.destroy(); + }); + + QUnit.test('O2M List with pager, decoration and default_order: add and cancel adding', function (assert) { + assert.expect(3); + + // The decoration on the list implies that its condition will be evaluated + // against the data of the field (actual records *displayed*) + // If one data is wrongly formed, it will crash + // This test adds then cancels a record in a paged, ordered, and decorated list + // That implies prefetching of records for sorting + // and evaluation of the decoration against *visible records* + + this.data.partner.records[0].p = [2, 4]; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + viewOptions: { + mode: 'edit', + }, + }); + + form.$('.o_field_x2many_list .o_field_x2many_list_row_add a').click(); + + var $dataRows = form.$('.o_field_x2many_list .o_data_row'); + assert.equal($dataRows.length, 2, + 'There should be 2 rows'); + + var $expectedSelectedRow = $dataRows.eq(1); + var $actualSelectedRow = form.$('.o_selected_row'); + assert.equal($actualSelectedRow[0], $expectedSelectedRow[0], + 'The selected row should be the new one'); + + // Cancel Creation + var escapeKey = $.ui.keyCode.ESCAPE; + $actualSelectedRow.find('input').trigger( + $.Event('keydown', { which: escapeKey, keyCode: escapeKey })); + + $dataRows = form.$('.o_field_x2many_list .o_data_row'); + assert.equal($dataRows.length, 1, + 'There should be 1 row'); + + form.destroy(); + }); + + QUnit.test('O2M with parented m2o and domain on parent.m2o', function (assert) { + assert.expect(3); + + /* records in an o2m can have a m2o pointing to themselves + * in that case, a domain evaluation on that field followed by name_search + * shouldn't send virtual_ids to the server + */ + + this.data.turtle.fields.parent_id = { string: "Parent", type: "many2one", relation: 'turtle' }; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + archs: { + 'turtle,false,form': '
', + }, + + mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/turtle/name_search') { + // We are going to pass twice here + // First time, we really have nothing + // Second time, a virtual_id has been created + assert.deepEqual(args.kwargs.args, [['id', 'in', []]]); + } + return this._super(route, args); + } + }); + + form.$('.o_field_x2many_list[name=turtles] .o_field_x2many_list_row_add a').click(); + var $modal = $('.modal-content'); + + var $turtleParent = $modal.find('.o_field_many2one input'); + var $dropdown = $turtleParent.autocomplete('widget'); + + $turtleParent.click(); + + $dropdown.find('li.o_m2o_dropdown_option:contains(Create)').first().mouseenter().click(); + + $modal = $('.modal-content'); + + $modal.eq(1).find('.modal-footer .btn-primary').eq(0).click(); // Confirm new Record + $modal.eq(0).find('.modal-footer .btn-primary').eq(1).click(); // Save & New + + assert.equal(form.$('.o_data_row').length, 1, + 'The main record should have the new record in its o2m'); + + $modal = $('.modal-content'); + $modal.find('.o_field_many2one input').click(); + + form.destroy(); + }); + + QUnit.test('one2many list editable with cell readonly modifier', function (assert) { + assert.expect(4); + + this.data.partner.records[0].p = [2]; + this.data.partner.records[1].turtles = [1, 2]; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/partner/write') { + assert.deepEqual(args.args[1].p[1][2], { foo: 'ff', qux: 99 }, + 'The right values should be written'); + } + return this._super(route, args); + } + }); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + + var $targetInput = $('.o_selected_row .o_input[name=foo]'); + assert.equal($targetInput[0], document.activeElement, + 'The first input of the line should have the focus'); + + // Simulating hitting the 'f' key twice + $targetInput.val('f').trigger('input'); + $targetInput.val($targetInput.val() + 'f').trigger('input'); + + assert.equal($targetInput[0], document.activeElement, + 'The first input of the line should still have the focus'); + + // Simulating a TAB key + $targetInput.trigger($.Event('keydown', { which: 9, keyCode: 9 })); + + var $secondTarget = $('.o_selected_row .o_input[name=qux]'); + + assert.equal($secondTarget[0], document.activeElement, + 'The second input of the line should have the focus after the TAB press'); + + + $secondTarget.val(9).trigger('input'); + $secondTarget.val($secondTarget.val() + 9).trigger('input'); + + form.$buttons.find('.o_form_button_save').click(); + + form.destroy(); + }); + + QUnit.test('one2many basic properties', function (assert) { + assert.expect(6); + + this.data.partner.records[0].p = [2]; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + + assert.strictEqual(form.$('td.o_list_record_selector').length, 0, + "embedded one2many should not have a selector"); + assert.ok(!form.$('.o_field_x2many_list_row_add').length, + "embedded one2many should not be editable"); + assert.ok(!form.$('td.o_list_record_remove').length, + "embedded one2many records should not have a remove icon"); + + form.$buttons.find('.o_form_button_edit').click(); + + assert.ok(form.$('.o_field_x2many_list_row_add').length, + "embedded one2many should now be editable"); + + assert.strictEqual(form.$('.o_field_x2many_list_row_add').attr('colspan'), "2", + "should have colspan 2 (one for field foo, one for being below remove icon)"); + + assert.ok(form.$('td.o_list_record_remove').length, + "embedded one2many records should have a remove icon"); + form.destroy(); + }); + + QUnit.test('transferring class attributes in one2many sub fields', function (assert) { + assert.expect(3); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + assert.strictEqual(form.$('td.hey').length, 1, + 'should have a td with the desired class'); + + form.$buttons.find('.o_form_button_edit').click(); + + assert.strictEqual(form.$('td.hey').length, 1, + 'should have a td with the desired class'); + + form.$('td.o_data_cell').click(); + + assert.strictEqual(form.$('input[name="turtle_foo"].hey').length, 1, + 'should have an input with the desired class'); + + form.destroy(); + }); + + QUnit.test('one2many with date and datetime', function (assert) { + assert.expect(2); + + this.data.partner.records[0].p = [2]; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + session: { + getTZOffset: function () { + return 120; + }, + }, + }); + assert.strictEqual(form.$('td:eq(0)').text(), "01/25/2017", + "should have formatted the date"); + assert.strictEqual(form.$('td:eq(1)').text(), "12/12/2016 12:55:05", + "should have formatted the datetime"); + form.destroy(); + }); + + QUnit.test('rendering with embedded one2many', function (assert) { + assert.expect(2); + + this.data.partner.records[0].p = [2]; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + assert.strictEqual(form.$('th:contains(Foo)').length, 1, + "embedded one2many should have a column titled according to foo"); + assert.strictEqual(form.$('td:contains(blip)').length, 1, + "embedded one2many should have a cell with relational value"); + form.destroy(); + }); + + QUnit.test('use the limit attribute in arch (in field o2m inline tree view)', function (assert) { + assert.expect(2); + + this.data.partner.records[0].turtles = [1, 2, 3]; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + mockRPC: function (route, args) { + if (args.model === 'turtle') { + assert.deepEqual(args.args[0], [1, 2], + 'should only load first 2 records'); + } + return this._super.apply(this, arguments); + }, + }); + + assert.strictEqual(form.$('.o_data_row').length, 2, + 'should display 2 data rows'); + form.destroy(); + }); + + QUnit.test('use the limit attribute in arch (in field o2m non inline tree view)', function (assert) { + assert.expect(2); + + this.data.partner.records[0].turtles = [1, 2, 3]; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '', + archs: { + 'turtle,false,list': '', + }, + res_id: 1, + mockRPC: function (route, args) { + if (args.model === 'turtle' && args.method === 'read') { + assert.deepEqual(args.args[0], [1, 2], + 'should only load first 2 records'); + } + return this._super.apply(this, arguments); + }, + }); + + assert.strictEqual(form.$('.o_data_row').length, 2, + 'should display 2 data rows'); + form.destroy(); + }); + + QUnit.test('one2many with default_order on view not inline', function (assert) { + assert.expect(1); + + this.data.partner.records[0].turtles = [1, 2, 3]; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + archs: { + 'turtle,false,list': '' + + '' + + '' + + '', + }, + res_id: 1, + }); + assert.strictEqual(form.$('.o_field_one2many .o_list_view .o_data_row').text(), "9blip21kawa0yop", + "the default order should be correctly applied"); + form.destroy(); + }); + + QUnit.test('embedded one2many with widget', function (assert) { + assert.expect(1); + + this.data.partner.records[0].p = [2]; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + assert.strictEqual(form.$('span.o_row_handle').length, 1, "should have 1 handles"); + form.destroy(); + }); + + QUnit.test('embedded one2many with handle widget', function (assert) { + assert.expect(10); + + var nbConfirmChange = 0; + testUtils.patch(ListRenderer, { + confirmChange: function () { + nbConfirmChange++; + return this._super.apply(this, arguments); + }, + }); + + this.data.partner.records[0].turtles = [1, 2, 3]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + testUtils.intercept(form, "field_changed", function (event) { + assert.step(event.data.changes.turtles.data.turtle_int.toString()); + }, true); + + assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "yopblipkawa", + "should have the 3 rows in the correct order"); + + form.$buttons.find('.o_form_button_edit').click(); + + assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "yopblipkawa", + "should still have the 3 rows in the correct order"); + assert.strictEqual(nbConfirmChange, 0, "should not have confirmed any change yet"); + + // Drag and drop the second line in first position + testUtils.dragAndDrop( + form.$('.ui-sortable-handle').eq(1), + form.$('tbody tr').first(), + { position: 'top' } + ); + + assert.strictEqual(nbConfirmChange, 1, "should have confirmed changes only once"); + assert.verifySteps(["0", "1"], + "sequences values should be incremental starting from the previous minimum one"); + + assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "blipyopkawa", + "should have the 3 rows in the new order"); + + form.$buttons.find('.o_form_button_save').click(); + + assert.deepEqual(_.map(this.data.turtle.records, function (turtle) { + return _.pick(turtle, 'id', 'turtle_foo', 'turtle_int'); + }), [ + { id: 1, turtle_foo: "yop", turtle_int: 1 }, + { id: 2, turtle_foo: "blip", turtle_int: 0 }, + { id: 3, turtle_foo: "kawa", turtle_int: 21 } + ], "should have save the changed sequence"); + + assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "blipyopkawa", + "should still have the 3 rows in the new order"); + + testUtils.unpatch(ListRenderer); + + form.destroy(); + }); + + QUnit.test('onchange for embedded one2many in a one2many with a second page', function (assert) { + assert.expect(1); + + this.data.turtle.fields.partner_ids.type = 'one2many'; + this.data.turtle.records[0].partner_ids = [1]; + // we need a second page, so we set two records and only display one per page + this.data.partner.records[0].turtles = [1, 2]; + + this.data.partner.onchanges = { + turtles: function (obj) { + obj.turtles = [ + [5], + [1, 1, { + turtle_foo: "hop", + partner_ids: [[5], [4, 1]], + }], + [1, 2, { + turtle_foo: "blip", + partner_ids: [[5], [4, 2], [4, 4]], + }], + ]; + }, + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'write') { + var expectedResultTurtles = [ + [1, 1, { + turtle_foo: "hop", + }], + [1, 2, { + partner_ids: [[4, 2, false], [4, 4, false]], + turtle_foo: "blip", + }], + ]; + assert.deepEqual(args.args[1].turtles, expectedResultTurtles, + "the right values should be written"); + } + return this._super.apply(this, arguments); + } + }); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_data_cell').eq(1).click(); + var $cell = form.$('.o_selected_row .o_input[name=turtle_foo]'); + $cell.val("hop").trigger('change'); + form.$buttons.find('.o_form_button_save').click(); + + form.destroy(); + }); + + QUnit.test('onchange for embedded one2many in a one2many updated by server', function (assert) { + // here we test that after an onchange, the embedded one2many field has + // been updated by a new list of ids by the server response, to this new + // list should be correctly sent back at save time + assert.expect(3); + + this.data.turtle.fields.partner_ids.type = 'one2many'; + this.data.partner.records[0].turtles = [2]; + this.data.turtle.records[1].partner_ids = [2]; + + this.data.partner.onchanges = { + turtles: function (obj) { + obj.turtles = [ + [5], + [1, 2, { + turtle_foo: "hop", + partner_ids: [[5], [4, 2], [4, 4]], + }], + ]; + }, + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/partner/write') { + var expectedResultTurtles = [ + [1, 2, { + partner_ids: [[4, 2, false], [4, 4, false]], + turtle_foo: "hop", + }], + ]; + assert.deepEqual(args.args[1].turtles, expectedResultTurtles, + 'The right values should be written'); + } + return this._super.apply(this, arguments); + } + }); + + assert.deepEqual(form.$('.o_many2many_tags_cell').text().trim(), "second record", + "the partner_ids should be as specified at initialization"); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_data_cell').eq(1).click(); + var $cell = form.$('.o_selected_row .o_input[name=turtle_foo]'); + $cell.val("hop").trigger("change"); + form.$buttons.find('.o_form_button_save').click(); + + assert.deepEqual(form.$('.o_many2many_tags_cell').text().trim().split(/\s+/), + ["second", "record", "aaa"], + 'The partner_ids should have been updated'); + + form.destroy(); + }); + + QUnit.test('onchange for embedded one2many with handle widget', function (assert) { + assert.expect(2); + + this.data.partner.records[0].turtles = [1, 2, 3]; + var partnerOnchange = 0; + this.data.partner.onchanges = { + turtles: function (obj) { + partnerOnchange++; + }, + }; + var turtleOnchange = 0; + this.data.turtle.onchanges = { + turtle_int: function (obj) { + turtleOnchange++; + }, + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + form.$buttons.find('.o_form_button_edit').click(); + + // Drag and drop the second line in first position + testUtils.dragAndDrop( + form.$('.ui-sortable-handle').eq(1), + form.$('tbody tr').first(), + { position: 'top' } + ); + + assert.strictEqual(turtleOnchange, 2, "should trigger one onchange per line updated"); + assert.strictEqual(partnerOnchange, 1, "should trigger only one onchange on the parent"); + + form.destroy(); + }); + + QUnit.test('onchange for embedded one2many with handle widget using same sequence', function (assert) { + assert.expect(4); + + this.data.turtle.records[0].turtle_int = 1; + this.data.turtle.records[1].turtle_int = 1; + this.data.turtle.records[2].turtle_int = 1; + this.data.partner.records[0].turtles = [1, 2, 3]; + var turtleOnchange = 0; + this.data.turtle.onchanges = { + turtle_int: function () { + turtleOnchange++; + }, + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'write') { + assert.deepEqual(args.args[1].turtles, [[4, 2, false], [1, 1, { "turtle_int": 2 }], [1, 3, { "turtle_int": 3 }]], + "should change all lines that have changed (the first one doesn't change because it has the same sequence)"); + } + return this._super.apply(this, arguments); + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + + + assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "yopblipkawa", + "should have the 3 rows in the correct order"); + + // Drag and drop the second line in first position + testUtils.dragAndDrop( + form.$('.ui-sortable-handle').eq(1), + form.$('tbody tr').first(), + { position: 'top' } + ); + + assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "blipyopkawa", + "should still have the 3 rows in the correct order"); + assert.strictEqual(turtleOnchange, 3, "should update all lines"); + + form.$buttons.find('.o_form_button_save').click(); + form.destroy(); + }); + + QUnit.test('onchange (with command 5) for embedded one2many with handle widget', function (assert) { + assert.expect(3); + + var ids = []; + for (var i = 10; i < 50; i++) { + var id = 10 + i; + ids.push(id); + this.data.turtle.records.push({ + id: id, + turtle_int: 0, + turtle_foo: "#" + id, + }); + } + ids.push(1, 2, 3); + this.data.partner.records[0].turtles = ids; + this.data.partner.onchanges = { + turtles: function (obj) { + obj.turtles = [[5]].concat(obj.turtles); + }, + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + form.$('.o_pager_next').click(); + assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "yopblipkawa", + "should have the 3 rows in the correct order"); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_one2many .o_list_view tbody tr:first td:first').click(); + form.$('.o_field_one2many .o_list_view tbody tr:first input:first').val('blurp').trigger('input'); + + // Drag and drop the third line in second position + testUtils.dragAndDrop( + form.$('.ui-sortable-handle').eq(2), + form.$('.o_field_one2many tbody tr').eq(1), + { position: 'top' } + ); + + assert.strictEqual(form.$('.o_data_cell').text(), "blurpkawablip", "should display to record in 'turtle_int' order"); + + form.$buttons.find('.o_form_button_save').click(); + form.$('.o_pager_next').click(); + + assert.strictEqual(form.$('.o_data_cell:not(.o_handle_cell)').text(), "blurpkawablip", + "should display to record in 'turtle_int' order"); + + form.destroy(); + }); + + QUnit.test('onchange with modifiers for embedded one2many on the second page', function (assert) { + assert.expect(7); + + var data = this.data; + var ids = []; + for (var i = 10; i < 60; i++) { + var id = 10 + i; + ids.push(id); + data.turtle.records.push({ + id: id, + turtle_int: 0, + turtle_foo: "#" + id, + }); + } + ids.push(1, 2, 3); + data.partner.records[0].turtles = ids; + data.partner.onchanges = { + turtles: function (obj) { + // TODO: make this test more 'difficult' + // For now, the server only returns UPDATE commands (no LINK TO) + // even though it should do it (for performance reasons) + // var turtles = obj.turtles.splice(0, 20); + + var turtles = []; + turtles.unshift([5]); + // create UPDATE commands for each records (this is the server + // usual answer for onchange) + for (var k in obj.turtles) { + var change = obj.turtles[k]; + var record = _.findWhere(data.turtle.records, { id: change[1] }); + if (change[0] === 1) { + _.extend(record, change[2]); + } + turtles.push([1, record.id, record]); + } + obj.turtles = turtles; + }, + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + form.$buttons.find('.o_form_button_edit').click(); + + assert.equal(form.$('.o_field_one2many td[class="o_data_cell"]').text(), "#20#21#22#23#24#25#26#27#28#29", + "should display the records in order"); + + form.$('.o_field_one2many .o_list_view tbody tr:first td:first').click(); + form.$('.o_field_one2many .o_list_view tbody tr:first input:first').val('blurp').trigger('input'); + + // the domain fail if the widget does not use the allready loaded data. + form.$buttons.find('.o_form_button_cancel').click(); + + assert.equal(form.$('.o_field_one2many td[class="o_data_cell"]').text(), "blurp#21#22#23#24#25#26#27#28#29", + "should display the records in order with the changes"); + + $('.modal .modal-footer button:first').click(); + + assert.equal(form.$('.o_field_one2many td[class="o_data_cell"]').text(), "#20#21#22#23#24#25#26#27#28#29", + "should cancel changes and display the records in order"); + + form.$buttons.find('.o_form_button_edit').click(); + + // Drag and drop the third line in second position + testUtils.dragAndDrop( + form.$('.ui-sortable-handle').eq(2), + form.$('.o_field_one2many tbody tr').eq(1), + { position: 'top' } + ); + + assert.equal(form.$('.o_field_one2many td[class="o_data_cell"]').text(), "#20#30#31#32#33#34#35#36#37#38", + "should display the records in order after resequence (display record with turtle_int=0)"); + + // Drag and drop the third line in second position + testUtils.dragAndDrop( + form.$('.ui-sortable-handle').eq(2), + form.$('.o_field_one2many tbody tr').eq(1), + { position: 'top' } + ); + + assert.equal(form.$('.o_field_one2many td[class="o_data_cell"]').text(), "#20#39#40#41#42#43#44#45#46#47", + "should display the records in order after resequence (display record with turtle_int=0)"); + + form.$buttons.find('.o_form_button_cancel').click(); + + assert.equal(form.$('.o_field_one2many td[class="o_data_cell"]').text(), "#20#39#40#41#42#43#44#45#46#47", + "should display the records in order after resequence"); + + $('.modal .modal-footer button:first').click(); + + assert.equal(form.$('.o_field_one2many td[class="o_data_cell"]').text(), "#20#21#22#23#24#25#26#27#28#29", + "should cancel changes and display the records in order"); + + form.destroy(); + }); + + QUnit.test('onchange followed by edition on the second page', function (assert) { + assert.expect(12); + + var ids = []; + for (var i = 1; i < 85; i++) { + var id = 10 + i; + ids.push(id); + this.data.turtle.records.push({ + id: id, + turtle_int: id / 3 | 0, + turtle_foo: "#" + i, + }); + } + ids.splice(41, 0, 1, 2, 3); + this.data.partner.records[0].turtles = ids; + this.data.partner.onchanges = { + turtles: function (obj) { + obj.turtles = [[5]].concat(obj.turtles); + }, + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_pager_next').click(); + + form.$('.o_field_one2many .o_list_view tbody tr:eq(1) td:first').click(); + form.$('.o_field_one2many .o_list_view tbody tr:eq(1) input:first').val('value 1').trigger('input'); + form.$('.o_field_one2many .o_list_view tbody tr:eq(2) td:first').click(); + form.$('.o_field_one2many .o_list_view tbody tr:eq(2) input:first').val('value 2').trigger('input'); + + assert.strictEqual(form.$('.o_data_row').length, 40, "should display 40 records"); + assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(#39))').index(), 0, "should display '#39' at the first line"); + + form.$('.o_field_x2many_list_row_add a').click(); + + assert.strictEqual(form.$('.o_data_row').length, 40, "should display 39 records and the create line"); + assert.strictEqual(form.$('.o_data_row:first .o_field_char').length, 1, "should display the create line in first position"); + assert.strictEqual(form.$('.o_data_row:first .o_field_char').val(), "", "should an empty input"); + assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(#39))').index(), 1, "should display '#39' at the second line"); + + form.$('.o_data_row input:first').val('value 3').trigger('input'); + + assert.strictEqual(form.$('.o_data_row:first .o_field_char').length, 1, "should display the create line in first position after onchange"); + assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(#39))').index(), 1, "should display '#39' at the second line after onchange"); + + form.$('.o_field_x2many_list_row_add a').click(); + + assert.strictEqual(form.$('.o_data_row').length, 40, "should display 39 records and the create line"); + assert.strictEqual(form.$('.o_data_row:first .o_field_char').length, 1, "should display the create line in first position"); + assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(value 3))').index(), 1, "should display the created line at the second position"); + assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(#39))').index(), 2, "should display '#39' at the third line"); + + form.destroy(); + }); + + QUnit.test('onchange followed by edition on the second page (part 2)', function (assert) { + assert.expect(8); + + var ids = []; + for (var i = 1; i < 85; i++) { + var id = 10 + i; + ids.push(id); + this.data.turtle.records.push({ + id: id, + turtle_int: id / 3 | 0, + turtle_foo: "#" + i, + }); + } + ids.splice(41, 0, 1, 2, 3); + this.data.partner.records[0].turtles = ids; + this.data.partner.onchanges = { + turtles: function (obj) { + obj.turtles = [[5]].concat(obj.turtles); + }, + }; + + // bottom order + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_pager_next').click(); + + form.$('.o_field_one2many .o_list_view tbody tr:eq(1) td:first').click(); + form.$('.o_field_one2many .o_list_view tbody tr:eq(1) input:first').val('value 1').trigger('input'); + form.$('.o_field_one2many .o_list_view tbody tr:eq(2) td:first').click(); + form.$('.o_field_one2many .o_list_view tbody tr:eq(2) input:first').val('value 2').trigger('input'); + + assert.strictEqual(form.$('.o_data_row').length, 40, "should display 40 records"); + assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(#77))').index(), 39, "should display '#77' at the last line"); + + form.$('.o_field_x2many_list_row_add a').click(); + + assert.strictEqual(form.$('.o_data_row').length, 41, "should display 41 records and the create line"); + assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(#76))').index(), 38, "should display '#76' at the penultimate line"); + assert.strictEqual(form.$('.o_data_row:has(.o_field_char)').index(), 40, "should display the create line at the last position"); + + form.$('.o_data_row input:first').val('value 3').trigger('input'); + form.$('.o_field_x2many_list_row_add a').click(); + + assert.strictEqual(form.$('.o_data_row').length, 42, "should display 42 records and the create line"); + assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(#76))').index(), 38, "should display '#76' at the penultimate line"); + assert.strictEqual(form.$('.o_data_row:has(.o_field_char)').index(), 41, "should display the create line at the last position"); + + form.destroy(); + }); + + QUnit.test('onchange returning a command 6 for an x2many', function (assert) { + assert.expect(2); + + this.data.partner.onchanges = { + foo: function (obj) { + obj.turtles = [[6, false, [1, 2, 3]]]; + }, + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + res_id: 1, + viewOptions: { + mode: 'edit', + }, + }); + + assert.strictEqual(form.$('.o_data_row').length, 1, + "there should be one record in the relation"); + + // change the value of foo to trigger the onchange + form.$('.o_field_widget[name=foo]').val('some value').trigger('input'); + + assert.strictEqual(form.$('.o_data_row').length, 3, + "there should be three records in the relation"); + + form.destroy(); + }); + + QUnit.test('x2many fields inside x2manys are fetched after an onchange', function (assert) { + assert.expect(6); + + this.data.turtle.records[0].partner_ids = [1]; + this.data.partner.onchanges = { + foo: function (obj) { + obj.turtles = [[5], [4, 1], [4, 2], [4, 3]]; + }, + }; + + var checkRPC = false; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + mockRPC: function (route, args) { + if (checkRPC && args.method === 'read' && args.model === 'partner') { + assert.deepEqual(args.args[1], ['display_name'], + "should only read the display_name for the m2m tags"); + assert.deepEqual(args.args[0], [1], + "should only read the display_name of the unknown record"); + } + return this._super.apply(this, arguments); + }, + res_id: 1, + viewOptions: { + mode: 'edit', + }, + }); + + assert.strictEqual(form.$('.o_data_row').length, 1, + "there should be one record in the relation"); + assert.strictEqual(form.$('.o_data_row .o_field_widget[name=partner_ids]').text().replace(/\s/g, ''), + 'secondrecordaaa', "many2many_tags should be correctly displayed"); + + // change the value of foo to trigger the onchange + checkRPC = true; // enable flag to check read RPC for the m2m field + form.$('.o_field_widget[name=foo]').val('some value').trigger('input'); + + assert.strictEqual(form.$('.o_data_row').length, 3, + "there should be three records in the relation"); + assert.strictEqual(form.$('.o_data_row:first .o_field_widget[name=partner_ids]').text().trim(), + 'first record', "many2many_tags should be correctly displayed"); + + form.destroy(); + }); + + QUnit.test('reference fields inside x2manys are fetched after an onchange', function (assert) { + assert.expect(5); + + this.data.turtle.records[1].turtle_ref = 'product,41'; + this.data.partner.onchanges = { + foo: function (obj) { + obj.turtles = [[5], [4, 1], [4, 2], [4, 3]]; + }, + }; + + var checkRPC = false; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + mockRPC: function (route, args) { + if (checkRPC && args.method === 'name_get') { + assert.deepEqual(args.args[0], [37], + "should only fetch the name_get of the unknown record"); + } + return this._super.apply(this, arguments); + }, + res_id: 1, + viewOptions: { + mode: 'edit', + }, + }); + + assert.strictEqual(form.$('.o_data_row').length, 1, + "there should be one record in the relation"); + assert.strictEqual(form.$('.ref_field').text().trim(), 'xpad', + "reference field should be correctly displayed"); + + // change the value of foo to trigger the onchange + checkRPC = true; // enable flag to check read RPC for reference field + form.$('.o_field_widget[name=foo]').val('some value').trigger('input'); + + assert.strictEqual(form.$('.o_data_row').length, 3, + "there should be three records in the relation"); + assert.strictEqual(form.$('.ref_field').text().trim(), 'xpadxphone', + "reference fields should be correctly displayed"); + + form.destroy(); + }); + + QUnit.test('onchange on one2many containing x2many in form view', function (assert) { + assert.expect(16); + + this.data.partner.onchanges = { + foo: function (obj) { + obj.turtles = [[0, false, { turtle_foo: 'new record' }]]; + }, + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + archs: { + 'partner,false,list': '', + 'partner,false,search': '', + }, + }); + + assert.strictEqual(form.$('.o_data_row').length, 1, + "the onchange should have created one record in the relation"); + + // open the created o2m record in a form view, and add a m2m subrecord + // in its relation + form.$('.o_data_row').click(); + + assert.strictEqual($('.modal').length, 1, "should have opened a dialog"); + assert.strictEqual($('.modal .o_data_row').length, 0, + "there should be no record in the one2many in the dialog"); + + // add a many2many subrecord + $('.modal .o_field_x2many_list_row_add a').click(); + + assert.strictEqual($('.modal').length, 2, + "should have opened a second dialog"); + + // select a many2many subrecord + $('.modal:nth(1) .o_list_view .o_data_cell:first').click(); + + assert.strictEqual($('.modal').length, 1, + "second dialog should be closed"); + assert.strictEqual($('.modal .o_data_row').length, 1, + "there should be one record in the one2many in the dialog"); + assert.notOk($('.modal .o_x2m_control_panel .o_cp_pager div').is(':visible'), + 'm2m pager should be hidden'); + + // click on 'Save & Close' + $('.modal-footer .btn-primary:first').click(); + + assert.strictEqual($('.modal').length, 0, "dialog should be closed"); + + // reopen o2m record, and another m2m subrecord in its relation, but + // discard the changes + form.$('.o_data_row').click(); + + assert.strictEqual($('.modal').length, 1, "should have opened a dialog"); + assert.strictEqual($('.modal .o_data_row').length, 1, + "there should be one record in the one2many in the dialog"); + + // add another m2m subrecord + $('.modal .o_field_x2many_list_row_add a').click(); + + assert.strictEqual($('.modal').length, 2, + "should have opened a second dialog"); + + $('.modal:nth(1) .o_list_view .o_data_cell:first').click(); + + assert.strictEqual($('.modal').length, 1, + "second dialog should be closed"); + assert.strictEqual($('.modal .o_data_row').length, 2, + "there should be two records in the one2many in the dialog"); + + // click on 'Discard' + $('.modal-footer .btn-secondary').click(); + + assert.strictEqual($('.modal').length, 0, "dialog should be closed"); + + // reopen o2m record to check that second changes have properly been discarded + form.$('.o_data_row').click(); + + assert.strictEqual($('.modal').length, 1, "should have opened a dialog"); + assert.strictEqual($('.modal .o_data_row').length, 1, + "there should be one record in the one2many in the dialog"); + + form.destroy(); + }); + + QUnit.test('embedded one2many with handle widget with minimum setValue calls', function (assert) { + var done = assert.async(); + assert.expect(20); + + + this.data.turtle.records[0].turtle_int = 6; + this.data.turtle.records.push({ + id: 4, + turtle_int: 20, + turtle_foo: "a1", + }, { + id: 5, + turtle_int: 9, + turtle_foo: "a2", + }, { + id: 6, + turtle_int: 2, + turtle_foo: "a3", + }, { + id: 7, + turtle_int: 11, + turtle_foo: "a4", + }); + this.data.partner.records[0].turtles = [1, 2, 3, 4, 5, 6, 7]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + testUtils.intercept(form, "field_changed", function (event) { + assert.step(form.model.get(event.data.changes.turtles.id).res_id); + }, true); + + form.$buttons.find('.o_form_button_edit').click(); + + var steps = []; + var positions = [ + [6, 0, 'top', [3, 6, 1, 2, 5, 7, 4]], // move the last to the first line + [5, 1, 'top', [7, 6, 1, 2, 5]], // move the penultimate to the second line + [2, 5, 'center', [1, 2, 5, 6]], // move the third to the penultimate line + ]; + function dragAndDrop() { + var pos = positions.shift(); + + testUtils.dragAndDrop( + form.$('.ui-sortable-handle').eq(pos[0]), + form.$('tbody tr').eq(pos[1]), + { position: pos[2] } + ); + + steps = steps.concat(pos[3]); + assert.verifySteps(steps, + "sequences values should be apply from the begin index to the drop index"); + + if (positions.length) { + + setTimeout(dragAndDrop, 10); + + } else { + + assert.deepEqual(_.pluck(form.model.get(form.handle).data.turtles.data, 'data'), [ + { id: 3, turtle_foo: "kawa", turtle_int: 2 }, + { id: 7, turtle_foo: "a4", turtle_int: 3 }, + { id: 1, turtle_foo: "yop", turtle_int: 4 }, + { id: 2, turtle_foo: "blip", turtle_int: 5 }, + { id: 5, turtle_foo: "a2", turtle_int: 6 }, + { id: 6, turtle_foo: "a3", turtle_int: 7 }, + { id: 4, turtle_foo: "a1", turtle_int: 8 } + ], "sequences must be apply correctly"); + + form.destroy(); + done(); + } + } + + dragAndDrop(); + }); + + QUnit.test('embedded one2many (editable list) with handle widget', function (assert) { + assert.expect(8); + + this.data.partner.records[0].p = [1, 2, 4]; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + testUtils.intercept(form, "field_changed", function (event) { + assert.step(event.data.changes.p.data.int_field.toString()); + }, true); + + assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "My little Foo Valueblipyop", + "should have the 3 rows in the correct order"); + + form.$buttons.find('.o_form_button_edit').click(); + assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "My little Foo Valueblipyop", + "should still have the 3 rows in the correct order"); + + // Drag and drop the second line in first position + testUtils.dragAndDrop( + form.$('.ui-sortable-handle').eq(1), + form.$('tbody tr').first(), + { position: 'top' } + ); + + assert.verifySteps(["0", "1"], + "sequences values should be incremental starting from the previous minimum one"); + + assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "blipMy little Foo Valueyop", + "should have the 3 rows in the new order"); + + form.$('tbody tr:first td:first').click(); + + assert.strictEqual(form.$('tbody tr:first td.o_data_cell:not(.o_handle_cell) input').val(), "blip", + "should edit the correct row"); + + form.$buttons.find('.o_form_button_save').click(); + assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "blipMy little Foo Valueyop", + "should still have the 3 rows in the new order"); + + form.destroy(); + }); + + QUnit.test('one2many field when using the pager', function (assert) { + assert.expect(13); + + var ids = []; + for (var i = 0; i < 45; i++) { + var id = 10 + i; + ids.push(id); + this.data.partner.records.push({ + id: id, + display_name: "relational record " + id, + }); + } + this.data.partner.records[0].p = ids.slice(0, 42); + this.data.partner.records[1].p = ids.slice(42); + + var count = 0; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
', + viewOptions: { + ids: [1, 2], + index: 0, + }, + mockRPC: function () { + count++; + return this._super.apply(this, arguments); + }, + res_id: 1, + }); + + // we are on record 1, which has 90 related record (first 40 should be + // displayed), 2 RPCs (read) should have been done, one on the main record + // and one for the O2M + assert.strictEqual(count, 2, 'two RPCs should have been done'); + assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40, + 'one2many kanban should contain 40 cards for record 1'); + + // move to record 2, which has 3 related records (and shouldn't contain the + // related records of record 1 anymore). Two additional RPCs should have + // been done + form.pager.next(); + assert.strictEqual(count, 4, 'two RPCs should have been done'); + assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 3, + 'one2many kanban should contain 3 cards for record 2'); + + // move back to record 1, which should contain again its first 40 related + // records + form.pager.previous(); + assert.strictEqual(count, 6, 'two RPCs should have been done'); + assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40, + 'one2many kanban should contain 40 cards for record 1'); + + // move to the second page of the o2m: 1 RPC should have been done to fetch + // the 2 subrecords of page 2, and those records should now be displayed + form.$('.o_x2m_control_panel .o_pager_next').click(); + assert.strictEqual(count, 7, 'one RPC should have been done'); + assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 2, + 'one2many kanban should contain 2 cards for record 1 at page 2'); + + // move to record 2 again and check that everything is correctly updated + form.pager.next(); + assert.strictEqual(count, 9, 'two RPCs should have been done'); + assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 3, + 'one2many kanban should contain 3 cards for record 2'); + + // move back to record 1 and move to page 2 again: all data should have + // been correctly reloaded + form.pager.previous(); + assert.strictEqual(count, 11, 'two RPCs should have been done'); + form.$('.o_x2m_control_panel .o_pager_next').click(); + assert.strictEqual(count, 12, 'one RPC should have been done'); + assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 2, + 'one2many kanban should contain 2 cards for record 1 at page 2'); + form.destroy(); + }); + + QUnit.test('edition of one2many field with pager', function (assert) { + assert.expect(31); + + var ids = []; + for (var i = 0; i < 45; i++) { + var id = 10 + i; + ids.push(id); + this.data.partner.records.push({ + id: id, + display_name: "relational record " + id, + }); + } + this.data.partner.records[0].p = ids; + + var saveCount = 0; + var checkRead = false; + var readIDs; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + archs: { + 'partner,false,form': '
', + }, + mockRPC: function (route, args) { + if (args.method === 'read' && checkRead) { + readIDs = args.args[0]; + checkRead = false; + } + if (args.method === 'write') { + saveCount++; + var nbCommands = args.args[1].p.length; + var nbLinkCommands = _.filter(args.args[1].p, function (command) { + return command[0] === 4; + }).length; + switch (saveCount) { + case 1: + assert.strictEqual(nbCommands, 46, + "should send 46 commands (one for each record)"); + assert.strictEqual(nbLinkCommands, 45, + "should send a LINK_TO command for each existing record"); + assert.deepEqual(args.args[1].p[45], [0, args.args[1].p[45][1], { + display_name: 'new record', + }], "should sent a CREATE command for the new record"); + break; + case 2: + assert.strictEqual(nbCommands, 46, + "should send 46 commands"); + assert.strictEqual(nbLinkCommands, 45, + "should send a LINK_TO command for each existing record"); + assert.deepEqual(args.args[1].p[45], [2, 10, false], + "should sent a DELETE command for the deleted record"); + break; + case 3: + assert.strictEqual(nbCommands, 47, + "should send 47 commands"); + assert.strictEqual(nbLinkCommands, 43, + "should send a LINK_TO command for each existing record"); + assert.deepEqual(args.args[1].p[43], + [0, args.args[1].p[43][1], { display_name: 'new record page 1' }], + "should sent correct CREATE command"); + assert.deepEqual(args.args[1].p[44], + [0, args.args[1].p[44][1], { display_name: 'new record page 2' }], + "should sent correct CREATE command"); + assert.deepEqual(args.args[1].p[45], + [2, 11, false], + "should sent correct DELETE command"); + assert.deepEqual(args.args[1].p[46], + [2, 52, false], + "should sent correct DELETE command"); + break; + } + } + return this._super.apply(this, arguments); + }, + res_id: 1, + }); + + assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40, + 'there should be 40 records on page 1'); + assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(), + '1-40 / 45', "pager range should be correct"); + + // add a record on page one + checkRead = true; + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o-kanban-button-new').click(); + $('.modal input').val('new record').trigger('input'); + $('.modal .modal-footer .btn-primary:first').click(); // save and close + // checks + assert.strictEqual(readIDs, undefined, "should not have read any record"); + assert.strictEqual(form.$('span:contains(new record)').length, 0, + "new record should be on page 2"); + assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40, + 'there should be 40 records on page 1'); + assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(), + '1-40 / 46', "pager range should be correct"); + assert.strictEqual(form.$('.o_kanban_record:first span:contains(new record)').length, + 0, 'new record should not be on page 1'); + // save + form.$buttons.find('.o_form_button_save').click(); + + // delete a record on page one + checkRead = true; + form.$buttons.find('.o_form_button_edit').click(); + assert.strictEqual(form.$('.o_kanban_record:first span:contains(relational record 10)').length, + 1, 'first record should be the one with id 10 (next checks rely on that)'); + form.$('.delete_icon:first').click(); + // checks + assert.deepEqual(readIDs, [50], + "should have read a record (to display 40 records on page 1)"); + assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40, + 'there should be 40 records on page 1'); + assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(), + '1-40 / 45', "pager range should be correct"); + // save + form.$buttons.find('.o_form_button_save').click(); + + // add and delete records in both pages + form.$buttons.find('.o_form_button_edit').click(); + checkRead = true; + readIDs = undefined; + // add and delete a record in page 1 + form.$('.o-kanban-button-new').click(); + $('.modal input').val('new record page 1').trigger('input'); + $('.modal .modal-footer .btn-primary:first').click(); // save and close + assert.strictEqual(form.$('.o_kanban_record:first span:contains(relational record 11)').length, + 1, 'first record should be the one with id 11 (next checks rely on that)'); + form.$('.delete_icon:first').click(); + assert.deepEqual(readIDs, [51], + "should have read a record (to display 40 records on page 1)"); + // add and delete a record in page 2 + form.$('.o_x2m_control_panel .o_pager_next').click(); + assert.strictEqual(form.$('.o_kanban_record:first span:contains(relational record 52)').length, + 1, 'first record should be the one with id 52 (next checks rely on that)'); + checkRead = true; + readIDs = undefined; + form.$('.delete_icon:first').click(); + form.$('.o-kanban-button-new').click(); + $('.modal input').val('new record page 2').trigger('input'); + $('.modal .modal-footer .btn-primary:first').click(); // save and close + assert.strictEqual(readIDs, undefined, "should not have read any record"); + // checks + assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 5, + 'there should be 5 records on page 2'); + assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(), + '41-45 / 45', "pager range should be correct"); + assert.strictEqual(form.$('.o_kanban_record span:contains(new record page 1)').length, + 1, 'new records should be on page 2'); + assert.strictEqual(form.$('.o_kanban_record span:contains(new record page 2)').length, + 1, 'new records should be on page 2'); + // save + form.$buttons.find('.o_form_button_save').click(); + + form.destroy(); + }); + + QUnit.test('edition of one2many field, with onchange and not inline sub view', function (assert) { + assert.expect(2); + + this.data.turtle.onchanges.turtle_int = function (obj) { + obj.turtle_foo = String(obj.turtle_int); + }; + this.data.partner.onchanges.turtles = function () { }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '', + archs: { + 'turtle,false,list': '', + 'turtle,false,form': '
', + }, + mockRPC: function (route, args) { + return this._super.apply(this, arguments); + }, + res_id: 1, + }); + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + $('input[name="turtle_int"]').val('5').trigger('input'); + $('.modal-footer button.btn-primary').first().click(); + assert.strictEqual(form.$('tbody tr:eq(1) td.o_data_cell').text(), '5', + 'should display 5 in the foo field'); + form.$('tbody tr:eq(1) td.o_data_cell').click(); + + $('input[name="turtle_int"]').val('3').trigger('input'); + $('.modal-footer button.btn-primary').first().click(); + assert.strictEqual(form.$('tbody tr:eq(1) td.o_data_cell').text(), '3', + 'should now display 3 in the foo field'); + form.destroy(); + }); + + QUnit.test('sorting one2many fields', function (assert) { + assert.expect(4); + + this.data.partner.fields.foo.sortable = true; + this.data.partner.records.push({ id: 23, foo: "abc" }); + this.data.partner.records.push({ id: 24, foo: "xyz" }); + this.data.partner.records.push({ id: 25, foo: "def" }); + this.data.partner.records[0].p = [23, 24, 25]; + + var rpcCount = 0; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + mockRPC: function () { + rpcCount++; + return this._super.apply(this, arguments); + }, + }); + + rpcCount = 0; + assert.ok(form.$('table tbody tr:eq(2) td:contains(def)').length, + "the 3rd record is the one with 'def' value"); + form.renderer._render = function () { + throw "should not render the whole form"; + }; + + form.$('table thead th:contains(Foo)').click(); + assert.strictEqual(rpcCount, 0, + 'sort should be in memory, no extra RPCs should have been done'); + assert.ok(form.$('table tbody tr:eq(2) td:contains(xyz)').length, + "the 3rd record is the one with 'xyz' value"); + + form.$('table thead th:contains(Foo)').click(); + assert.ok(form.$('table tbody tr:eq(2) td:contains(abc)').length, + "the 3rd record is the one with 'abc' value"); + + form.destroy(); + }); + + QUnit.test('one2many list field edition', function (assert) { + assert.expect(6); + + this.data.partner.records.push({ + id: 3, + display_name: "relational record 1", + }); + this.data.partner.records[1].p = [3]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 2, + }); + + // edit the first line of the o2m + assert.strictEqual(form.$('.o_field_one2many tbody td').first().text(), 'relational record 1', + "display name of first record in o2m list should be 'relational record 1'"); + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_one2many tbody td').first().click(); + assert.ok(form.$('.o_field_one2many tbody td').first().parent().hasClass('o_selected_row'), + "first row of o2m should be in edition"); + form.$('.o_field_one2many tbody td').first().find('input').val("new value").trigger('input'); + assert.ok(form.$('.o_field_one2many tbody td').first().parent().hasClass('o_selected_row'), + "first row of o2m should still be in edition"); + + // // leave o2m edition + form.$el.click(); + assert.ok(!form.$('.o_field_one2many tbody td').first().parent().hasClass('o_selected_row'), + "first row of o2m should be readonly again"); + + // discard changes + form.$buttons.find('.o_form_button_cancel').click(); + assert.strictEqual(form.$('.o_field_one2many tbody td').first().text(), 'new value', + "changes shouldn't have been discarded yet, waiting for user confirmation"); + $('.modal .modal-footer .btn-primary').click(); + assert.strictEqual(form.$('.o_field_one2many tbody td').first().text(), 'relational record 1', + "display name of first record in o2m list should be 'relational record 1'"); + + // edit again and save + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_one2many tbody td').first().click(); + form.$('.o_field_one2many tbody td').first().find('input').val("new value").trigger('input'); + form.$el.click(); + form.$buttons.find('.o_form_button_save').click(); + // FIXME: this next test doesn't pass as the save of updates of + // relational data is temporarily disabled + // assert.strictEqual(form.$('.o_field_one2many tbody td').first().text(), 'new value', + // "display name of first record in o2m list should be 'new value'"); + + form.destroy(); + }); + + QUnit.test('one2many list: create action disabled', function (assert) { + assert.expect(2); + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + assert.ok(!form.$('.o_field_x2many_list_row_add').length, + '"Add an item" link should not be available in readonly'); + + form.$buttons.find('.o_form_button_edit').click(); + + assert.ok(!form.$('.o_field_x2many_list_row_add').length, + '"Add an item" link should not be available in readonly'); + form.destroy(); + }); + + QUnit.test('one2many list: unlink two records', function (assert) { + assert.expect(9); + this.data.partner.records[0].p = [1, 2, 4]; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/partner/write') { + var commands = args.args[1].p; + assert.strictEqual(commands.length, 3, + 'should have generated three commands'); + assert.ok(commands[0][0] === 4 && commands[0][1] === 4, + 'should have generated the command 4 (LINK_TO) with id 4'); + assert.ok(commands[1][0] === 3 && commands[1][1] === 1, + 'should have generated the command 3 (UNLINK) with id 1'); + assert.ok(commands[2][0] === 3 && commands[2][1] === 2, + 'should have generated the command 3 (UNLINK) with id 2'); + } + return this._super.apply(this, arguments); + }, + archs: { + 'partner,false,form': + '
', + }, + }); + form.$buttons.find('.o_form_button_edit').click(); + + assert.strictEqual(form.$('td.o_list_record_remove button').length, 3, + "should have 3 remove buttons"); + + assert.ok(form.$('td.o_list_record_remove button').first().hasClass('fa fa-times'), + "should have X icons to remove (unlink) records"); + + form.$('td.o_list_record_remove button').first().click(); + + assert.strictEqual(form.$('td.o_list_record_remove button').length, 2, + "should have 2 remove buttons (a record is supposed to have been unlinked)"); + + form.$('tr.o_data_row').first().click(); + assert.strictEqual($('.modal .modal-footer .o_btn_remove').length, 1, + 'there should be a modal having Remove Button'); + $('.modal .modal-footer .o_btn_remove').click(); + + assert.strictEqual(form.$('td.o_list_record_remove button').length, 1, + "should have 1 delete button (another record is supposed to have been unlinked)"); + + // save and check that the correct command has been generated + form.$buttons.find('.o_form_button_save').click(); + form.destroy(); + }); + + QUnit.test('one2many list: deleting two records', function (assert) { + assert.expect(9); + this.data.partner.records[0].p = [1, 2, 4]; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/partner/write') { + var commands = args.args[1].p; + assert.strictEqual(commands.length, 3, + 'should have generated three commands'); + assert.ok(commands[0][0] === 4 && commands[0][1] === 4, + 'should have generated the command 4 (LINK_TO) with id 4'); + assert.ok(commands[1][0] === 2 && commands[1][1] === 1, + 'should have generated the command 2 (DELETE) with id 1'); + assert.ok(commands[2][0] === 2 && commands[2][1] === 2, + 'should have generated the command 2 (DELETE) with id 2'); + } + return this._super.apply(this, arguments); + }, + archs: { + 'partner,false,form': + '
', + }, + }); + form.$buttons.find('.o_form_button_edit').click(); + + assert.strictEqual(form.$('td.o_list_record_remove button').length, 3, + "should have 3 remove buttons"); + + assert.ok(form.$('td.o_list_record_remove button').first().hasClass('fa fa-trash-o'), + "should have trash bin icons to remove (delete) records"); + + form.$('td.o_list_record_remove button').first().click(); + + assert.strictEqual(form.$('td.o_list_record_remove button').length, 2, + "should have 2 remove buttons (a record is supposed to have been deleted)"); + + form.$('tr.o_data_row').first().click(); + assert.strictEqual($('.modal .modal-footer .o_btn_remove').length, 1, + 'there should be a modal having Remove Button'); + $('.modal .modal-footer .o_btn_remove').click(); + + assert.strictEqual(form.$('td.o_list_record_remove button').length, 1, + "should have 1 remove button (another record is supposed to have been deleted)"); + + // save and check that the correct command has been generated + form.$buttons.find('.o_form_button_save').click(); + + // FIXME: it would be nice to test that the view is re-rendered correctly, + // but as the relational data isn't re-fetched, the rendering is ok even + // if the changes haven't been saved + form.destroy(); + }); + + QUnit.test('one2many kanban: edition', function (assert) { + assert.expect(17); + + this.data.partner.records[0].p = [2]; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + // color will be in the kanban but not in the form + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + // foo will be in the form but not in the kanban + '' + + '' + + '' + + '', + res_id: 1, + }); + + assert.ok(!form.$('.o_kanban_view .delete_icon').length, + 'delete icon should not be visible in readonly'); + assert.ok(!form.$('.o_field_one2many .o-kanban-button-new').length, + '"Create" button should not be visible in readonly'); + + form.$buttons.find('.o_form_button_edit').click(); + + assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 1, + 'should contain 1 record'); + assert.strictEqual(form.$('.o_kanban_record span:first').text(), 'second record', + 'display_name of subrecord should be the one in DB'); + assert.strictEqual(form.$('.o_kanban_record span:nth(1)').text(), 'Red', + 'color of subrecord should be the one in DB'); + assert.ok(form.$('.o_kanban_view .delete_icon').length, + 'delete icon should be visible in edit'); + assert.ok(form.$('.o_field_one2many .o-kanban-button-new').length, + '"Create" button should be visible in edit'); + assert.ok(form.$('.o_field_one2many .o-kanban-button-new').hasClass('btn-secondary'), + "'Create' button should have className 'btn-secondary'"); + assert.strictEqual(form.$('.o_field_one2many .o-kanban-button-new').text().trim(), "Add", + 'Create button should have "Add" label'); + + // edit existing subrecord + form.$('.oe_kanban_global_click').click(); + + $('.modal .o_form_view input').val('new name').trigger('input'); + $('.modal .modal-footer .btn-primary').click(); // save + assert.strictEqual(form.$('.o_kanban_record span:first').text(), 'new name', + 'value of subrecord should have been updated'); + + // create a new subrecord + form.$('.o-kanban-button-new').click(); + $('.modal .o_form_view input').val('new subrecord 1').trigger('input'); + $('.modal .modal-footer .btn-primary').click(); // save and close + assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 2, + 'should contain 2 records'); + assert.strictEqual(form.$('.o_kanban_record:nth(1) span').text(), 'new subrecord 1', + 'value of newly created subrecord should be "new subrecord 1"'); + + // create two new subrecords + form.$('.o-kanban-button-new').click(); + $('.modal .o_form_view input').val('new subrecord 2').trigger('input'); + $('.modal .modal-footer .btn-primary:nth(1)').click(); // save and new + $('.modal .o_form_view input').val('new subrecord 3').trigger('input'); + $('.modal .modal-footer .btn-primary').click(); // save and close + assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 4, + 'should contain 4 records'); + + // delete subrecords + form.$('.oe_kanban_global_click').first().click(); + assert.strictEqual($('.modal .modal-footer .o_btn_remove').length, 1, + 'There should be a modal having Remove Button'); + $('.modal .modal-footer .o_btn_remove').click(); // Click Remove Button of Formview Dialog + assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 3, + 'should contain 3 records'); + form.$('.o_kanban_view .delete_icon:first()').click(); + form.$('.o_kanban_view .delete_icon:first()').click(); + assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 1, + 'should contain 1 records'); + assert.strictEqual(form.$('.o_kanban_record span:first').text(), 'new subrecord 3', + 'the remaining subrecord should be "new subrecord 3"'); + form.destroy(); + }); + + QUnit.test('one2many kanban (editable): properly handle create_text node option', function (assert) { + assert.expect(1); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '
' + + '' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
', + res_id: 1, + }); + + form.$buttons.find('.o_form_button_edit').click(); + assert.strictEqual(form.$('.o_field_one2many[name="turtles"] .o-kanban-button-new').text().trim(), + "Add turtle", "In O2M Kanban, Add button should have 'Add turtle' label"); + + form.destroy(); + }); + + QUnit.test('one2many kanban: create action disabled', function (assert) { + assert.expect(3); + + this.data.partner.records[0].p = [4]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + res_id: 1, + }); + + assert.ok(!form.$('.o-kanban-button-new').length, + '"Add" button should not be available in readonly'); + + form.$buttons.find('.o_form_button_edit').click(); + + assert.ok(!form.$('.o-kanban-button-new').length, + '"Add" button should not be available in edit'); + assert.ok(form.$('.o_kanban_view .delete_icon').length, + 'delete icon should be visible in edit'); + form.destroy(); + }); + + QUnit.test('editable one2many list, pager is updated', function (assert) { + assert.expect(1); + + this.data.turtle.records.push({ id: 4, turtle_foo: 'stephen hawking' }); + this.data.partner.records[0].turtles = [1, 2, 3, 4]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + // add a record, then click in form view to confirm it + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + form.$el.click(); + + assert.strictEqual(form.$('.o_cp_pager').text().trim(), '1-3 / 5', + "pager should display the correct total"); + form.destroy(); + }); + + QUnit.test('one2many list (non editable): edition', function (assert) { + assert.expect(12); + + var nbWrite = 0; + this.data.partner.records[0].p = [2, 4]; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'write') { + nbWrite++; + assert.deepEqual(args.args[1], { + p: [[1, 2, { display_name: 'new name' }], [2, 4, false]] + }, "should have sent the correct commands"); + } + return this._super.apply(this, arguments); + }, + }); + + assert.ok(!form.$('.o_list_record_remove').length, + 'remove icon should not be visible in readonly'); + assert.ok(!form.$('.o_field_x2many_list_row_add').length, + '"Add an item" should not be visible in readonly'); + + form.$buttons.find('.o_form_button_edit').click(); + + assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 2, + 'should contain 2 records'); + assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'second record', + 'display_name of first subrecord should be the one in DB'); + assert.ok(form.$('.o_list_record_remove').length, + 'remove icon should be visible in edit'); + assert.ok(form.$('.o_field_x2many_list_row_add').length, + '"Add an item" should not visible in edit'); + + // edit existing subrecord + form.$('.o_list_view tbody tr:first() td:eq(1)').click(); + + $('.modal .o_form_view input').val('new name').trigger('input'); + $('.modal .modal-footer .btn-primary').click(); // save + assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name', + 'value of subrecord should have been updated'); + assert.strictEqual(nbWrite, 0, "should not have write anything in DB"); + + // create new subrecords + // TODO when 'Add an item' will be implemented + + // remove subrecords + form.$('.o_list_record_remove:nth(1)').click(); + assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 1, + 'should contain 1 subrecord'); + assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name', + 'the remaining subrecord should be "new name"'); + + form.$buttons.find('.o_form_button_save').click(); // save the record + assert.strictEqual(nbWrite, 1, "should have write the changes in DB"); + + form.destroy(); + }); + + QUnit.test('one2many list (editable): edition', function (assert) { + assert.expect(7); + + this.data.partner.records[0].p = [2, 4]; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + res_id: 1, + }); + + assert.ok(!form.$('.o_field_x2many_list_row_add').length, + '"Add an item" link should not be available in readonly'); + + form.$('.o_list_view tbody td:first()').click(); + assert.ok($('.modal .o_form_readonly').length, + 'in readonly, clicking on a subrecord should open it in readonly in a dialog'); + $('.modal .o_form_button_cancel').click(); // close the dialog + + form.$buttons.find('.o_form_button_edit').click(); + + assert.ok(form.$('.o_field_x2many_list_row_add').length, + '"Add an item" link should be available in edit'); + + // edit existing subrecord + form.$('.o_list_view tbody td:first()').click(); + assert.strictEqual($('.modal').length, 0, + 'in edit, clicking on a subrecord should not open a dialog'); + assert.ok(form.$('.o_list_view tbody tr:first()').hasClass('o_selected_row'), + 'first row should be in edition'); + form.$('.o_list_view input:first()').val('new name').trigger('input'); + + form.$('.o_list_view tbody tr:nth(1) td:first').click(); // click on second record to validate the first one + assert.ok(!form.$('.o_list_view tbody tr:first').hasClass('o_selected_row'), + 'first row should not be in edition anymore'); + assert.strictEqual(form.$('.o_list_view tbody td:first').text(), 'new name', + 'value of subrecord should have been updated'); + + // create new subrecords + // TODO when 'Add an item' will be implemented + form.destroy(); + }); + + QUnit.test('one2many list (editable): edition, part 2', function (assert) { + assert.expect(8); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'write') { + assert.strictEqual(args.args[1].p[0][0], 0, + "should send a 0 command for field p"); + assert.strictEqual(args.args[1].p[1][0], 0, + "should send a second 0 command for field p"); + } + return this._super.apply(this, arguments); + }, + }); + + // edit mode, then click on Add an item and enter a value + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + form.$('.o_selected_row > td input').val('kartoffel').trigger('input'); + + // click again on Add an item + form.$('.o_field_x2many_list_row_add a').click(); + assert.strictEqual(form.$('td:contains(kartoffel)').length, 1, + "should have one td with the new value"); + assert.strictEqual(form.$('.o_selected_row > td input').length, 1, + "should have one other new td"); + assert.strictEqual(form.$('tr.o_data_row').length, 2, "should have 2 data rows"); + + // enter another value and save + form.$('.o_selected_row > td input').val('gemuse').trigger('input'); + form.$buttons.find('.o_form_button_save').click(); + assert.strictEqual(form.$('tr.o_data_row').length, 2, "should have 2 data rows"); + assert.strictEqual(form.$('td:contains(kartoffel)').length, 1, + "should have one td with the new value"); + assert.strictEqual(form.$('td:contains(gemuse)').length, 1, + "should have one td with the new value"); + + form.destroy(); + }); + + QUnit.test('one2many list (editable): edition, part 3', function (assert) { + assert.expect(3); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + // edit mode, then click on Add an item 2 times + assert.strictEqual(form.$('tr.o_data_row').length, 1, + "should have 1 data rows"); + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + form.$('.o_field_x2many_list_row_add a').click(); + assert.strictEqual(form.$('tr.o_data_row').length, 3, + "should have 3 data rows"); + + // cancel the edition + form.$buttons.find('.o_form_button_cancel').click(); + $('.modal-footer button.btn-primary').first().click(); + assert.strictEqual(form.$('tr.o_data_row').length, 1, + "should have 1 data rows"); + + form.destroy(); + }); + + QUnit.test('one2many list (editable): edition, part 4', function (assert) { + assert.expect(3); + var i = 0; + + this.data.turtle.onchanges = { + turtle_trululu: function (obj) { + if (i) { + obj.turtle_description = "Some Description"; + } + i++; + }, + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 2, + }); + + // edit mode, then click on Add an item + assert.strictEqual(form.$('tr.o_data_row').length, 0, + "should have 0 data rows"); + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + assert.strictEqual(form.$('textarea').val(), "", + "field turtle_description should be empty"); + + // add a value in the turtle_trululu field to trigger an onchange + var $dropdown = form.$('.o_field_many2one[name=turtle_trululu] input') + .autocomplete('widget'); + form.$('.o_field_many2one[name=turtle_trululu] input').click(); + $dropdown.find('a:contains(first record)').mouseenter().click(); + assert.strictEqual(form.$('textarea').val(), "Some Description", + "field turtle_description should be set to the result of the onchange"); + form.destroy(); + }); + + QUnit.test('one2many list (editable): discarding required empty data', function (assert) { + assert.expect(7); + + this.data.turtle.fields.turtle_foo.required = true; + delete this.data.turtle.fields.turtle_foo.default; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 2, + mockRPC: function (route, args) { + if (args.method) { + assert.step(args.method); + } + return this._super.apply(this, arguments); + }, + }); + + // edit mode, then click on Add an item, then click elsewhere + assert.strictEqual(form.$('tr.o_data_row').length, 0, + "should have 0 data rows"); + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + form.$('label.o_form_label').first().click(); + assert.strictEqual(form.$('tr.o_data_row').length, 0, + "should still have 0 data rows"); + + // click on Add an item again, then click on save + form.$('.o_field_x2many_list_row_add a').click(); + form.$buttons.find('.o_form_button_save').click(); + assert.strictEqual(form.$('tr.o_data_row').length, 0, + "should still have 0 data rows"); + + assert.verifySteps(['read', 'default_get', 'default_get']); + form.destroy(); + }); + + QUnit.test('editable one2many list, adding line when only one page', function (assert) { + assert.expect(1); + + this.data.partner.records[0].turtles = [1, 2, 3]; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + // add a record, to reach the page size limit + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + + // the record currently being added should not count in the pager + assert.ok(!form.$('.o_cp_pager').is(':visible'), + "pager should not be visible"); + form.destroy(); + }); + + QUnit.test('editable one2many list, adding line, then discarding', function (assert) { + assert.expect(1); + + this.data.turtle.records.push({ id: 4, turtle_foo: 'stephen hawking' }); + this.data.partner.records[0].turtles = [1, 2, 3, 4]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + // add a record, then discard + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + form.$buttons.find('.o_form_button_cancel').click(); + + // confirm the discard operation + $('.modal .modal-footer .btn-primary').click(); // click on confirm + + assert.strictEqual(form.$('.o_cp_pager').text().trim(), '1-3 / 4', + "pager should still be visible"); + form.destroy(); + }); + + QUnit.test('editable one2many list, required field and pager', function (assert) { + assert.expect(1); + + this.data.turtle.records.push({ id: 4, turtle_foo: 'stephen hawking' }); + this.data.turtle.fields.turtle_foo.required = true; + this.data.partner.records[0].turtles = [1, 2, 3, 4]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + // add a (empty) record + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + + // go on next page. The new record is not valid and should be discarded + form.$('.o_pager_next').click(); + assert.strictEqual(form.$('tr.o_data_row').length, 1, + "should have 1 data rows"); + + form.destroy(); + }); + + QUnit.test('editable one2many list, required field, pager and confirm discard', function (assert) { + assert.expect(3); + + this.data.turtle.records.push({ id: 4, turtle_foo: 'stephen hawking' }); + this.data.turtle.fields.turtle_foo.required = true; + this.data.partner.records[0].turtles = [1, 2, 3, 4]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + // add a record with a dirty state, but not valid + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + form.$('input[name="turtle_int"]').val(4321).trigger('input'); + + // go to next page. The new record is not valid, but dirty. we should + // see a confirm dialog + form.$('.o_pager_next').click(); + + assert.strictEqual(form.$('.o_cp_pager').text().trim(), '1-3 / 4', + "pager should still display the correct total"); + + // click on cancel + $('.modal .modal-footer .btn-secondary').click(); // click on cancel + + assert.strictEqual(form.$('.o_cp_pager').text().trim(), '1-3 / 4', + "pager should again display the correct total"); + assert.strictEqual(form.$('.o_field_one2many input.o_field_invalid').length, 1, + "there should be an invalid input in the one2many"); + form.destroy(); + }); + + QUnit.test('editable one2many list, adding, discarding, and pager', function (assert) { + assert.expect(2); + + this.data.partner.records[0].turtles = [1]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + // add a 4 records record (to make the pager appear) + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + form.$('.o_field_x2many_list_row_add a').click(); + form.$('.o_field_x2many_list_row_add a').click(); + form.$('.o_field_x2many_list_row_add a').click(); + + // go on next page + form.$('.o_pager_next').click(); + + // discard + form.$buttons.find('.o_form_button_cancel').click(); + $('.modal .modal-footer .btn-primary').click(); + + assert.strictEqual(form.$('tr.o_data_row').length, 1, + "should have 1 data row"); + assert.ok(!form.$('.o_cp_pager').is(':visible'), + "pager should not be visible"); + + form.destroy(); + }); + + QUnit.test('unselecting a line with missing required data', function (assert) { + assert.expect(5); + + this.data.turtle.fields.turtle_foo.required = true; + delete this.data.turtle.fields.turtle_foo.default; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 2, + }); + + // edit mode, then click on Add an item, then click elsewhere + assert.strictEqual(form.$('tr.o_data_row').length, 0, + "should have 0 data rows"); + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + assert.strictEqual(form.$('tr.o_data_row').length, 1, + "should have 1 data rows"); + + // adding a value in the non required field, so it is dirty, but with + // a missing required field + form.$('input[name="turtle_int"]').val('12345').trigger('input'); + + // click elsewhere, + form.$('label.o_form_label').click(); + assert.strictEqual($('.modal').length, 1, + 'a confirmation model should be opened'); + + // click on cancel, the line should still be selected + $('.modal .modal-footer button.btn-secondary').click(); + assert.strictEqual(form.$('tr.o_data_row.o_selected_row').length, 1, + "should still have 1 selected data row"); + + // click elsewhere, and click on ok (on the confirmation dialog) + form.$('label.o_form_label').click(); + $('.modal .modal-footer button.btn-primary').click(); + assert.strictEqual(form.$('tr.o_data_row').length, 0, + "should have 0 data rows (invalid line has been discarded"); + + form.destroy(); + }); + + QUnit.test('pressing enter in a o2m with a required empty field', function (assert) { + assert.expect(4); + + this.data.turtle.fields.turtle_foo.required = true; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 2, + mockRPC: function (route, args) { + assert.step(args.method); + return this._super.apply(this, arguments); + }, + }); + + // edit mode, then click on Add an item, then click elsewhere + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + form.$('input[name="turtle_foo"]').trigger($.Event('keydown', { + which: $.ui.keyCode.ENTER, + keyCode: $.ui.keyCode.ENTER, + })); + assert.ok(form.$('input[name="turtle_foo"]').hasClass('o_field_invalid'), + "input should be marked invalid"); + assert.verifySteps(['read', 'default_get']); + form.destroy(); + }); + + QUnit.test('editing a o2m, with required field and onchange', function (assert) { + assert.expect(12); + + this.data.turtle.fields.turtle_foo.required = true; + delete this.data.turtle.fields.turtle_foo.default; + this.data.turtle.onchanges = { + turtle_foo: function (obj) { + obj.turtle_int = obj.turtle_foo.length; + }, + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 2, + mockRPC: function (route, args) { + if (args.method) { + assert.step(args.method); + } + return this._super.apply(this, arguments); + }, + }); + + // edit mode, then click on Add an item + assert.strictEqual(form.$('tr.o_data_row').length, 0, + "should have 0 data rows"); + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + + // input some text in required turtle_foo field + form.$('input[name="turtle_foo"]').val('aubergine').trigger('input'); + assert.strictEqual(form.$('input[name="turtle_int"]').val(), "9", + "onchange should have been triggered"); + + // save and check everything is fine + form.$buttons.find('.o_form_button_save').click(); + assert.strictEqual(form.$('.o_data_row td:contains(aubergine)').length, 1, + "should have one row with turtle_foo value"); + assert.strictEqual(form.$('.o_data_row td:contains(9)').length, 1, + "should have one row with turtle_int value"); + + assert.verifySteps(['read', 'default_get', 'onchange', 'onchange', 'write', 'read', 'read']); + form.destroy(); + }); + + QUnit.test('editable o2m, pressing ESC discard current changes', function (assert) { + assert.expect(5); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 2, + mockRPC: function (route, args) { + assert.step(args.method); + return this._super.apply(this, arguments); + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + assert.strictEqual(form.$('tr.o_data_row').length, 1, + "there should be one data row"); + + form.$('input[name="turtle_foo"]').trigger({ type: 'keydown', which: $.ui.keyCode.ESCAPE }); + assert.strictEqual(form.$('tr.o_data_row').length, 0, + "data row should have been discarded"); + assert.verifySteps(['read', 'default_get']); + form.destroy(); + }); + + QUnit.test('editable o2m with required field, pressing ESC discard current changes', function (assert) { + assert.expect(5); + + this.data.turtle.fields.turtle_foo.required = true; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 2, + mockRPC: function (route, args) { + assert.step(args.method); + return this._super.apply(this, arguments); + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + assert.strictEqual(form.$('tr.o_data_row').length, 1, + "there should be one data row"); + + form.$('input[name="turtle_foo"]').trigger({ type: 'keydown', which: $.ui.keyCode.ESCAPE }); + assert.strictEqual(form.$('tr.o_data_row').length, 0, + "data row should have been discarded"); + assert.verifySteps(['read', 'default_get']); + form.destroy(); + }); + + QUnit.test('pressing escape in editable o2m list in dialog', function (assert) { + assert.expect(3); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + archs: { + "partner,false,form": '
' + + '' + + '' + + '' + + '' + + '' + + '
', + }, + viewOptions: { + mode: 'edit', + }, + }); + + form.$('.o_field_x2many_list_row_add a').click(); + $('.modal .o_field_x2many_list_row_add a').click(); + + assert.strictEqual($('.modal .o_data_row.o_selected_row').length, 1, + "there should be a row in edition in the dialog"); + + // trigger keydown ESCAPE in the edited row + $('.modal .o_data_cell input').trigger({ type: 'keydown', which: $.ui.keyCode.ESCAPE }); + + assert.strictEqual($('.modal').length, 1, + "dialog should still be open"); + assert.strictEqual($('.modal .o_data_row').length, 0, + "the row should have been removed"); + + form.destroy(); + }); + + QUnit.test('editable o2m with onchange and required field: delete an invalid line', function (assert) { + assert.expect(5); + + this.data.partner.onchanges = { + turtles: function () { }, + }; + this.data.partner.records[0].turtles = [1]; + this.data.turtle.records[0].product_id = 37; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + mockRPC: function (route, args) { + assert.step(args.method); + return this._super.apply(this, arguments); + }, + viewOptions: { + mode: 'edit', + }, + }); + + form.$('.o_data_cell:first').click(); + form.$('.o_field_widget[name="product_id"] input').val('').trigger('keyup'); + assert.verifySteps(['read', 'read'], 'no onchange should be done as line is invalid'); + form.$('.o_list_record_remove').click(); + assert.verifySteps(['read', 'read', 'onchange'], 'onchange should have been done'); + + form.destroy(); + }); + + QUnit.test('onchange in a one2many', function (assert) { + assert.expect(1); + + this.data.partner.records.push({ + id: 3, + foo: "relational record 1", + }); + this.data.partner.records[1].p = [3]; + this.data.partner.onchanges = { p: true }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 2, + mockRPC: function (route, args) { + if (args.method === 'onchange') { + return $.when({ + value: { + p: [ + [5], // delete all + [0, 0, { foo: "from onchange" }], // create new + ] + } + }); + } + return this._super(route, args); + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_one2many tbody td').first().click(); + form.$('.o_field_one2many tbody td').first().find('input').val("new value").trigger('input'); + form.$buttons.find('.o_form_button_save').click(); + + assert.strictEqual(form.$('.o_field_one2many tbody td').first().text(), 'from onchange', + "display name of first record in o2m list should be 'new value'"); + form.destroy(); + }); + + QUnit.test('one2many, default_get and onchange (basic)', function (assert) { + assert.expect(1); + + this.data.partner.fields.p.default = [ + [6, 0, []], // replace with zero ids + ]; + this.data.partner.onchanges = { p: true }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + mockRPC: function (route, args) { + if (args.method === 'onchange') { + return $.when({ + value: { + p: [ + [5], // delete all + [0, 0, { foo: "from onchange" }], // create new + ] + } + }); + } + return this._super(route, args); + }, + }); + + assert.ok(form.$('td:contains(from onchange)').length, + "should have 'from onchange' value in one2many"); + form.destroy(); + }); + + QUnit.test('one2many and default_get (with date)', function (assert) { + assert.expect(1); + + this.data.partner.fields.p.default = [ + [0, false, { date: '2017-10-08' }], + ]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + }); + + assert.strictEqual(form.$('.o_data_cell').text(), '10/08/2017', + "should correctly display the date"); + + form.destroy(); + }); + + QUnit.test('one2many and onchange (with integer)', function (assert) { + assert.expect(4); + + this.data.turtle.onchanges = { + turtle_int: function (obj) { } + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + mockRPC: function (route, args) { + assert.step(args.method); + return this._super.apply(this, arguments); + }, + }); + form.$buttons.find('.o_form_button_edit').click(); + + form.$('td:contains(9)').click(); + form.$('td input[name="turtle_int"]').val("3").trigger('input'); + + // the 'change' event is triggered on the input when we focus somewhere + // else, for example by clicking in the body. However, if we try to + // programmatically click in the body, it does not trigger a change + // event, so we simply trigger it directly instead. + form.$('td input[name="turtle_int"]').trigger('change'); + + assert.verifySteps(['read', 'read', 'onchange']); + form.destroy(); + }); + + QUnit.test('one2many and onchange (with date)', function (assert) { + assert.expect(7); + + this.data.partner.onchanges = { + date: function (obj) { } + }; + this.data.partner.records[0].p = [2]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + mockRPC: function (route, args) { + assert.step(args.method); + return this._super.apply(this, arguments); + }, + }); + form.$buttons.find('.o_form_button_edit').click(); + + form.$('td:contains(01/25/2017)').click(); + form.$('.o_datepicker_input').click(); + $('.bootstrap-datetimepicker-widget .picker-switch').first().click(); // Month selection + $('.bootstrap-datetimepicker-widget .picker-switch').first().click(); // Year selection + $('.bootstrap-datetimepicker-widget .year:contains(2017)').click(); + $('.bootstrap-datetimepicker-widget .month').eq(1).click(); // February + $('.day:contains(22)').click(); // select the 22 February + + form.$buttons.find('.o_form_button_save').click(); + + assert.verifySteps(['read', 'read', 'onchange', 'write', 'read', 'read']); + form.destroy(); + }); + + QUnit.test('one2many and onchange (with command DELETE_ALL)', function (assert) { + assert.expect(5); + + this.data.partner.onchanges = { + foo: function (obj) { + obj.p = [[5]]; + }, + p: function () { }, // dummy onchange on the o2m to execute _isX2ManyValid() + }; + this.data.partner.records[0].p = [2]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + mockRPC: function (method, args) { + if (args.method === 'write') { + assert.deepEqual(args.args[1].p, [ + [0, args.args[1].p[0][1], { display_name: 'z' }], + [2, 2, false], + ], "correct commands should be sent"); + } + return this._super.apply(this, arguments); + }, + res_id: 1, + viewOptions: { + mode: 'edit', + }, + }); + + assert.strictEqual(form.$('.o_data_row').length, 1, + "o2m should contain one row"); + + // empty o2m by triggering the onchange + form.$('.o_field_widget[name=foo]').val('trigger onchange').trigger('input'); + + assert.strictEqual(form.$('.o_data_row').length, 0, + "rows of the o2m should have been deleted"); + + // add two new subrecords + form.$('.o_field_x2many_list_row_add a').click(); + form.$('.o_field_widget[name=display_name]').val('x').trigger('input'); + form.$('.o_field_x2many_list_row_add a').click(); + form.$('.o_field_widget[name=display_name]').val('y').trigger('input'); + + assert.strictEqual(form.$('.o_data_row').length, 2, + "o2m should contain two rows"); + + // empty o2m by triggering the onchange + form.$('.o_field_widget[name=foo]').val('trigger onchange again').trigger('input'); + + assert.strictEqual(form.$('.o_data_row').length, 0, + "rows of the o2m should have been deleted"); + + form.$('.o_field_x2many_list_row_add a').click(); + form.$('.o_field_widget[name=display_name]').val('z').trigger('input'); + + form.$buttons.find('.o_form_button_save').click(); + form.destroy(); + }); + + QUnit.test('one2many and onchange only write modified field', function (assert) { + assert.expect(2); + + this.data.partner.onchanges = { + turtles: function (obj) { + obj.turtles = [ + [5], // delete all + [1, 3, { // the server returns all fields + display_name: "coucou", + product_id: [37, "xphone"], + turtle_bar: false, + turtle_foo: "has changed", + turtle_int: 42, + turtle_qux: 9.8, + partner_ids: [], + turtle_ref: 'product,37', + }], + ]; + }, + }; + + this.data.partner.records[0].turtles = [3]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + mockRPC: function (method, args) { + if (args.method === 'write') { + assert.deepEqual(args.args[1].turtles, [ + [1, 3, { display_name: 'coucou', turtle_foo: 'has changed', turtle_int: 42 }], + ], "correct commands should be sent (only send changed values)"); + } + return this._super.apply(this, arguments); + }, + res_id: 1, + viewOptions: { + mode: 'edit', + }, + }); + + assert.strictEqual(form.$('.o_data_row').length, 1, + "o2m should contain one row"); + + form.$('.o_field_one2many .o_list_view tbody tr:first td:first').click(); + form.$('.o_field_one2many .o_list_view tbody tr:first input:first').val('blurp').trigger('input'); + + form.$buttons.find('.o_form_button_save').click(); + form.destroy(); + }); + + QUnit.test('one2many with CREATE onchanges correctly refreshed', function (assert) { + assert.expect(5); + + var delta = 0; + testUtils.patch(AbstractField, { + init: function () { + delta++; + this._super.apply(this, arguments); + }, + destroy: function () { + delta--; + this._super.apply(this, arguments); + }, + }); + + var deactiveOnchange = true; + + this.data.partner.records[0].turtles = []; + this.data.partner.onchanges = { + turtles: function (obj) { + if (deactiveOnchange) { return; } + // the onchange will either: + // - create a second line if there is only one line + // - edit the second line if there are two lines + if (obj.turtles.length === 1) { + obj.turtles = [ + [5], // delete all + [0, obj.turtles[0][1], { + display_name: "first", + turtle_int: obj.turtles[0][2].turtle_int, + }], + [0, 0, { + display_name: "second", + turtle_int: -obj.turtles[0][2].turtle_int, + }], + ]; + } else if (obj.turtles.length === 2) { + obj.turtles = [ + [5], // delete all + [0, obj.turtles[0][1], { + display_name: "first", + turtle_int: obj.turtles[0][2].turtle_int, + }], + [0, obj.turtles[1][1], { + display_name: "second", + turtle_int: -obj.turtles[0][2].turtle_int, + }], + ]; + } + }, + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + res_id: 1, + viewOptions: { + mode: 'edit', + }, + }); + + assert.strictEqual(form.$('.o_data_row').length, 0, + "o2m shouldn't contain any row"); + + form.$('.o_field_x2many_list_row_add a').click(); + // trigger the first onchange + deactiveOnchange = false; + form.$('input[name="turtle_int"]').val('10').trigger('input'); + // put the list back in non edit mode + form.$('input[name="foo"]').click(); + assert.strictEqual(form.$('.o_data_row').text(), "first10second-10", + "should correctly refresh the records"); + + // trigger the second onchange + form.$('.o_field_x2many_list tbody tr:first td:first').click(); + form.$('input[name="turtle_int"]').val('20').trigger('input'); + + form.$('input[name="foo"]').click(); + assert.strictEqual(form.$('.o_data_row').text(), "first20second-20", + "should correctly refresh the records"); + + assert.strictEqual(form.$('.o_field_widget').length, delta, + "all (non visible) field widgets should have been destroyed"); + + form.$buttons.find('.o_form_button_save').click(); + + assert.strictEqual(form.$('.o_data_row').text(), "first20second-20", + "should correctly refresh the records after save"); + + form.destroy(); + testUtils.unpatch(AbstractField); + }); + + QUnit.test('editable one2many with sub widgets are rendered in readonly', function (assert) { + assert.expect(2); + + var editableWidgets = 0; + testUtils.patch(AbstractField, { + init: function () { + this._super.apply(this, arguments); + if (this.mode === 'edit') { + editableWidgets++; + } + }, + }); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + viewOptions: { + mode: 'edit', + }, + }); + + assert.strictEqual(editableWidgets, 1, + "o2m is only widget in edit mode"); + form.$('tbody td.o_field_x2many_list_row_add a').click(); + + assert.strictEqual(editableWidgets, 3, + "3 widgets currently in edit mode"); + + form.destroy(); + testUtils.unpatch(AbstractField); + }); + + QUnit.test('one2many editable list with onchange keeps the order', function (assert) { + assert.expect(2); + + this.data.partner.records[0].p = [1, 2, 4]; + this.data.partner.onchanges = { + p: function () { }, + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + viewOptions: { + mode: 'edit', + }, + }); + + assert.strictEqual(form.$('.o_data_cell').text(), 'first recordsecond recordaaa', + "records should be display in the correct order"); + + form.$('.o_data_row:first .o_data_cell').click(); + form.$('.o_selected_row .o_field_widget[name=display_name]').val('new').trigger('input'); + form.$el.click(); // click outside to validate the row + + assert.strictEqual(form.$('.o_data_cell').text(), 'newsecond recordaaa', + "records should be display in the correct order"); + + form.destroy(); + }); + + QUnit.test('one2many list (editable): readonly domain is evaluated', function (assert) { + assert.expect(2); + + this.data.partner.records[0].p = [2, 4]; + this.data.partner.records[1].product_id = false; + this.data.partner.records[2].product_id = 37; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + form.$buttons.find('.o_form_button_edit').click(); + + assert.ok(form.$('.o_list_view tbody tr:eq(0) td:first').hasClass('o_readonly_modifier'), + "first record should have display_name in readonly mode"); + + assert.notOk(form.$('.o_list_view tbody tr:eq(1) td:first').hasClass('o_readonly_modifier'), + "second record should not have display_name in readonly mode"); + form.destroy(); + }); + + QUnit.test('pager of one2many field in new record', function (assert) { + assert.expect(2); + + this.data.partner.records[0].p = []; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + archs: { + 'partner,false,form': + '
', + }, + }); + + assert.ok(!form.$('.o_x2m_control_panel .o_cp_pager div').is(':visible'), + 'o2m pager should be hidden'); + + // click to create a subrecord + form.$('tbody td.o_field_x2many_list_row_add a').click(); + $('.modal input').val('new record').trigger('input'); + $('.modal .modal-footer button:eq(0)').click(); // save and close + + assert.ok(!form.$('.o_x2m_control_panel .o_cp_pager div').is(':visible'), + 'o2m pager should be hidden'); + form.destroy(); + }); + + QUnit.test('one2many list with a many2one', function (assert) { + assert.expect(5); + + this.data.partner.records[0].p = [2]; + this.data.partner.records[1].product_id = 37; + this.data.partner.onchanges.p = function (obj) { + obj.p = [ + [5], // delete all + [1, 2, { product_id: [37, "xphone"] }], // update existing record + [0, 0, { product_id: [41, "xpad"] }] + ]; + // + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + archs: { + 'partner,false,form': + '
', + }, + mockRPC: function (route, args) { + if (args.method === 'onchange') { + assert.deepEqual(args.args[1].p, [[4, 2, false], [0, args.args[1].p[1][1], { product_id: 41 }]], + "should trigger onchange with correct parameters"); + } + return this._super.apply(this, arguments); + } + }); + + assert.strictEqual(form.$('tbody td:contains(xphone)').length, 1, + "should have properly fetched the many2one nameget"); + assert.strictEqual(form.$('tbody td:contains(xpad)').length, 0, + "should not display 'xpad' anywhere"); + + form.$buttons.find('.o_form_button_edit').click(); + + form.$('tbody td.o_field_x2many_list_row_add a').click(); + + $('.modal .o_field_many2one input').click(); + + var $dropdown = $('.modal .o_field_many2one input').autocomplete('widget'); + + $dropdown.find('li:eq(1) a').mouseenter(); + $dropdown.find('li:eq(1) a').click(); + + $('.modal .modal-footer button:eq(0)').click(); // save and close + + assert.strictEqual(form.$('tbody td:contains(xpad)').length, 1, + "should display 'xpad' on a td"); + assert.strictEqual(form.$('tbody td:contains(xphone)').length, 1, + "should still display xphone"); + form.destroy(); + }); + + QUnit.test('one2many list with inline form view', function (assert) { + assert.expect(5); + + this.data.partner.records[0].p = []; + + var rpcCount = 0; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + // don't remove this, it is + // useful to make sure the foo fieldwidget + // does not crash because the foo field + // is not in the form view + '' + + '' + + '', + res_id: 1, + mockRPC: function (route, args) { + rpcCount++; + if (args.method === 'write') { + assert.deepEqual(args.args[1].p, [[0, args.args[1].p[0][1], { + foo: false, int_field: 123, product_id: 41, + }]]); + } + return this._super(route, args); + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + + form.$('tbody td.o_field_x2many_list_row_add a').click(); + + // write in the many2one field, value = 37 (xphone) + $('.modal .o_field_many2one input').click(); + var $dropdown = $('.modal .o_field_many2one input').autocomplete('widget'); + $dropdown.find('li:eq(0) a').mouseenter(); + $dropdown.find('li:eq(0) a').click(); + + // write in the integer field + $('.modal .modal-body input.o_field_widget').val('123').trigger('input'); + + // save and close + $('.modal .modal-footer button:eq(0)').click(); + + assert.strictEqual(form.$('tbody td:contains(xphone)').length, 1, + "should display 'xphone' in a td"); + + // reopen the record in form view + form.$('tbody td:contains(xphone)').click(); + + assert.strictEqual($('.modal .modal-body input').val(), "xphone", + "should display 'xphone' in an input"); + + $('.modal .modal-body input.o_field_widget').val('456').trigger('input'); + + // discard + $('.modal .modal-footer span:contains(Discard)').click(); + + // reopen the record in form view + form.$('tbody td:contains(xphone)').click(); + + assert.strictEqual($('.modal .modal-body input.o_field_widget').val(), "123", + "should display 123 (previous change has been discarded)"); + + // write in the many2one field, value = 41 (xpad) + $('.modal .o_field_many2one input').click(); + $dropdown = $('.modal .o_field_many2one input').autocomplete('widget'); + $dropdown.find('li:eq(1) a').mouseenter(); + $dropdown.find('li:eq(1) a').click(); + + // save and close + $('.modal .modal-footer button:eq(0)').click(); + + assert.strictEqual(form.$('tbody td:contains(xpad)').length, 1, + "should display 'xpad' in a td"); + + // save the record + form.$buttons.find('.o_form_button_save').click(); + form.destroy(); + }); + + QUnit.test('one2many list with inline form view with context with parent key', function (assert) { + assert.expect(2); + + this.data.partner.records[0].p = [2]; + this.data.partner.records[0].product_id = 41; + this.data.partner.records[1].product_id = 37; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'name_search') { + assert.strictEqual(args.kwargs.context.partner_foo, "yop", + "should have correctly evaluated parent foo field"); + assert.strictEqual(args.kwargs.context.lalala, 41, + "should have correctly evaluated parent product_id field"); + } + return this._super.apply(this, arguments); + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + // open a modal + form.$('tr.o_data_row:eq(0) td:contains(xphone)').click(); + + // write in the many2one field + $('.modal .o_field_many2one input').click(); + + form.destroy(); + }); + + QUnit.test('value of invisible x2many fields is correctly evaluated in context', function (assert) { + assert.expect(1); + + this.data.partner.records[0].timmy = [12]; + this.data.partner.records[0].p = [2, 3]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: + '
' + + '' + + '' + + '' + + '', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'name_search') { + assert.deepEqual( + args.kwargs.context, { + p: [[4, 2, false], [4, 3, false]], + timmy: [[6, false, [12]]], + }, 'values of x2manys should have been correctly evaluated in context'); + } + return this._super.apply(this, arguments); + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_widget[name=product_id] input').click(); + + form.destroy(); + }); + + QUnit.test('one2many list, editable, with many2one and with context with parent key', function (assert) { + assert.expect(1); + + this.data.partner.records[0].p = [2]; + this.data.partner.records[1].product_id = 37; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'name_search') { + assert.strictEqual(args.kwargs.context.partner_foo, "yop", + "should have correctly evaluated parent foo field"); + } + return this._super.apply(this, arguments); + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + + form.$('tr.o_data_row:eq(0) td:contains(xphone)').click(); + + // trigger a name search + form.$('table td input').click(); + + form.destroy(); + }); + + QUnit.test('one2many list, editable, with a date in the context', function (assert) { + assert.expect(1); + + this.data.partner.records[0].p = [2]; + this.data.partner.records[1].product_id = 37; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 2, + mockRPC: function (route, args) { + if (args.method === 'default_get') { + assert.strictEqual(args.kwargs.context.date, '2017-01-25', + "should have properly evaluated date key in context"); + } + return this._super.apply(this, arguments); + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + + form.destroy(); + }); + + QUnit.test('one2many field with context', function (assert) { + assert.expect(2); + + var counter = 0; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'default_get') { + var expected = counter === 0 ? + [[4, 2, false]] : + [[4, 2, false], [0, args.kwargs.context.turtles[1][1], { turtle_foo: 'hammer' }]]; + assert.deepEqual(args.kwargs.context.turtles, expected, + "should have properly evaluated turtles key in context"); + counter++; + } + return this._super.apply(this, arguments); + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + form.$('input[name="turtle_foo"]').val('hammer').trigger('input'); + form.$('.o_field_x2many_list_row_add a').click(); + form.destroy(); + }); + + QUnit.test('one2many list edition, some basic functionality', function (assert) { + assert.expect(3); + + this.data.partner.fields.foo.default = false; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + form.$buttons.find('.o_form_button_edit').click(); + + form.$('tbody td.o_field_x2many_list_row_add a').click(); + + assert.strictEqual(form.$('td input.o_field_widget').length, 1, + "should have created a row in edit mode"); + + form.$('td input.o_field_widget').val('a').trigger('input'); + + assert.strictEqual(form.$('td input.o_field_widget').length, 1, + "should not have unselected the row after edition"); + + form.$('td input.o_field_widget').val('abc').trigger('input'); + form.$buttons.find('.o_form_button_save').click(); + + assert.strictEqual(form.$('td:contains(abc)').length, 1, + "should have a row with the correct value"); + form.destroy(); + }); + + QUnit.test('one2many list, the context is properly evaluated and sent', function (assert) { + assert.expect(2); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'default_get') { + var context = args.kwargs.context; + assert.strictEqual(context.hello, "world"); + assert.strictEqual(context.abc, 10); + } + return this._super.apply(this, arguments); + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('tbody td.o_field_x2many_list_row_add a').click(); + form.destroy(); + }); + + QUnit.test('one2many with many2many widget: create', function (assert) { + assert.expect(10); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '', + archs: { + 'turtle,false,list': '', + 'turtle,false,search': '', + }, + session: {}, + res_id: 1, + mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/turtle/create') { + assert.ok(args.args, "should write on the turtle record"); + } + if (route === '/web/dataset/call_kw/partner/write') { + assert.strictEqual(args.args[0][0], 1, "should write on the partner record 1"); + assert.strictEqual(args.args[1].turtles[0][0], 6, "should send only a 'replace with' command"); + } + return this._super.apply(this, arguments); + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + + assert.strictEqual($('.modal .o_data_row').length, 2, + "sould have 2 records in the select view (the last one is not displayed because it is already selected)"); + + $('.modal .o_data_row:first .o_list_record_selector input').click(); + $('.modal .o_select_button').click(); + $('.o_form_button_save').click(); + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + + assert.strictEqual($('.modal .o_data_row').length, 1, + "sould have 1 record in the select view"); + + $('.modal-footer button:eq(1)').click(); + $('.modal input.o_field_widget[name="turtle_foo"]').val('tototo').trigger('input'); + $('.modal input.o_field_widget[name="turtle_int"]').val(50).trigger('input'); + var $many2one = $('.modal [name="product_id"] input').click(); + var $dropdown = $many2one.autocomplete('widget'); + $dropdown.find('li:first a').mouseenter(); + $dropdown.find('li:first a').click(); + + $('.modal-footer button:contains(&):first').click(); + + assert.strictEqual($('.modal').length, 0, "sould close the modals"); + + assert.strictEqual(form.$('.o_data_row').length, 3, + "sould have 3 records in one2many list"); + assert.strictEqual(form.$('.o_data_row').text(), "blip1.59yop1.50tototo1.550xphone", + "should display the record values in one2many list"); + + $('.o_form_button_save').click(); + + form.destroy(); + }); + + QUnit.test('one2many with many2many widget: edition', function (assert) { + assert.expect(7); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '', + archs: { + 'turtle,false,list': '', + 'turtle,false,search': '', + }, + session: {}, + res_id: 1, + mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/turtle/write') { + assert.strictEqual(args.args[0].length, 1, "should write on the turtle record"); + assert.deepEqual(args.args[1], { "product_id": 37 }, "should write only the product_id on the turtle record"); + } + if (route === '/web/dataset/call_kw/partner/write') { + assert.strictEqual(args.args[0][0], 1, "should write on the partner record 1"); + assert.strictEqual(args.args[1].turtles[0][0], 6, "should send only a 'replace with' command"); + } + return this._super.apply(this, arguments); + }, + }); + + form.$('.o_data_row:first').click(); + assert.strictEqual($('.modal .modal-title').first().text().trim(), 'Open: one2many turtle field', + "modal should use the python field string as title"); + $('.modal .o_form_button_cancel').click(); + form.$buttons.find('.o_form_button_edit').click(); + + // edit the first one2many record + form.$('.o_data_row:first').click(); + var $many2one = $('.modal [name="product_id"] input').click(); + var $dropdown = $many2one.autocomplete('widget'); + $dropdown.find('li:first a').mouseenter(); + $dropdown.find('li:first a').click(); + $('.modal-footer button:first').click(); + + $('.o_form_button_save').click(); // don't save anything because the one2many does not change + + // add a one2many record + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + $('.modal .o_data_row:first .o_list_record_selector input').click(); + $('.modal .o_select_button').click(); + + // edit the second one2many record + form.$('.o_data_row:eq(1)').click(); + $many2one = $('.modal [name="product_id"] input').click(); + $dropdown = $many2one.autocomplete('widget'); + $dropdown.find('li:first a').mouseenter(); + $dropdown.find('li:first a').click(); + $('.modal-footer button:first').click(); + + $('.o_form_button_save').click(); + + form.destroy(); + }); + + QUnit.test('new record, the context is properly evaluated and sent', function (assert) { + assert.expect(2); + + this.data.partner.fields.int_field.default = 17; + var n = 0; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + mockRPC: function (route, args) { + if (args.method === 'default_get') { + n++; + if (n === 2) { + var context = args.kwargs.context; + assert.strictEqual(context.hello, "world"); + assert.strictEqual(context.abc, 17); + } + } + return this._super.apply(this, arguments); + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('tbody td.o_field_x2many_list_row_add a').click(); + form.destroy(); + }); + + QUnit.test('parent data is properly sent on an onchange rpc', function (assert) { + assert.expect(1); + + this.data.partner.onchanges = { bar: function () { } }; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'onchange') { + var fieldValues = args.args[1]; + assert.strictEqual(fieldValues.trululu.foo, "yop", + "should have properly sent the parent foo value"); + } + return this._super.apply(this, arguments); + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('tbody td.o_field_x2many_list_row_add a').click(); + form.destroy(); + }); + + QUnit.test('parent data is properly sent on an onchange rpc, new record', function (assert) { + assert.expect(6); + + this.data.turtle.onchanges = { turtle_bar: function () { } }; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + mockRPC: function (route, args) { + assert.step(args.method); + if (args.method === 'onchange' && args.model === 'turtle') { + var fieldValues = args.args[1]; + assert.strictEqual(fieldValues.turtle_trululu.foo, "My little Foo Value", + "should have properly sent the parent foo value"); + } + return this._super.apply(this, arguments); + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('tbody td.o_field_x2many_list_row_add a').click(); + assert.verifySteps(['default_get', 'onchange', 'default_get', 'onchange']); + form.destroy(); + }); + + QUnit.test('id in one2many obtained in onchange is properly set', function (assert) { + assert.expect(1); + + this.data.partner.onchanges.turtles = function (obj) { + obj.turtles = [ + [5], + [1, 3, { turtle_foo: "kawa" }] + ]; + }; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + }); + + assert.strictEqual(form.$('tr.o_data_row').text(), '3kawa', + "should have properly displayed id and foo field"); + form.destroy(); + }); + + QUnit.test('id field in one2many in a new record', function (assert) { + assert.expect(1); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + mockRPC: function (route, args) { + if (args.method === 'create') { + var virtualID = args.args[0].turtles[0][1]; + assert.deepEqual(args.args[0].turtles, + [[0, virtualID, { turtle_foo: "cat" }]], + 'should send proper commands'); + } + return this._super.apply(this, arguments); + }, + }); + form.$('td.o_field_x2many_list_row_add a').click(); + form.$('td input[name="turtle_foo"]').val('cat').trigger('input'); + form.$buttons.find('.o_form_button_save').click(); + + form.destroy(); + }); + + QUnit.test('sub form view with a required field', function (assert) { + assert.expect(2); + this.data.partner.fields.foo.required = true; + this.data.partner.fields.foo.default = null; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '', + res_id: 1, + }); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('tbody td.o_field_x2many_list_row_add a').click(); + $('.modal-footer button.btn-primary').first().click(); + + assert.strictEqual($('.modal').length, 1, "should still have an open modal"); + assert.strictEqual($('.modal tbody label.o_field_invalid').length, 1, + "should have displayed invalid fields"); + form.destroy(); + }); + + QUnit.test('one2many list with action button', function (assert) { + assert.expect(4); + + this.data.partner.records[0].p = [2]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '
", "should display notification"); - $notification.find('.o_buttons button:contains(accept0)').click(); - $notification.find('.o_buttons button:contains(accept0)').click(); - $notification.find('.o_buttons button:contains(refuse0)').click(); - $notification.eq(0).find('.o_close').click(); - - $notification.find('.o_buttons button:contains(refuse1)').click(); - - $notification.eq(2).find('.o_close').click(); + testUtils.dom.click($notification.find('.o_buttons button:contains(accept0)')); + testUtils.dom.click($notification.find('.o_buttons button:contains(refuse1)')); + testUtils.dom.click($notification.eq(2).find('.o_close')); setTimeout(function () { assert.strictEqual($notification.is(':hidden'), true, "should hide the notification"); @@ -211,8 +206,8 @@ QUnit.module('Services', { var done = assert.async(); assert.expect(2); - testUtils.unpatch(Notification); - testUtils.patch(Notification, { + testUtils.mock.unpatch(Notification); + testUtils.mock.patch(Notification, { _autoCloseDelay: 2500, _animationDelay: 0, }); diff --git a/addons/web/static/tests/tools/debug_manager_tests.js b/addons/web/static/tests/tools/debug_manager_tests.js index cf31d085a50..6929d55cce5 100644 --- a/addons/web/static/tests/tools/debug_manager_tests.js +++ b/addons/web/static/tests/tools/debug_manager_tests.js @@ -81,7 +81,8 @@ QUnit.module('DebugManager', {}, function () { assert.strictEqual($attachmentMenu.length, 1, "should have Manage Attachments menu item"); assert.strictEqual($attachmentMenu.text().trim(), "Manage Attachments", "should have correct menu item text"); - $attachmentMenu.click(); + testUtils.dom.click(debugManager.$('> a')); // open dropdown + testUtils.dom.click($attachmentMenu); debugManager.destroy(); }); @@ -152,14 +153,15 @@ QUnit.module('DebugManager', {}, function () { debugManager.update('action', action, form); // click on set_defaults dropdown - debugManager.$('a[data-action="set_defaults"]').click(); + testUtils.dom.click(debugManager.$('> a')); // open dropdown + testUtils.dom.click(debugManager.$('a[data-action="set_defaults"]')); var $modal = $('.modal-content'); assert.strictEqual($modal.length, 1, 'One modal present'); $modal.find('select[id=formview_default_fields] option[value=foo]').prop('selected', true); // Save - $modal.find('.modal-footer button').eq(1).click(); + testUtils.dom.click($modal.find('.modal-footer button').eq(1)); form.destroy(); debugManager.destroy(); diff --git a/addons/web/static/tests/views/abstract_controller_tests.js b/addons/web/static/tests/views/abstract_controller_tests.js index c9b7daa45dc..cf5810219c7 100644 --- a/addons/web/static/tests/views/abstract_controller_tests.js +++ b/addons/web/static/tests/views/abstract_controller_tests.js @@ -64,8 +64,8 @@ odoo.define("base.abstract_controller_tests", function(require) { } } }); - view.$(".simple").click(); - view.$(".with-child span").click(); + testUtils.dom.click(view.$(".simple")); + testUtils.dom.click(view.$(".with-child span")); assert.verifySteps(["a1", "a2"]); } ); diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js index 7ed0c3914ee..9f421e9c6e9 100644 --- a/addons/web/static/tests/views/calendar_tests.js +++ b/addons/web/static/tests/views/calendar_tests.js @@ -172,15 +172,15 @@ QUnit.module('Views', { // test view scales - assert.strictEqual(calendar.$('.fc-event').length, 9, "should display 9 events on the week (4 event + 5 days event)"); + assert.containsN(calendar, '.fc-event', 9, "should display 9 events on the week (4 event + 5 days event)"); assert.strictEqual($sidebar.find('.o_selected_range').length, 7, "week scale should highlight 7 days in mini calendar"); calendar.$buttons.find('.o_calendar_button_day').trigger('click'); // display only one day - assert.strictEqual(calendar.$('.fc-event').length, 2, "should display 2 events on the day"); + assert.containsN(calendar, '.fc-event', 2, "should display 2 events on the day"); assert.strictEqual($sidebar.find('.o_selected_range').length, 1, "should highlight the target day in mini calendar"); calendar.$buttons.find('.o_calendar_button_month').trigger('click'); // display all the month - assert.strictEqual(calendar.$('.fc-event').length, 7, "should display 7 events on the month (5 events + 2 week event - 1 'event 6' is filtered + 1 'Undefined event')"); + assert.containsN(calendar, '.fc-event', 7, "should display 7 events on the month (5 events + 2 week event - 1 'event 6' is filtered + 1 'Undefined event')"); assert.strictEqual($sidebar.find('.o_selected_range').length, 31, "month scale should highlight all days in mini calendar"); // test filters @@ -200,12 +200,12 @@ QUnit.module('Views', { assert.strictEqual($attendeesFilter.find('.o_calendar_filter_item').length, 3, "should display 3 filter items for 'attendees' who use write_model (2 saved + Everything)"); assert.ok($attendeesFilter.find('.o_field_many2one').length, "should display one2many search bar for 'attendees' filter"); - assert.strictEqual(calendar.$('.fc-event').length, 7, + assert.containsN(calendar, '.fc-event', 7, "should display 7 events ('event 5' counts for 2 because it spans two weeks and thus generate two fc-event elements)"); - calendar.$('.o_calendar_filter .custom-checkbox input').first().click(); // Disable first filter - assert.strictEqual(calendar.$('.fc-event').length, 4, "should now only display 4 event"); - calendar.$('.o_calendar_filter .custom-checkbox input').eq(1).click(); // Disable second filter - assert.strictEqual(calendar.$('.fc-event').length, 0, "should not display any event anymore"); + testUtils.dom.click(calendar.$('.o_calendar_filter .custom-checkbox input').first()); + assert.containsN(calendar, '.fc-event', 4, "should now only display 4 event"); + testUtils.dom.click(calendar.$('.o_calendar_filter .custom-checkbox input').eq(1)); + assert.containsNone(calendar, '.fc-event', "should not display any event anymore"); // test search bar in filter $sidebar.find('input[type="text"]').trigger('click'); @@ -257,12 +257,12 @@ QUnit.module('Views', { 'Meetings Test (Dec 11 – 17, 2016)', "should display the current week"); // switch to day mode - actionManager.controlPanel.$('.o_calendar_button_day').click(); + testUtils.dom.click(actionManager.controlPanel.$('.o_calendar_button_day')); assert.strictEqual(actionManager.controlPanel.$('.breadcrumb-item').text(), 'Meetings Test (December 12, 2016)', "should display the current day"); // switch to month mode - actionManager.controlPanel.$('.o_calendar_button_month').click(); + testUtils.dom.click(actionManager.controlPanel.$('.o_calendar_button_month')); assert.strictEqual(actionManager.controlPanel.$('.breadcrumb-item').text(), 'Meetings Test (December 2016)', "should display the current month"); @@ -308,7 +308,7 @@ QUnit.module('Views', { assert.ok($('.modal-body').length, "should switch the modal in edit mode"); assert.notOk($('.modal-footer button.btn:contains(Delete)').length, "formViewDialog should not have a delete button in edit mode"); - $('.modal-body input:first').val('event 4 modified').trigger('input'); + testUtils.fields.editInput($('.modal-body input:first'), 'event 4 modified'); $('.modal-footer button.btn:contains(Save)').trigger('click'); assert.notOk($('.modal-body').length, "save button should close the modal"); @@ -318,21 +318,21 @@ QUnit.module('Views', { var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); - testUtils.triggerMouseEvent($cell, "mousedown"); - testUtils.triggerMouseEvent($cell, "mouseup"); + testUtils.dom.triggerMouseEvent($cell, "mousedown"); + testUtils.dom.triggerMouseEvent($cell, "mouseup"); assert.ok($('.modal-sm').length, "should open the quick create dialog"); - $('.modal-body input:first').val('new event in quick create').trigger('input'); + testUtils.fields.editInput($('.modal-body input:first'), 'new event in quick create'); $('.modal-footer button.btn:contains(Create)').trigger('click').trigger('click'); assert.strictEqual(calendar.$('.fc-event:contains(new event in quick create)').length, 1, "should display the new record after quick create"); - assert.strictEqual(calendar.$('td.fc-event-container[colspan]').length, 2, "should the new record have only one day"); + assert.containsN(calendar, 'td.fc-event-container[colspan]', 2, "should the new record have only one day"); // create a new event, quick create only (validated by pressing enter key) - testUtils.triggerMouseEvent($cell, "mousedown"); - testUtils.triggerMouseEvent($cell, "mouseup"); + testUtils.dom.triggerMouseEvent($cell, "mousedown"); + testUtils.dom.triggerMouseEvent($cell, "mouseup"); assert.ok($('.modal-sm').length, "should open the quick create dialog"); @@ -348,12 +348,12 @@ QUnit.module('Views', { $cell = calendar.$('.fc-day-grid .fc-row:eq(4) .fc-day:eq(2)'); - testUtils.triggerMouseEvent($cell, "mousedown"); - testUtils.triggerMouseEvent($cell, "mouseup"); + testUtils.dom.triggerMouseEvent($cell, "mousedown"); + testUtils.dom.triggerMouseEvent($cell, "mouseup"); assert.strictEqual($('.modal-sm').length, 1, "should open the quick create dialog"); - $('.modal-body input:first').val('coucou').trigger('input'); + testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); $('.modal-footer button.btn:contains(Edit)').trigger('click'); assert.strictEqual($('.modal-lg .o_form_view').length, 1, "should open the slow create dialog"); @@ -370,11 +370,11 @@ QUnit.module('Views', { $cell = calendar.$('.fc-day-grid .fc-row:eq(3) .fc-day:eq(2)'); - testUtils.triggerMouseEvent($cell, "mousedown"); - testUtils.triggerMouseEvent($cell.next(), "mousemove"); - testUtils.triggerMouseEvent($cell.next(), "mouseup"); + testUtils.dom.triggerMouseEvent($cell, "mousedown"); + testUtils.dom.triggerMouseEvent($cell.next(), "mousemove"); + testUtils.dom.triggerMouseEvent($cell.next(), "mouseup"); - $('.modal-dialog input:first').val('new event in quick create 2').trigger('input'); + testUtils.fields.editInput($('.modal-dialog input:first'), 'new event in quick create 2'); $('.modal-footer button.btn:contains(Edit)').trigger('click'); assert.strictEqual($('.modal-lg input:first').val(), 'new event in quick create 2', "should open the formViewDialog with default values"); @@ -384,7 +384,7 @@ QUnit.module('Views', { assert.notOk($('.modal').length, "should close dialogs"); var $newevent2 = calendar.$('.fc-event:contains(new event in quick create 2)'); assert.ok($newevent2.length, "should display the 2 days new record"); - assert.strictEqual($newevent2.closest('.fc-event-container').attr('colspan'), "2", "the new record should have 2 days"); + assert.hasAttrValue($newevent2.closest('.fc-event-container'), 'colspan', "2", "the new record should have 2 days"); // delete the a record @@ -394,11 +394,11 @@ QUnit.module('Views', { $('.modal-footer button.btn:contains(Ok)').trigger('click'); assert.notOk(calendar.$('.fc-event:contains(event 4) .fc-content').length, "the record should be deleted"); - assert.strictEqual(calendar.$('.fc-event-container .fc-event').length, 10, "should display 10 events"); + assert.containsN(calendar, '.fc-event-container .fc-event', 10, "should display 10 events"); // move to next month - calendar.$buttons.find('.o_calendar_button_next').click(); + testUtils.dom.click(calendar.$buttons.find('.o_calendar_button_next')); - assert.strictEqual(calendar.$('.fc-event-container .fc-event').length, 0, "should display 0 events"); + assert.containsNone(calendar, '.fc-event-container .fc-event', "should display 0 events"); calendar.destroy(); }); @@ -440,13 +440,13 @@ QUnit.module('Views', { // create a new event var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); - testUtils.triggerMouseEvent($cell, "mousedown"); - testUtils.triggerMouseEvent($cell, "mouseup"); + testUtils.dom.triggerMouseEvent($cell, "mousedown"); + testUtils.dom.triggerMouseEvent($cell, "mouseup"); assert.strictEqual($('.modal-sm .modal-title').text(), 'Create: Events', "should open the quick create dialog"); - $('.modal-body input:first').val('new event in quick create').trigger('input'); + testUtils.fields.editInput($('.modal-body input:first'), 'new event in quick create'); $('.modal-footer button.btn:contains(Create)').trigger('click').trigger('click'); // If the event is not default-prevented, a traceback will be raised, which we do not want @@ -515,20 +515,20 @@ QUnit.module('Views', { var left = calendar.$('.fc-day:eq(2)').offset().left + 5; try { - testUtils.triggerPositionalMouseEvent(left, top, "mousedown"); + testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); } catch (e) { calendar.destroy(); $view.remove(); throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); } - testUtils.triggerPositionalMouseEvent(left, top + 60, "mousemove"); + testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mousemove"); assert.strictEqual(calendar.$('.fc-content .fc-time').text(), "08:00 - 10:00", "should display the time in the calendar sticker"); - testUtils.triggerPositionalMouseEvent(left, top + 60, "mouseup"); - $('.modal input:first').val('new event').trigger('input'); + testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mouseup"); + testUtils.fields.editInput($('.modal input:first'), 'new event'); $('.modal button.btn:contains(Create)').trigger('click'); var $newevent = calendar.$('.fc-event:contains(new event)'); @@ -551,7 +551,7 @@ QUnit.module('Views', { $newevent.trigger('click'); $('.modal button.btn-secondary:contains(Delete)').trigger('click'); $('.modal button.btn-primary:contains(Ok)').trigger('click'); - assert.strictEqual(calendar.$('.fc-content').length, 0, "should delete the record"); + assert.containsNone(calendar, '.fc-content', "should delete the record"); calendar.destroy(); $view.remove(); @@ -624,15 +624,15 @@ QUnit.module('Views', { var left = calendar.$('.fc-day:eq(2)').offset().left + 5; try { - testUtils.triggerPositionalMouseEvent(left, top, "mousedown"); + testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); } catch (e) { calendar.destroy(); $view.remove(); throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); } - testUtils.triggerPositionalMouseEvent(left, top + 60, "mousemove"); - testUtils.triggerPositionalMouseEvent(left, top + 60, "mouseup"); - $('.modal input:first').val('new event').trigger('input'); + testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mousemove"); + testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mouseup"); + testUtils.fields.editInput($('.modal input:first'), 'new event'); $('.modal button.btn:contains(Edit)').trigger('click'); assert.strictEqual($('.o_field_widget[name="start"] input').val(), "12/13/2016 08:00:00", @@ -649,14 +649,14 @@ QUnit.module('Views', { "should display the datetime from the date with the timezone"); // use datepicker to enter a date: 12/13/2016 08:00:00 - testUtils.openDatepicker($('.o_field_widget[name="start"].o_datepicker')); + testUtils.dom.openDatepicker($('.o_field_widget[name="start"].o_datepicker')); $('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]').trigger('click'); $('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour').trigger('click'); $('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(08)').trigger('click'); $('.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]').trigger('click'); // use datepicker to enter a date: 12/13/2016 10:00:00 - testUtils.openDatepicker($('.o_field_widget[name="stop"].o_datepicker')); + testUtils.dom.openDatepicker($('.o_field_widget[name="stop"].o_datepicker')); $('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]').trigger('click'); $('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour').trigger('click'); $('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(10)').trigger('click'); @@ -689,10 +689,10 @@ QUnit.module('Views', { "start": "2016-12-12 06:00:00", "stop": "2016-12-12 08:00:00" }; - testUtils.triggerPositionalMouseEvent(left, top, "mousedown"); + testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); left = calendar.$('.fc-day:eq(1)').offset().left + 5; - testUtils.triggerPositionalMouseEvent(left, top, "mousemove"); - testUtils.triggerPositionalMouseEvent(left, top, "mouseup"); + testUtils.dom.triggerPositionalMouseEvent(left, top, "mousemove"); + testUtils.dom.triggerPositionalMouseEvent(left, top, "mouseup"); // Move to "All day" expectedEvent = { @@ -700,10 +700,10 @@ QUnit.module('Views', { "start": "2016-12-12 00:00:00", "stop": "2016-12-12 00:00:00" }; - testUtils.triggerPositionalMouseEvent(left, top, "mousedown"); + testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); top = calendar.$('.fc-day:eq(1)').offset().top + 5; - testUtils.triggerPositionalMouseEvent(left, top, "mousemove"); - testUtils.triggerPositionalMouseEvent(left, top, "mouseup"); + testUtils.dom.triggerPositionalMouseEvent(left, top, "mousemove"); + testUtils.dom.triggerPositionalMouseEvent(left, top, "mouseup"); calendar.destroy(); $view.remove(); @@ -764,20 +764,20 @@ QUnit.module('Views', { var left = calendar.$('.fc-day:eq(2)').offset().left + 5; try { - testUtils.triggerPositionalMouseEvent(left, top, "mousedown"); + testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); } catch (e) { calendar.destroy(); $view.remove(); throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); } - testUtils.triggerPositionalMouseEvent(left, top + 60, "mousemove"); + testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mousemove"); assert.strictEqual(calendar.$('.fc-content .fc-time').text(), "8:00am - 10:00am", "should display the time in the calendar sticker"); - testUtils.triggerPositionalMouseEvent(left, top + 60, "mouseup"); - $('.modal input:first').val('new event').trigger('input'); + testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mouseup"); + testUtils.fields.editInput($('.modal input:first'), 'new event'); $('.modal button.btn:contains(Create)').trigger('click'); var $newevent = calendar.$('.fc-event:contains(new event)'); @@ -800,7 +800,7 @@ QUnit.module('Views', { $newevent.trigger('click'); $('.modal button.btn-secondary:contains(Delete)').trigger('click'); $('.modal button.btn-primary:contains(Ok)').trigger('click'); - assert.strictEqual(calendar.$('.fc-content').length, 0, "should delete the record"); + assert.containsNone(calendar, '.fc-content', "should delete the record"); calendar.destroy(); $view.remove(); @@ -873,15 +873,15 @@ QUnit.module('Views', { var left = calendar.$('.fc-day:eq(2)').offset().left + 5; try { - testUtils.triggerPositionalMouseEvent(left, top, "mousedown"); + testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); } catch (e) { calendar.destroy(); $view.remove(); throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); } - testUtils.triggerPositionalMouseEvent(left, top + 60, "mousemove"); - testUtils.triggerPositionalMouseEvent(left, top + 60, "mouseup"); - $('.modal input:first').val('new event').trigger('input'); + testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mousemove"); + testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mouseup"); + testUtils.fields.editInput($('.modal input:first'), 'new event'); $('.modal button.btn:contains(Edit)').trigger('click'); assert.strictEqual($('.o_field_widget[name="start"] input').val(), "12/13/2016 08:00:00", @@ -898,14 +898,14 @@ QUnit.module('Views', { "should display the datetime from the date with the timezone"); // use datepicker to enter a date: 12/13/2016 08:00:00 - testUtils.openDatepicker($('.o_field_widget[name="start"].o_datepicker')); + testUtils.dom.openDatepicker($('.o_field_widget[name="start"].o_datepicker')); $('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]').trigger('click'); $('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour').trigger('click'); $('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(08)').trigger('click'); $('.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]').trigger('click'); // use datepicker to enter a date: 12/13/2016 10:00:00 - testUtils.openDatepicker($('.o_field_widget[name="stop"].o_datepicker')); + testUtils.dom.openDatepicker($('.o_field_widget[name="stop"].o_datepicker')); $('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]').trigger('click'); $('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour').trigger('click'); $('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(10)').trigger('click'); @@ -938,10 +938,10 @@ QUnit.module('Views', { "start": "2016-12-12 06:00:00", "stop": "2016-12-12 08:00:00" }; - testUtils.triggerPositionalMouseEvent(left, top, "mousedown"); + testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); left = calendar.$('.fc-day:eq(1)').offset().left + 5; - testUtils.triggerPositionalMouseEvent(left, top, "mousemove"); - testUtils.triggerPositionalMouseEvent(left, top, "mouseup"); + testUtils.dom.triggerPositionalMouseEvent(left, top, "mousemove"); + testUtils.dom.triggerPositionalMouseEvent(left, top, "mouseup"); // Move to "All day" expectedEvent = { @@ -949,10 +949,10 @@ QUnit.module('Views', { "start": "2016-12-12 00:00:00", "stop": "2016-12-12 00:00:00" }; - testUtils.triggerPositionalMouseEvent(left, top, "mousedown"); + testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); top = calendar.$('.fc-day:eq(1)').offset().top + 5; - testUtils.triggerPositionalMouseEvent(left, top, "mousemove"); - testUtils.triggerPositionalMouseEvent(left, top, "mouseup"); + testUtils.dom.triggerPositionalMouseEvent(left, top, "mousemove"); + testUtils.dom.triggerPositionalMouseEvent(left, top, "mouseup"); calendar.destroy(); $view.remove(); @@ -1021,23 +1021,23 @@ QUnit.module('Views', { var pos = calendar.$('.fc-bg td:eq(4)').offset(); try { - testUtils.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousedown"); + testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousedown"); } catch (e) { calendar.destroy(); $view.remove(); throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); } pos = calendar.$('.fc-bg td:eq(5)').offset(); - testUtils.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousemove"); - testUtils.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mouseup"); + testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousemove"); + testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mouseup"); - $('.modal input:first').val('new event').trigger('input'); + testUtils.fields.editInput($('.modal input:first'), 'new event'); $('.modal button.btn:contains(Create)').trigger('click'); var $newevent = calendar.$('.fc-event:contains(new event)'); assert.strictEqual($newevent.text().replace(/[\s\n\r]+/g, ''), "newevent", "should display the new event with time and title"); - assert.strictEqual($newevent.parent().attr('colspan'), "2", + assert.hasAttrValue($newevent.parent(), 'colspan', "2", "should appear over two days."); assert.deepEqual($newevent.data('fcSeg').event.record, @@ -1100,15 +1100,15 @@ QUnit.module('Views', { var pos = calendar.$('.fc-bg td:eq(4)').offset(); try { - testUtils.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousedown"); + testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousedown"); } catch (e) { calendar.destroy(); $view.remove(); throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); } pos = calendar.$('.fc-bg td:eq(5)').offset(); - testUtils.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousemove"); - testUtils.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mouseup"); + testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousemove"); + testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mouseup"); calendar.destroy(); $view.remove(); @@ -1159,23 +1159,23 @@ QUnit.module('Views', { var pos = calendar.$('.fc-bg td:eq(20)').offset(); try { - testUtils.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousedown"); + testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousedown"); } catch (e) { calendar.destroy(); $view.remove(); throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); } pos = calendar.$('.fc-bg td:eq(21)').offset(); - testUtils.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousemove"); - testUtils.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mouseup"); + testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousemove"); + testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mouseup"); - $('.modal input:first').val('new event').trigger('input'); + testUtils.fields.editInput($('.modal input:first'), 'new event'); $('.modal button.btn:contains(Create)').trigger('click'); var $newevent = calendar.$('.fc-event:contains(new event)'); assert.strictEqual($newevent.text().replace(/[\s\n\r]+/g, ''), "newevent", "should display the new event with time and title"); - assert.strictEqual($newevent.parent().attr('colspan'), "2", + assert.hasAttrValue($newevent.parent(), 'colspan', "2", "should appear over two days."); assert.deepEqual($newevent.data('fcSeg').event.record, { @@ -1218,26 +1218,26 @@ QUnit.module('Views', { }, }); - assert.strictEqual(calendar.$('.fc-agendaWeek-view').length, 1, "should be in week mode"); - assert.strictEqual(calendar.$('.fc-event').length, 9, "should display 9 events on the week (4 event + 5 days event)"); - calendar.$('.o_calendar_mini a:contains(19)').click(); + assert.containsOnce(calendar, '.fc-agendaWeek-view', "should be in week mode"); + assert.containsN(calendar, '.fc-event', 9, "should display 9 events on the week (4 event + 5 days event)"); + testUtils.dom.click(calendar.$('.o_calendar_mini a:contains(19)')); // Clicking on a day in another week should switch to the other week view - assert.strictEqual(calendar.$('.fc-agendaWeek-view').length, 1, "should be in week mode"); - assert.strictEqual(calendar.$('.fc-event').length, 4, "should display 4 events on the week (1 event + 3 days event)"); + assert.containsOnce(calendar, '.fc-agendaWeek-view', "should be in week mode"); + assert.containsN(calendar, '.fc-event', 4, "should display 4 events on the week (1 event + 3 days event)"); // Clicking on a day in the same week should switch to that particular day view - calendar.$('.o_calendar_mini a:contains(18)').click(); - assert.strictEqual(calendar.$('.fc-agendaDay-view').length, 1, "should be in day mode"); - assert.strictEqual(calendar.$('.fc-event').length, 2, "should display 2 events on the day"); + testUtils.dom.click(calendar.$('.o_calendar_mini a:contains(18)')); + assert.containsOnce(calendar, '.fc-agendaDay-view', "should be in day mode"); + assert.containsN(calendar, '.fc-event', 2, "should display 2 events on the day"); // Clicking on the same day should toggle between day, month and week views - calendar.$('.o_calendar_mini a:contains(18)').click(); - assert.strictEqual(calendar.$('.fc-month-view').length, 1, "should be in month mode"); - assert.strictEqual(calendar.$('.fc-event').length, 7, "should display 7 events on the month (event 5 is on multiple weeks and generates to .fc-event)"); - calendar.$('.o_calendar_mini a:contains(18)').click(); - assert.strictEqual(calendar.$('.fc-agendaWeek-view').length, 1, "should be in week mode"); - assert.strictEqual(calendar.$('.fc-event').length, 4, "should display 4 events on the week (1 event + 3 days event)"); - calendar.$('.o_calendar_mini a:contains(18)').click(); - assert.strictEqual(calendar.$('.fc-agendaDay-view').length, 1, "should be in day mode"); - assert.strictEqual(calendar.$('.fc-event').length, 2, "should display 2 events on the day"); + testUtils.dom.click(calendar.$('.o_calendar_mini a:contains(18)')); + assert.containsOnce(calendar, '.fc-month-view', "should be in month mode"); + assert.containsN(calendar, '.fc-event', 7, "should display 7 events on the month (event 5 is on multiple weeks and generates to .fc-event)"); + testUtils.dom.click(calendar.$('.o_calendar_mini a:contains(18)')); + assert.containsOnce(calendar, '.fc-agendaWeek-view', "should be in week mode"); + assert.containsN(calendar, '.fc-event', 4, "should display 4 events on the week (1 event + 3 days event)"); + testUtils.dom.click(calendar.$('.o_calendar_mini a:contains(18)')); + assert.containsOnce(calendar, '.fc-agendaDay-view', "should be in day mode"); + assert.containsN(calendar, '.fc-event', 2, "should display 2 events on the day"); calendar.destroy(); }); @@ -1267,7 +1267,7 @@ QUnit.module('Views', { }, }); - assert.strictEqual(calendar.$('.o_calendar_filter_items .o_cal_avatar').length, 3, + assert.containsN(calendar, '.o_calendar_filter_items .o_cal_avatar', 3, "should have 3 avatars in the side bar"); var $event1Avatars = calendar.$('.fc-event .o_calendar_avatars').first(); @@ -1313,7 +1313,7 @@ QUnit.module('Views', { // click on an existing event to open the form view - testUtils.intercept(calendar, 'do_action', function (event) { + testUtils.mock.intercept(calendar, 'do_action', function (event) { assert.deepEqual(event.data.action, { type: "ir.actions.act_window", @@ -1330,11 +1330,11 @@ QUnit.module('Views', { // create a new event and edit it var $cell = calendar.$('.fc-day-grid .fc-row:eq(4) .fc-day:eq(2)'); - testUtils.triggerMouseEvent($cell, "mousedown"); - testUtils.triggerMouseEvent($cell, "mouseup"); - $('.modal-body input:first').val('coucou').trigger('input'); + testUtils.dom.triggerMouseEvent($cell, "mousedown"); + testUtils.dom.triggerMouseEvent($cell, "mouseup"); + testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); - testUtils.intercept(calendar, 'do_action', function (event) { + testUtils.mock.intercept(calendar, 'do_action', function (event) { assert.deepEqual(event.data.action, { type: "ir.actions.act_window", @@ -1387,11 +1387,11 @@ QUnit.module('Views', { // create a new event and edit it var $cell = calendar.$('.fc-day-grid .fc-row:eq(4) .fc-day:eq(2)'); - testUtils.triggerMouseEvent($cell, "mousedown"); - testUtils.triggerMouseEvent($cell, "mouseup"); - $('.modal-body input:first').val('coucou').trigger('input'); + testUtils.dom.triggerMouseEvent($cell, "mousedown"); + testUtils.dom.triggerMouseEvent($cell, "mouseup"); + testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); - testUtils.intercept(calendar, 'do_action', function (event) { + testUtils.mock.intercept(calendar, 'do_action', function (event) { assert.deepEqual(event.data.action, { type: "ir.actions.act_window", @@ -1444,11 +1444,11 @@ QUnit.module('Views', { }, }); - assert.strictEqual(calendar.$('.fc-resizer').length, 0, "should not have resize button"); + assert.containsNone(calendar, '.fc-resizer', "should not have resize button"); // click on an existing event to open the form view - testUtils.intercept(calendar, 'do_action', function (event) { + testUtils.mock.intercept(calendar, 'do_action', function (event) { assert.deepEqual(event.data.action, { type: "ir.actions.act_window", @@ -1465,11 +1465,11 @@ QUnit.module('Views', { // create a new event and edit it var $cell = calendar.$('.fc-day-grid .fc-row:eq(4) .fc-day:eq(2)'); - testUtils.triggerMouseEvent($cell, "mousedown"); - testUtils.triggerMouseEvent($cell, "mouseup"); - $('.modal-body input:first').val('coucou').trigger('input'); + testUtils.dom.triggerMouseEvent($cell, "mousedown"); + testUtils.dom.triggerMouseEvent($cell, "mouseup"); + testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); - testUtils.intercept(calendar, 'do_action', function (event) { + testUtils.mock.intercept(calendar, 'do_action', function (event) { assert.deepEqual(event.data.action, { type: "ir.actions.act_window", @@ -1538,17 +1538,17 @@ QUnit.module('Views', { }, }); - assert.strictEqual(calendar.$('.fc-event').length, 9, + assert.containsN(calendar, '.fc-event', 9, "should display 9 events on the week"); // Select the events only associated with partner 2 - calendar.$('.o_calendar_filter_item[data-id=2] input').click(); - assert.strictEqual(calendar.$('.fc-event').length, 4, + testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-id=2] input')); + assert.containsN(calendar, '.fc-event', 4, "should display 4 events on the week"); // Click on the 'all' filter to reload all events - calendar.$('.o_calendar_filter_item[data-value=all] input').click(); - assert.strictEqual(calendar.$('.fc-event').length, 9, + testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=all] input')); + assert.containsN(calendar, '.fc-event', 9, "should display 9 events on the week"); calendar.destroy(); @@ -1586,29 +1586,29 @@ QUnit.module('Views', { var $view = $('#qunit-fixture').contents(); $view.prependTo('body'); // => select with click position - calendar.$('.o_calendar_filter_item[data-value=4] input').click(); + testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=4] input')); - assert.strictEqual(calendar.$('.o_calendar_filter_item').length, 5, "should display 5 filter items"); - assert.strictEqual(calendar.$('.fc-event').length, 3, "should display 3 events"); + assert.containsN(calendar, '.o_calendar_filter_item', 5, "should display 5 filter items"); + assert.containsN(calendar, '.fc-event', 3, "should display 3 events"); // quick create a record var left = calendar.$('.fc-bg td:eq(4)').offset().left+15; var top = calendar.$('.fc-slats tr:eq(4) td:first').offset().top+15; try { - testUtils.triggerPositionalMouseEvent(left, top, "mousedown"); + testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); } catch (e) { calendar.destroy(); $view.remove(); throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); } - testUtils.triggerPositionalMouseEvent(left, top + 200, "mousemove"); - testUtils.triggerPositionalMouseEvent(left, top + 200, "mouseup"); + testUtils.dom.triggerPositionalMouseEvent(left, top + 200, "mousemove"); + testUtils.dom.triggerPositionalMouseEvent(left, top + 200, "mouseup"); - $('.modal-body input:first').val('coucou').trigger('input'); + testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); $('.modal-footer button.btn:contains(Create)').trigger('click'); - assert.strictEqual(calendar.$('.o_calendar_filter_item').length, 6, "should add the missing filter (active)"); - assert.strictEqual(calendar.$('.fc-event').length, 4, "should display the created item"); + assert.containsN(calendar, '.o_calendar_filter_item', 6, "should add the missing filter (active)"); + assert.containsN(calendar, '.fc-event', 4, "should display the created item"); // change default value for quick create an hide record this.data.event.fields.user_id.default = 4; @@ -1617,19 +1617,19 @@ QUnit.module('Views', { // quick create and other record left = calendar.$('.fc-bg td:eq(3)').offset().left+15; top = calendar.$('.fc-slats tr:eq(4) td:first').offset().top+15; - testUtils.triggerPositionalMouseEvent(left, top, "mousedown"); - testUtils.triggerPositionalMouseEvent(left, top + 200, "mousemove"); - testUtils.triggerPositionalMouseEvent(left, top + 200, "mouseup"); + testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); + testUtils.dom.triggerPositionalMouseEvent(left, top + 200, "mousemove"); + testUtils.dom.triggerPositionalMouseEvent(left, top + 200, "mouseup"); - $('.modal-body input:first').val('coucou 2').trigger('input'); + testUtils.fields.editInput($('.modal-body input:first'), 'coucou 2'); $('.modal-footer button.btn:contains(Create)').trigger('click'); - assert.strictEqual(calendar.$('.o_calendar_filter_item').length, 6, "should have the same filters"); - assert.strictEqual(calendar.$('.fc-event').length, 4, "should not display the created item"); + assert.containsN(calendar, '.o_calendar_filter_item', 6, "should have the same filters"); + assert.containsN(calendar, '.fc-event', 4, "should not display the created item"); - calendar.$('.o_calendar_filter_item[data-value=4] input').click(); + testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=4] input')); - assert.strictEqual(calendar.$('.fc-event').length, 11, "should display all records"); + assert.containsN(calendar, '.fc-event', 11, "should display all records"); calendar.destroy(); $view.remove(); @@ -1683,31 +1683,31 @@ QUnit.module('Views', { var $view = $('#qunit-fixture').contents(); $view.prependTo('body'); // => select with click position - calendar.$('.o_calendar_filter_item[data-value=4] input').click(); + testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=4] input')); - assert.strictEqual(calendar.$('.o_calendar_filter_item').length, 5, "should display 5 filter items"); - assert.strictEqual(calendar.$('.fc-event').length, 3, "should display 3 events"); + assert.containsN(calendar, '.o_calendar_filter_item', 5, "should display 5 filter items"); + assert.containsN(calendar, '.fc-event', 3, "should display 3 events"); // quick create a record var left = calendar.$('.fc-bg td:eq(4)').offset().left+15; var top = calendar.$('.fc-slats tr:eq(4) td:first').offset().top+15; try { - testUtils.triggerPositionalMouseEvent(left, top, "mousedown"); + testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); } catch (e) { calendar.destroy(); $view.remove(); throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); } - testUtils.triggerPositionalMouseEvent(left, top + 200, "mousemove"); - testUtils.triggerPositionalMouseEvent(left, top + 200, "mouseup"); + testUtils.dom.triggerPositionalMouseEvent(left, top + 200, "mousemove"); + testUtils.dom.triggerPositionalMouseEvent(left, top + 200, "mouseup"); - $('.modal-body input:first').val('coucou').trigger('input'); + testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); $('.modal-footer button.btn:contains(Edit)').trigger('click'); $('.modal-footer button.btn:contains(Save)').trigger('click'); - assert.strictEqual(calendar.$('.o_calendar_filter_item').length, 6, "should add the missing filter (active)"); - assert.strictEqual(calendar.$('.fc-event').length, 4, "should display the created item"); + assert.containsN(calendar, '.o_calendar_filter_item', 6, "should add the missing filter (active)"); + assert.containsN(calendar, '.fc-event', 4, "should display the created item"); calendar.destroy(); $view.remove(); @@ -1763,10 +1763,10 @@ QUnit.module('Views', { }, }); - calendar.$('.o_calendar_filter_item[data-value=4] input').click(); + testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=4] input')); - assert.strictEqual(calendar.$('.o_calendar_filter_item').length, 5, "should display 5 filter items"); - assert.strictEqual(calendar.$('.fc-event').length, 3, "should display 3 events"); + assert.containsN(calendar, '.o_calendar_filter_item', 5, "should display 5 filter items"); + assert.containsN(calendar, '.fc-event', 3, "should display 3 events"); calendar.$('.fc-event:contains(event 2) .fc-content').trigger('click'); $('.modal button.btn:contains(Edit)').trigger('click'); @@ -1774,8 +1774,8 @@ QUnit.module('Views', { $('.ui-menu-item a:contains(user 5)').trigger('mouseenter').trigger('click'); $('.modal button.btn:contains(Save)').trigger('click'); - assert.strictEqual(calendar.$('.o_calendar_filter_item').length, 6, "should add the missing filter (active)"); - assert.strictEqual(calendar.$('.fc-event').length, 3, "should display the updated item"); + assert.containsN(calendar, '.o_calendar_filter_item', 6, "should add the missing filter (active)"); + assert.containsN(calendar, '.fc-event', 3, "should display the updated item"); calendar.destroy(); }); @@ -1842,11 +1842,11 @@ QUnit.module('Views', { }, }); - calendar.$('.o_calendar_filter_item[data-value=4] input').click(); - $('.o_calendar_button_prev').click(); + testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=4] input')); + testUtils.dom.click($('.o_calendar_button_prev')); - assert.strictEqual(calendar.$('.o_calendar_filter_item').length, 6, "should display 6 filter items"); - assert.strictEqual(calendar.$('.fc-event').length, 2, "should display 2 events"); + assert.containsN(calendar, '.o_calendar_filter_item', 6, "should display 6 filter items"); + assert.containsN(calendar, '.fc-event', 2, "should display 2 events"); assert.strictEqual(calendar.$('.fc-event .o_field_name').text().replace(/\s/g, ''), "event7event8", "should display 2 events"); @@ -1868,10 +1868,10 @@ QUnit.module('Views', { }, }); - assert.strictEqual(calendar.$('.fc-event').length, 5, + assert.containsN(calendar, '.fc-event', 5, "should display 5 events"); - calendar.$('.o_calendar_filter_item[data-value=all] input[type=checkbox]').click(); - assert.strictEqual(calendar.$('.fc-event').length, 5, + testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=all] input[type=checkbox]')); + assert.containsN(calendar, '.fc-event', 5, "should display 5 events"); calendar.destroy(); }); @@ -1898,8 +1898,8 @@ QUnit.module('Views', { var top = calendar.$('.fc-axis:contains(0:00)').offset().top + 5; var left = calendar.$('.fc-day:eq(2)').offset().left + 5; try { - testUtils.triggerPositionalMouseEvent(left, top, "mousedown"); - testUtils.triggerPositionalMouseEvent(left, top, "mouseup"); + testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); + testUtils.dom.triggerPositionalMouseEvent(left, top, "mouseup"); } catch (e) { calendar.destroy(); $view.remove(); @@ -1910,7 +1910,7 @@ QUnit.module('Views', { "should open the quick create dialog"); // Creating the event - $('.modal-body input:first').val('new event in quick create').trigger('input'); + testUtils.fields.editInput($('.modal-body input:first'), 'new event in quick create'); $('.modal-footer button.btn:contains(Create)').trigger('click').trigger('click'); assert.strictEqual(calendar.$('.fc-event:contains(new event in quick create)').length, 1, "should display the new record after quick create dialog"); @@ -1950,7 +1950,7 @@ QUnit.module('Views', { } }, }); - assert.strictEqual(calendar.$('.fc-day-grid .fc-event-container').length, 1, + assert.containsOnce(calendar, '.fc-day-grid .fc-event-container', "should be one event in the all day row"); assert.strictEqual(calendar.model.data.data[0].r_start.date(), 14, "the date should be 14"); @@ -1991,16 +1991,16 @@ QUnit.module('Views', { // create a new event var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); - testUtils.triggerMouseEvent($cell, "mousedown"); - testUtils.triggerMouseEvent($cell, "mouseup"); + testUtils.dom.triggerMouseEvent($cell, "mousedown"); + testUtils.dom.triggerMouseEvent($cell, "mouseup"); var $input = $('.modal input:first'); - $input.val('new event in quick create').trigger('input'); + testUtils.fields.editInput($input, 'new event in quick create'); // Simulate ENTER pressed on Create button (after a TAB) $input.trigger($.Event('keyup', { which: $.ui.keyCode.ENTER, keyCode: $.ui.keyCode.ENTER, })); - $('.modal-footer button:first').click(); + testUtils.dom.click($('.modal-footer button:first')); def.resolve(); assert.strictEqual(createCount, 1, "should create only one event"); @@ -2012,7 +2012,7 @@ QUnit.module('Views', { assert.expect(1); var instanceNumber = 0; - testUtils.patch(mixins.ParentedMixin, { + testUtils.mock.patch(mixins.ParentedMixin, { init: function () { instanceNumber++; return this._super.apply(this, arguments); @@ -2062,14 +2062,14 @@ QUnit.module('Views', { calendar.destroy(); - testUtils.unpatch(mixins.ParentedMixin); + testUtils.mock.unpatch(mixins.ParentedMixin); }); QUnit.test('create an event (async dialog) [REQUIRE FOCUS]', function (assert) { assert.expect(3); var def = $.Deferred(); - testUtils.patch(Dialog, { + testUtils.mock.patch(Dialog, { open: function () { var _super = this._super.bind(this); def.then(_super); @@ -2099,8 +2099,8 @@ QUnit.module('Views', { // create an event var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); - testUtils.triggerMouseEvent($cell, "mousedown"); - testUtils.triggerMouseEvent($cell, "mouseup"); + testUtils.dom.triggerMouseEvent($cell, "mousedown"); + testUtils.dom.triggerMouseEvent($cell, "mouseup"); assert.strictEqual($('.modal').length, 0, "should not have opened the dialog yet"); @@ -2113,7 +2113,7 @@ QUnit.module('Views', { "should focus the input in the dialog"); calendar.destroy(); - testUtils.unpatch(Dialog); + testUtils.mock.unpatch(Dialog); }); QUnit.test('calendar is configured to hide the groupby menu', function (assert) { @@ -2178,7 +2178,7 @@ QUnit.module('Views', { assert.strictEqual($sidebar.find('.ui-datepicker-current-day').text(), "12", "should highlight the target day"); // go to previous day - $sidebar.find('.ui-datepicker-current-day').prev().click(); + testUtils.dom.click($sidebar.find('.ui-datepicker-current-day').prev()); assert.strictEqual($sidebar.find('.ui-datepicker-current-day').text(), "11", "should highlight the selected day"); @@ -2223,10 +2223,10 @@ QUnit.module('Views', { }); var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); - testUtils.triggerMouseEvent($cell, "mousedown"); - testUtils.triggerMouseEvent($cell, "mouseup"); + testUtils.dom.triggerMouseEvent($cell, "mousedown"); + testUtils.dom.triggerMouseEvent($cell, "mouseup"); var $input = $('.modal-body input:first'); - $input.val("It's just a fleshwound").trigger('input'); + testUtils.fields.editInput($input, "It's just a fleshwound"); $('.modal button.btn:contains(Create)').trigger('click'); calendar.destroy(); @@ -2270,10 +2270,10 @@ QUnit.module('Views', { }); var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); - testUtils.triggerMouseEvent($cell, "mousedown"); - testUtils.triggerMouseEvent($cell, "mouseup"); + testUtils.dom.triggerMouseEvent($cell, "mousedown"); + testUtils.dom.triggerMouseEvent($cell, "mouseup"); var $input = $('.modal-body input:first'); - $input.val("It's just a fleshwound").trigger('input'); + testUtils.fields.editInput($input, "It's just a fleshwound"); $('.modal button.btn:contains(Create)').trigger('click'); calendar.destroy(); diff --git a/addons/web/static/tests/views/form_benchmarks.js b/addons/web/static/tests/views/form_benchmarks.js index d664859ba2f..c6127f847ec 100644 --- a/addons/web/static/tests/views/form_benchmarks.js +++ b/addons/web/static/tests/views/form_benchmarks.js @@ -85,7 +85,7 @@ QUnit.module('Form View', { '
' ''; this.run(assert, done, function (form) { - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); form.$('.o_data_cell:first').click(); form.$('input:first').val("tralala").trigger('input'); }); diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index ada27d09afc..b73a1438f13 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -144,34 +144,25 @@ QUnit.module('Views', { '', res_id: 2, }); - assert.strictEqual(form.$('div.test').length, 1, - "should contain a div with some html"); - assert.strictEqual(form.$('div.test').css('opacity'), "0.5", - "should keep the inline style on html elements"); - assert.strictEqual(form.$('label:contains(Foo)').length, 1, - "should contain label Foo"); - assert.strictEqual(form.$('span:contains(blip)').length, 1, - "should contain span with field value"); - assert.strictEqual(form.$('.o_group .o_group:first').attr('style'), 'background-color: red', - "should apply style attribute on groups"); - assert.strictEqual(form.$('.o_field_widget[name=foo]').attr('style'), 'color: blue', - "should apply style attribute on fields"); - assert.strictEqual(form.$('label:contains(something_id)').length, 0, - "should not contain f3 string description"); - assert.strictEqual(form.$('label:contains(f3_description)').length, 1, - "should contain custom f3 string description"); - assert.strictEqual(form.$('div.o_field_one2many table').length, 1, - "should render a one2many relation"); + assert.containsOnce(form, 'div.test'); + assert.strictEqual(form.$('div.test').css('opacity'), '0.5', + "should keep the inline style on html elements"); + assert.containsOnce(form, 'label:contains(Foo)'); + assert.containsOnce(form, 'span:contains(blip)'); + assert.hasAttrValue(form.$('.o_group .o_group:first'), 'style', 'background-color: red', + "should apply style attribute on groups"); + assert.hasAttrValue(form.$('.o_field_widget[name=foo]'), 'style', 'color: blue', + "should apply style attribute on fields"); + assert.containsNone(form, 'label:contains(something_id)'); + assert.containsOnce(form, 'label:contains(f3_description)'); + assert.containsOnce(form, 'div.o_field_one2many table'); + assert.containsOnce(form, 'tbody td:not(.o_list_record_selector) .custom-checkbox input:checked'); + assert.strictEqual(form.get('title'), 'second record', + "title should be display_name of record"); + assert.containsNone(form, 'label.o_form_label_empty:contains(timmy)'); - assert.strictEqual(form.$('tbody td:not(.o_list_record_selector) .custom-checkbox input:checked').length, 1, - "1 checkboxes should be checked"); - - assert.strictEqual(form.get('title'), "second record", - "title should be display_name of record"); - assert.strictEqual(form.$('label.o_form_label_empty:contains(timmy)').length, 0, - "the many2many label shouldn't be marked as empty"); form.destroy(); }); @@ -198,7 +189,7 @@ QUnit.module('Views', { '', res_id: 2, }).then(function (form) { - assert.strictEqual(form.$('.o_field_widget[name=foo]').attr('style'), 'color: blue', + assert.hasAttrValue(form.$('.o_field_widget[name=foo]'), 'style', 'color: blue', "should apply style attribute on fields"); form.destroy(); delete fieldRegistry.map.asyncwidget; @@ -220,10 +211,8 @@ QUnit.module('Views', { '', res_id: 2, }); - assert.ok(!form.$('span[name="display_name"]').hasClass('text-danger'), - 'field display name should not have decoration'); - assert.ok(form.$('span[name="foo"]').hasClass('text-danger'), - 'field foo should have decoration'); + assert.doesNotHaveClass(form.$('span[name="display_name"]'), 'text-danger'); + assert.hasClass(form.$('span[name="foo"]'), 'text-danger'); form.destroy(); }); @@ -241,11 +230,9 @@ QUnit.module('Views', { res_id: 2, viewOptions: {mode: 'edit'}, }); - assert.ok(!form.$('input[name="display_name"]').hasClass('text-danger'), - 'field display name should not have decoration'); - form.$('input[name="int_field"]').val(3).trigger('input'); - assert.ok(form.$('input[name="display_name"]').hasClass('text-danger'), - 'field display name should now have decoration'); + assert.doesNotHaveClass(form.$('input[name="display_name"]'), 'text-danger'); + testUtils.fields.editInput(form.$('input[name=int_field]'), 3); + assert.hasClass(form.$('input[name="display_name"]'), 'text-danger'); form.destroy(); }); @@ -262,11 +249,9 @@ QUnit.module('Views', { res_id: 2, viewOptions: {mode: 'edit'}, }); - assert.ok(!form.$('input[name="int_field"]').hasClass('text-danger'), - 'field should not have decoration'); - form.$('input[name="int_field"]').val(3).trigger('input'); - assert.ok(form.$('input[name="int_field"]').hasClass('text-danger'), - 'field should now have decoration'); + assert.doesNotHaveClass(form.$('input[name="int_field"]'), 'text-danger'); + testUtils.fields.editInput(form.$('input[name=int_field]'), 3); + assert.hasClass(form.$('input[name="int_field"]'), 'text-danger'); form.destroy(); }); @@ -311,8 +296,8 @@ QUnit.module('Views', { res_id: 1, }); - assert.strictEqual(form.$('table.o_inner_group').length, 1, - "should contain an inner group"); + assert.containsOnce(form, 'table.o_inner_group'); + form.destroy(); }); @@ -339,14 +324,10 @@ QUnit.module('Views', { res_id: 1, }); - assert.strictEqual(form.$('label.o_invisible_modifier:contains(Foo)').length, 1, - "should not contain label Foo"); - assert.strictEqual(form.$('span.o_invisible_modifier:contains(yop)').length, 1, - "should not contain span with field value"); - assert.strictEqual(form.$('.o_field_widget.o_invisible_modifier:contains(0.4)').length, 1, - "field qux should be invisible"); - assert.ok(form.$('.o_field_widget[name=p]').hasClass('o_invisible_modifier'), - "field p should be invisible"); + assert.containsOnce(form, 'label.o_invisible_modifier:contains(Foo)'); + assert.containsOnce(form, 'span.o_invisible_modifier:contains(yop)'); + assert.containsOnce(form, '.o_field_widget.o_invisible_modifier:contains(0.4)'); + assert.hasClass(form.$('.o_field_widget[name=p]'), 'o_invisible_modifier'); form.destroy(); }); @@ -375,21 +356,17 @@ QUnit.module('Views', { '', res_id: 1, }); - assert.strictEqual(form.$('.o_form_statusbar.o_invisible_modifier button:contains(coucou)').length, 1, - "should not display invisible header"); - assert.strictEqual(form.$('.o_notebook li.o_invisible_modifier a:contains(invisible)').length, 1, - "should not display tab invisible"); - assert.strictEqual(form.$('table.o_inner_group.o_invisible_modifier td:contains(invgroup)').length, 1, - "should not display invisible groups"); + assert.containsOnce(form, '.o_form_statusbar.o_invisible_modifier button:contains(coucou)'); + assert.containsOnce(form, '.o_notebook li.o_invisible_modifier a:contains(invisible)'); + assert.containsOnce(form, 'table.o_inner_group.o_invisible_modifier td:contains(invgroup)'); form.destroy(); }); - QUnit.test('invisible attrs on fields are re-evaluated on field change', function (assert) { + QUnit.test('invisible attrs on fields are re-evaluated on field change', async function (assert) { assert.expect(3); // we set the value bar to simulate a falsy boolean value. this.data.partner.records[0].bar = false; - var form = createView({ View: FormView, model: 'partner', @@ -405,16 +382,14 @@ QUnit.module('Views', { res_id: 1, }); - - form.$buttons.find('.o_form_button_edit').click(); - assert.ok(form.$('.foo_field').hasClass('o_invisible_modifier'), 'should not display foo field'); - assert.ok(form.$('.bar_field').hasClass('o_invisible_modifier'), 'should not display bar field'); + testUtils.form.clickEdit(form); + assert.hasClass(form.$('.foo_field'), 'o_invisible_modifier'); + assert.hasClass(form.$('.bar_field'), 'o_invisible_modifier'); // set a value on the m2o - var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); - form.$('.o_field_many2one input').click(); - $dropdown.find('li:first()').click(); - assert.ok(!form.$('.foo_field').hasClass('o_invisible_modifier'), 'should display foo field'); + await testUtils.fields.many2one.searchAndClickItem('product_id'); + assert.doesNotHaveClass(form.$('.foo_field'), 'o_invisible_modifier'); + form.destroy(); }); @@ -444,8 +419,7 @@ QUnit.module('Views', { '', res_id: 1, }).then(function (form) { - assert.ok(form.$('.o_field_widget[name="int_field"]').hasClass('o_invisible_modifier'), - 'int_field is invisible'); + assert.hasClass(form.$('.o_field_widget[name="int_field"]'), 'o_invisible_modifier'); form.destroy(); delete fieldRegistry.map.asyncwidget; }); @@ -473,10 +447,8 @@ QUnit.module('Views', { res_id: 1, }); - assert.ok(form.$('.o_notebook').hasClass('o_invisible_modifier'), - 'the notebook should handle modifiers (invisible)'); - assert.ok(form.$('.o_notebook').hasClass('new_class'), - 'the notebook should handle attributes'); + assert.hasClass(form.$('.o_notebook'), 'o_invisible_modifier'); + assert.hasClass(form.$('.o_notebook'), 'new_class'); form.destroy(); }); @@ -503,24 +475,16 @@ QUnit.module('Views', { res_id: 1, }); - form.$buttons.find('.o_form_button_edit').click(); - assert.ok(form.$('.o_notebook .nav .nav-link:first()').hasClass('active'), - 'first tab should be active'); - assert.ok(!form.$('.o_notebook .nav .nav-item:first()').hasClass('o_invisible_modifier'), - 'first tab should be visible'); + testUtils.form.clickEdit(form); + assert.hasClass(form.$('.o_notebook .nav .nav-link:first()'), 'active'); + assert.doesNotHaveClass(form.$('.o_notebook .nav .nav-item:first()'), 'o_invisible_modifier'); // set a value on the m2o - var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); - form.$('.o_field_many2one input').click(); - $dropdown.find('li:first()').click(); - assert.ok(!form.$('.o_notebook .nav .nav-link:first()').hasClass('active'), - 'first tab should not be active'); - assert.ok(form.$('.o_notebook .nav .nav-item:first()').hasClass('o_invisible_modifier'), - 'first tab should be invisible'); - assert.ok(form.$('.o_notebook .nav .nav-link:nth(1)').hasClass('active'), - 'second tab should be active'); - assert.ok(form.$('.o_notebook .tab-content .tab-pane:nth(1)').hasClass('active'), - 'second page should be active'); + testUtils.fields.many2one.searchAndClickItem('product_id'); + assert.doesNotHaveClass(form.$('.o_notebook .nav .nav-link:first()'), 'active'); + assert.hasClass(form.$('.o_notebook .nav .nav-item:first()'), 'o_invisible_modifier'); + assert.hasClass(form.$('.o_notebook .nav .nav-link:nth(1)'), 'active'); + assert.hasClass(form.$('.o_notebook .tab-content .tab-pane:nth(1)'), 'active'); form.destroy(); }); @@ -549,8 +513,7 @@ QUnit.module('Views', { assert.notOk(form.$('.o_notebook .nav .nav-item:first()').is(':visible'), 'first tab should be invisible'); - assert.ok(form.$('.o_notebook .nav .nav-link:nth(1)').hasClass('active'), - 'second tab should be active'); + assert.hasClass(form.$('.o_notebook .nav .nav-link:nth(1)'), 'active'); form.destroy(); }); @@ -578,10 +541,8 @@ QUnit.module('Views', { res_id: 1, }); - assert.notOk(form.$('.o_notebook .nav .nav-link:first()').hasClass('active'), - 'first tab should not active'); - assert.ok(form.$('.o_notebook .nav .nav-link:nth(1)').hasClass('active'), - 'second tab should be active'); + assert.doesNotHaveClass(form.$('.o_notebook .nav .nav-link:first()'), 'active'); + assert.hasClass(form.$('.o_notebook .nav .nav-link:nth(1)'), 'active'); form.destroy(); }); @@ -606,10 +567,10 @@ QUnit.module('Views', { res_id: 1 }); - assert.strictEqual(form.$('div.o_group:visible').length, 1, "should display the group"); - form.$buttons.find('.o_form_button_edit').click(); - form.$('.o_field_boolean input').click(); - assert.strictEqual(form.$('div.o_group:hidden').length, 1, "should not display the group"); + assert.containsOnce(form, 'div.o_group:visible'); + testUtils.form.clickEdit(form); + testUtils.dom.click('.o_field_boolean input', form); + assert.containsOnce(form, 'div.o_group:hidden'); form.destroy(); }); @@ -633,8 +594,7 @@ QUnit.module('Views', { '', }); - assert.notOk(form.$('div.hello').is(':visible'), - "attrs invisible should have been computed and applied"); + assert.isNotVisible(form.$('div.hello')); form.destroy(); }); @@ -663,16 +623,14 @@ QUnit.module('Views', { res_id: 2, }); - assert.strictEqual(form.$('button.oe_stat_button').length, 2, - "should have 2 stat buttons"); - assert.strictEqual(form.$('button.oe_stat_button.o_invisible_modifier').length, 1, - "should have 1 invisible stat buttons"); + assert.containsN(form, 'button.oe_stat_button', 2); + assert.containsOnce(form, 'button.oe_stat_button.o_invisible_modifier'); var count = 0; - testUtils.intercept(form, "execute_action", function () { + testUtils.mock.intercept(form, "execute_action", function () { count++; }); - form.$('.oe_stat_button').first().click(); + testUtils.dom.click('.oe_stat_button'); assert.strictEqual(count, 1, "should have triggered a execute action"); form.destroy(); }); @@ -695,8 +653,7 @@ QUnit.module('Views', { res_id: 2, }); - assert.strictEqual(form.$('label.o_form_label:contains(customstring)').length, 1, - "should have 1 label with correct string"); + assert.containsOnce(form, 'label.o_form_label:contains(customstring)'); form.destroy(); }); @@ -717,18 +674,18 @@ QUnit.module('Views', { '', res_id: 1, }); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); - assert.strictEqual(form.$('span[name="foo"]').length, 1, + assert.containsOnce(form, 'span[name="foo"]', "the foo field widget should be readonly"); - form.$('.o_field_boolean input').click(); - assert.strictEqual(form.$('input[name="foo"]').length, 1, + testUtils.dom.click(form.$('.o_field_boolean input')); + assert.containsOnce(form, 'input[name="foo"]', "the foo field widget should have been rerendered to now be editable"); - form.$('.o_field_boolean input').click(); - assert.strictEqual(form.$('span[name="foo"]').length, 1, + testUtils.dom.click(form.$('.o_field_boolean input')); + assert.containsOnce(form, 'span[name="foo"]', "the foo field widget should have been rerendered to now be readonly again"); - form.$('.o_field_boolean input').click(); - assert.strictEqual(form.$('input[name="foo"]').length, 1, + testUtils.dom.click(form.$('.o_field_boolean input')); + assert.containsOnce(form, 'input[name="foo"]', "the foo field widget should have been rerendered to now be editable again"); form.destroy(); @@ -763,30 +720,30 @@ QUnit.module('Views', { res_id: 2, }); - assert.strictEqual(form.$('.o_field_widget.o_field_empty').length, 2, + assert.containsN(form, '.o_field_widget.o_field_empty', 2, "should have 2 empty fields with correct class"); - assert.strictEqual(form.$('.o_form_label_empty').length, 2, + assert.containsN(form, '.o_form_label_empty', 2, "should have 2 muted labels (for the empty fieds) in readonly"); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); - assert.strictEqual(form.$('.o_field_empty').length, 1, + assert.containsOnce(form, '.o_field_empty', "in edit mode, only empty readonly fields should have the o_field_empty class"); - assert.strictEqual(form.$('.o_form_label_empty').length, 1, + assert.containsOnce(form, '.o_form_label_empty', "in edit mode, only labels associated to empty readonly fields should have the o_form_label_empty class"); - form.$('input[name="foo"]').val("test").trigger("input"); + testUtils.fields.editInput(form.$('input[name=foo]'), 'test'); - assert.strictEqual(form.$('.o_field_empty').length, 0, + assert.containsNone(form, '.o_field_empty', "after readonly modifier change, the o_field_empty class should have been removed"); - assert.strictEqual(form.$('.o_form_label_empty').length, 0, + assert.containsNone(form, '.o_form_label_empty', "after readonly modifier change, the o_form_label_empty class should have been removed"); - form.$('input[name="foo"]').val("hello").trigger("input"); + testUtils.fields.editInput(form.$('input[name=foo]'), 'hello'); - assert.strictEqual(form.$('.o_field_empty').length, 1, + assert.containsOnce(form, '.o_field_empty', "after value changed to false for a readonly field, the o_field_empty class should have been added"); - assert.strictEqual(form.$('.o_form_label_empty').length, 1, + assert.containsOnce(form, '.o_form_label_empty', "after value changed to false for a readonly field, the o_form_label_empty class should have been added"); form.destroy(); @@ -804,36 +761,33 @@ QUnit.module('Views', { model: 'partner', data: this.data, arch: '
' + - '' + - '' + - '' + - '' + - '' + - '' + + '' + + '' + + '' + + '' + '
', res_id: 2, }); - assert.strictEqual(form.$('.o_field_widget.o_field_empty').length, 2, - "should have 1 empty field with correct class"); - assert.strictEqual(form.$('.o_form_label_empty').length, 2, + assert.containsN(form, '.o_field_widget.o_field_empty', 2) + assert.containsN(form, '.o_form_label_empty', 2, "should have 1 muted label (for the empty fied) in readonly"); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); - assert.strictEqual(form.$('.o_field_empty').length, 0, + assert.containsNone(form, '.o_field_empty', "in edit mode, only empty readonly fields should have the o_field_empty class"); - assert.strictEqual(form.$('.o_form_label_empty').length, 0, + assert.containsNone(form, '.o_form_label_empty', "in edit mode, only labels associated to empty readonly fields should have the o_form_label_empty class"); - form.$('input[name="foo"]').val("readonly").trigger("input"); // display_name is now rerendered as readonly - form.$('input[name="foo"]').val("edit").trigger("input"); // display_name is now rerendered as editable - form.$('input[name="display_name"]').val('1').trigger("input"); // display_name is now set - form.$('input[name="foo"]').val("readonly").trigger("input"); // display_name is now rerendered as readonly + testUtils.fields.editInput(form.$('input[name=foo]'), 'readonly'); + testUtils.fields.editInput(form.$('input[name=foo]'), 'edit'); + testUtils.fields.editInput(form.$('input[name=display_name]'), 'some name'); + testUtils.fields.editInput(form.$('input[name=foo]'), 'readonly'); - assert.strictEqual(form.$('.o_field_empty').length, 0, + assert.containsNone(form, '.o_field_empty', "there still should not be any empty class on fields as the readonly one is now set"); - assert.strictEqual(form.$('.o_form_label_empty').length, 0, + assert.containsNone(form, '.o_form_label_empty', "there still should not be any empty class on labels as the associated readonly field is now set"); form.destroy(); @@ -857,9 +811,9 @@ QUnit.module('Views', { '' + '', }); - assert.strictEqual(form.$('.o_form_label_empty').length, 0, + assert.containsNone(form, '.o_form_label_empty', "no empty class on label"); - assert.strictEqual(form.$('.o_field_empty').length, 0, + assert.containsNone(form, '.o_field_empty', "no empty class on field"); form.destroy(); }); @@ -871,33 +825,24 @@ QUnit.module('Views', { View: FormView, model: 'partner', data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '' + + arch: '' + + '' + '', res_id: 1, }); assert.strictEqual(form.mode, 'readonly', 'form view should be in readonly mode'); - assert.ok(form.$('.o_form_view').hasClass('o_form_readonly'), - 'form view should be .o_form_readonly'); - assert.ok(form.$buttons.find('.o_form_buttons_view').is(':visible'), - 'readonly buttons should be visible'); - assert.ok(!form.$buttons.find('.o_form_buttons_edit').is(':visible'), - 'edit buttons should not be visible'); - form.$buttons.find('.o_form_button_edit').click(); + assert.hasClass(form.$('.o_form_view'), 'o_form_readonly'); + assert.isVisible(form.$buttons.find('.o_form_buttons_view')); + assert.isNotVisible(form.$buttons.find('.o_form_buttons_edit')); + + testUtils.form.clickEdit(form); + assert.strictEqual(form.mode, 'edit', 'form view should be in edit mode'); - assert.ok(form.$('.o_form_view').hasClass('o_form_editable'), - 'form view should be .o_form_editable'); - assert.ok(!form.$('.o_form_view').hasClass('o_form_readonly'), - 'form view should not be .o_form_readonly'); - assert.ok(!form.$buttons.find('.o_form_buttons_view').is(':visible'), - 'readonly buttons should not be visible'); - assert.ok(form.$buttons.find('.o_form_buttons_edit').is(':visible'), - 'edit buttons should be visible'); + assert.hasClass(form.$('.o_form_view'), 'o_form_editable'); + assert.doesNotHaveClass(form.$('.o_form_view'), 'o_form_readonly'); + assert.isNotVisible(form.$buttons.find('.o_form_buttons_view')); + assert.isVisible(form.$buttons.find('.o_form_buttons_edit')); form.destroy(); }); @@ -918,15 +863,15 @@ QUnit.module('Views', { '', res_id: 1, }); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); - assert.strictEqual(form.$('input[name="foo"].o_required_modifier').length, 1, + assert.containsOnce(form, 'input[name="foo"].o_required_modifier', "the foo field widget should be required"); - form.$('.o_field_boolean input').click(); - assert.strictEqual(form.$('input[name="foo"]:not(.o_required_modifier)').length, 1, + testUtils.dom.click('.o_field_boolean input'); + assert.containsOnce(form, 'input[name="foo"]:not(.o_required_modifier)', "the foo field widget should now have been marked as non-required"); - form.$('.o_field_boolean input').click(); - assert.strictEqual(form.$('input[name="foo"].o_required_modifier').length, 1, + testUtils.dom.click('.o_field_boolean input'); + assert.containsOnce(form, 'input[name="foo"].o_required_modifier', "the foo field widget should now have been marked as required again"); form.destroy(); @@ -950,12 +895,11 @@ QUnit.module('Views', { res_id: 1, }); - assert.strictEqual(form.$('span.o_required_modifier').length, 1, - "should have 1 span with o_required_modifier class"); + assert.containsOnce(form, 'span.o_required_modifier', form); - form.$buttons.find('.o_form_button_edit').click(); - assert.strictEqual(form.$('input.o_required_modifier').length, 1, - "in edit mode, should have 1 input with o_required_modifier"); + testUtils.form.clickEdit(form); + assert.containsOnce(form, 'input.o_required_modifier', + "in edit mode, should have 1 input with o_required_modifier"); form.destroy(); }); @@ -980,23 +924,21 @@ QUnit.module('Views', { }, }); - assert.ok(form.$('input[name="qux"]').hasClass('o_required_modifier'), - "qux input is flagged as required"); + assert.hasClass(form.$('input[name="qux"]'), 'o_required_modifier'); assert.strictEqual(form.$('input[name="qux"]').val(), "0.0", "qux input is 0 by default (float field)"); - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickSave(form); - assert.notOk(form.$('input[name="qux"]').hasClass('o_field_invalid'), - "qux input is not displayed as invalid"); + assert.containsNone(form.$('input[name="qux"]'), "should have switched to readonly"); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); - form.$('input[name="qux"]').val("1").trigger('input'); + testUtils.fields.editInput(form.$('input[name=qux]'), '1'); - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickSave(form); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); assert.strictEqual(form.$('input[name="qux"]').val(), "1.0", "qux input is properly formatted"); @@ -1023,8 +965,7 @@ QUnit.module('Views', { res_id: 1, }); - assert.strictEqual(form.$('div.o_horizontal_separator').length, 1, - "should contain a separator div"); + assert.containsOnce(form, 'div.o_horizontal_separator'); form.destroy(); }); @@ -1045,8 +986,9 @@ QUnit.module('Views', { '', res_id: 1, }); - assert.strictEqual(form.$('div.o_horizontal_separator').hasClass('o_invisible_modifier'), true, - "separator div should be hidden"); + + assert.hasClass(form.$('div.o_horizontal_separator'), 'o_invisible_modifier'); + form.destroy(); }); @@ -1062,8 +1004,9 @@ QUnit.module('Views', { arch: '
' + '' + '
' + - '
' + '' + '' + @@ -1077,35 +1020,31 @@ QUnit.module('Views', { return this._super.apply(this, arguments); }, }); - assert.strictEqual(form.$('button.btn i.fa.fa-check').length, 1, - "should contain a button with correct content"); - - assert.strictEqual(form.$('.o_form_statusbar button').length, 2, - "should have 2 buttons in the statusbar"); - assert.strictEqual(form.$('button[name="post"]').length, 1, - "'name' attribute of buttons is transmitted to the rendered html element"); + assert.containsOnce(form, 'button.btn i.fa.fa-check'); + assert.containsN(form, '.o_form_statusbar button', 3); + assert.containsOnce(form, 'button.p[name="post"]:contains(Confirm)'); + assert.containsN(form, '.o_form_statusbar button:visible', 2); - assert.strictEqual(form.$('.o_form_statusbar button:visible').length, 1, - "should have only 1 visible button in the statusbar"); - - testUtils.intercept(form, 'execute_action', function (event) { - assert.strictEqual(event.data.action_data.name, "post", + testUtils.mock.intercept(form, 'execute_action', function (ev) { + assert.strictEqual(ev.data.action_data.name, 'post', "should trigger execute_action with correct method name"); - assert.deepEqual(event.data.env.currentID, 2, "should have correct id in event data"); - event.data.on_success(); - event.data.on_closed(); + assert.deepEqual(ev.data.env.currentID, 2, "should have correct id in ev data"); + ev.data.on_success(); + ev.data.on_closed(); }); rpcCount = 0; - form.$('.o_form_statusbar button.p').click(); + testUtils.dom.click('.o_form_statusbar button.p', form); assert.strictEqual(rpcCount, 1, "should have done 1 rpcs to reload"); - testUtils.intercept(form, 'execute_action', function (event) { - event.data.on_fail(); + testUtils.mock.intercept(form, 'execute_action', function (ev) { + ev.data.on_fail(); }); - form.$('.o_form_statusbar button.s').click(); + testUtils.dom.click('.o_form_statusbar button.s', form); + + assert.strictEqual(rpcCount, 1, + "should have done 1 rpc, because we do not reload anymore if the server action fails"); - assert.strictEqual(rpcCount, 1, "should have done 1 rpc, because we do not reload anymore if the server action fails"); form.destroy(); }); @@ -1142,38 +1081,22 @@ QUnit.module('Views', { res_id: 2, }); - assert.strictEqual(form.$('button[name="0"]').attr('class'), 'btn btn-secondary', - "header buttons without any class should receive the correct classes"); - assert.strictEqual(form.$('button[name="1"]').attr('class'), 'btn btn-primary', - "header buttons with bootstrap primary class should receive the correct classes"); - assert.strictEqual(form.$('button[name="2"]').attr('class'), 'btn btn-primary', - "header buttons with oe_highlight class should receive the correct classes"); - assert.strictEqual(form.$('button[name="3"]').attr('class'), 'btn btn-secondary', - "header buttons with bootstrap default class should receive the correct classes"); - assert.strictEqual(form.$('button[name="4"]').attr('class'), 'btn btn-link', - "header buttons with bootstrap link class should receive the correct classes"); - assert.strictEqual(form.$('button[name="5"]').attr('class'), 'btn btn-link', - "header buttons with oe_link class should receive the correct classes"); - assert.strictEqual(form.$('button[name="6"]').attr('class'), 'btn btn-success', - "header buttons with bootstrap state class should receive the correct classes"); - assert.strictEqual(form.$('button[name="7"]').attr('class'), 'btn o_this_is_a_button btn-secondary', - "header buttons with custom classes should receive the correct classes"); - assert.strictEqual(form.$('button[name="8"]').attr('class'), 'btn btn-secondary', - "sheet header buttons without any class should receive the correct classes"); - assert.strictEqual(form.$('button[name="9"]').attr('class'), 'btn btn-primary', - "sheet buttons with bootstrap primary class should receive the correct classes"); - assert.strictEqual(form.$('button[name="10"]').attr('class'), 'btn btn-primary', - "sheet buttons with oe_highlight class should receive the correct classes"); - assert.strictEqual(form.$('button[name="11"]').attr('class'), 'btn btn-secondary', - "sheet buttons with bootstrap default class should receive the correct classes"); - assert.strictEqual(form.$('button[name="12"]').attr('class'), 'btn btn-link', - "sheet buttons with bootstrap link class should receive the correct classes"); - assert.strictEqual(form.$('button[name="13"]').attr('class'), 'btn btn-link', - "sheet buttons with oe_link class should receive the correct classes"); - assert.strictEqual(form.$('button[name="14"]').attr('class'), 'btn btn-success', - "sheet buttons with bootstrap state class should receive the correct classes"); - assert.strictEqual(form.$('button[name="15"]').attr('class'), 'btn o_this_is_a_button', - "sheet buttons with custom classes should receive the correct classes"); + assert.hasAttrValue(form.$('button[name="0"]'), 'class', 'btn btn-secondary'); + assert.hasAttrValue(form.$('button[name="1"]'), 'class', 'btn btn-primary'); + assert.hasAttrValue(form.$('button[name="2"]'), 'class', 'btn btn-primary'); + assert.hasAttrValue(form.$('button[name="3"]'), 'class', 'btn btn-secondary'); + assert.hasAttrValue(form.$('button[name="4"]'), 'class', 'btn btn-link'); + assert.hasAttrValue(form.$('button[name="5"]'), 'class', 'btn btn-link'); + assert.hasAttrValue(form.$('button[name="6"]'), 'class', 'btn btn-success'); + assert.hasAttrValue(form.$('button[name="7"]'), 'class', 'btn o_this_is_a_button btn-secondary'); + assert.hasAttrValue(form.$('button[name="8"]'), 'class', 'btn btn-secondary'); + assert.hasAttrValue(form.$('button[name="9"]'), 'class', 'btn btn-primary'); + assert.hasAttrValue(form.$('button[name="10"]'), 'class', 'btn btn-primary'); + assert.hasAttrValue(form.$('button[name="11"]'), 'class', 'btn btn-secondary'); + assert.hasAttrValue(form.$('button[name="12"]'), 'class', 'btn btn-link'); + assert.hasAttrValue(form.$('button[name="13"]'), 'class', 'btn btn-link'); + assert.hasAttrValue(form.$('button[name="14"]'), 'class', 'btn btn-success'); + assert.hasAttrValue(form.$('button[name="15"]'), 'class', 'btn o_this_is_a_button'); form.destroy(); }); @@ -1211,14 +1134,14 @@ QUnit.module('Views', { }, }); - testUtils.intercept(form, 'execute_action', function (event) { + testUtils.mock.intercept(form, 'execute_action', function (event) { assert.step('execute_action'); assert.deepEqual(event.data.env.currentID, resID, "execute action should be done on correct record id"); event.data.on_success(); event.data.on_closed(); }); - form.$('.o_form_statusbar button.p').click(); + testUtils.dom.click('.o_form_statusbar button.p', form); assert.verifySteps(['default_get', 'create', 'read', 'execute_action', 'read']); form.destroy(); @@ -1261,14 +1184,14 @@ QUnit.module('Views', { }, }); - testUtils.intercept(form, 'execute_action', function (event) { + testUtils.mock.intercept(form, 'execute_action', function (event) { assert.step('execute_action'); assert.deepEqual(event.data.env.currentID, resID, "execute action should be done on correct record id"); event.data.on_success(); event.data.on_closed(); }); - form.$('.o_form_statusbar button.p').click(); + testUtils.dom.click('.o_form_statusbar button.p', form); assert.verifySteps(['default_get', 'create', 'read', 'execute_action', 'read']); form.destroy(); @@ -1293,17 +1216,17 @@ QUnit.module('Views', { }); assert.strictEqual(form.mode, 'readonly', 'form view should be in readonly mode'); - assert.strictEqual(form.$('span:contains(blip)').length, 1, + assert.containsOnce(form, 'span:contains(blip)', "should contain span with field value"); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); assert.strictEqual(form.mode, 'edit', 'form view should be in edit mode'); - form.$('input').val("tralala").trigger('input'); - form.$buttons.find('.o_form_button_save').click(); + testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); + testUtils.form.clickSave(form); assert.strictEqual(form.mode, 'readonly', 'form view should be in readonly mode'); - assert.strictEqual(form.$('span:contains(tralala)').length, 1, + assert.containsOnce(form, 'span:contains(tralala)', "should contain span with field value"); form.destroy(); }); @@ -1327,17 +1250,16 @@ QUnit.module('Views', { res_id: 2, }); - form.$buttons.find('.o_form_button_edit').click(); - form.$('input').val("tralala").trigger('input'); - form.$buttons.find('.o_form_button_save').click(); - assert.strictEqual(form.$('span:contains(apple)').length, 1, + testUtils.form.clickEdit(form); + testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); + testUtils.form.clickSave(form); + assert.containsOnce(form, 'span:contains(apple)', "should contain span with field value"); form.destroy(); }); QUnit.test('disable buttons until reload data from server', function (assert) { assert.expect(2); - var def; var form = createView({ View: FormView, @@ -1360,22 +1282,17 @@ QUnit.module('Views', { }); var def = $.Deferred(); - form.$buttons.find('.o_form_button_edit').click(); - form.$('input').val("tralala").trigger('input'); - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickEdit(form); + testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); + testUtils.form.clickSave(form); // Save button should be disabled - assert.strictEqual( - form.$buttons.find('.o_form_button_save').attr('disabled'), - 'disabled', 'buttons should be disabled') - + assert.hasAttrValue(form.$buttons.find('.o_form_button_save'), 'disabled', 'disabled'); // Release the 'read' call def.resolve(); // Edit button should be enabled after the reload - assert.strictEqual( - form.$buttons.find('.o_form_button_edit').attr('disabled'), - undefined, 'buttons should be enabled') + assert.hasAttrValue(form.$buttons.find('.o_form_button_edit'), 'disabled', undefined); form.destroy(); }); @@ -1398,14 +1315,14 @@ QUnit.module('Views', { res_id: 2, }); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); - assert.strictEqual(form.$('input').eq(1).val(), "9", + assert.strictEqual(form.$('input[name=int_field]').val(), "9", "should contain input with initial value"); - form.$('input').first().val("tralala").trigger('input'); + testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); - assert.strictEqual(form.$('input').eq(1).val(), "1007", + assert.strictEqual(form.$('input[name=int_field]').val(), "1007", "should contain input with onchange applied"); form.destroy(); }); @@ -1430,15 +1347,15 @@ QUnit.module('Views', { }); - assert.strictEqual(form.$('input').val(), "9", + assert.strictEqual(form.$('input[name=int_field]').val(), "9", "should contain input with initial value"); - form.$('input').val("666").trigger('input'); + testUtils.fields.editInput(form.$('input[name=int_field]'), '666'); - assert.strictEqual(form.$('input').val(), "14", + assert.strictEqual(form.$('input[name=int_field]').val(), "14", "value should have been set to 14 by onchange"); - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_widget[name=int_field]').text(), "14", "value should still be 14"); @@ -1481,8 +1398,8 @@ QUnit.module('Views', { }, }); - form.$buttons.find('.o_form_button_edit').click(); - form.$('input:first').val("tralala").trigger('input'); + testUtils.form.clickEdit(form); + testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); form.destroy(); }); @@ -1519,17 +1436,17 @@ QUnit.module('Views', { }, }); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); // add a o2m row - form.$('.o_field_x2many_list_row_add a').click(); + testUtils.dom.click('.o_field_x2many_list_row_add a'); form.$('.o_field_one2many input:first').focus(); - form.$('.o_field_one2many input:first').val('valid line').trigger('input'); + testUtils.fields.editInput(form.$('.o_field_one2many input[name=display_name]'), 'valid line'); form.$('.o_field_one2many input:last').focus(); - form.$('.o_field_one2many input:last').val('12.4').trigger('input'); + testUtils.fields.editInput(form.$('.o_field_one2many input[name=qux]'), '12.4'); // trigger an onchange by modifying foo - form.$('input[name="foo"]:first').val("tralala").trigger('input'); + testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); form.destroy(); }); @@ -1590,12 +1507,12 @@ QUnit.module('Views', { }); var n = this.data.partner.records.length; - form.$buttons.find('.o_form_button_create').click(); + testUtils.form.clickCreate(form); assert.strictEqual(form.mode, 'edit', 'form view should be in edit mode'); assert.strictEqual(form.$('input:first').val(), "My little Foo Value", "should have correct default_get value"); - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickSave(form); assert.strictEqual(form.mode, 'readonly', 'form view should be in readonly mode'); assert.strictEqual(this.data.partner.records.length, n + 1, "should have created a record"); form.destroy(); @@ -1648,11 +1565,12 @@ QUnit.module('Views', { res_id: 1, }); - assert.ok(!form.sidebar.$el.hasClass('o_hidden'), 'sidebar should be visible'); - form.$buttons.find('.o_form_button_edit').click(); - assert.ok(form.sidebar.$el.hasClass('o_hidden'), 'sidebar should be invisible'); - form.$buttons.find('.o_form_button_cancel').click(); - assert.ok(!form.sidebar.$el.hasClass('o_hidden'), 'sidebar should be visible'); + assert.isVisible(form.sidebar); + testUtils.form.clickEdit(form); + assert.isNotVisible(form.sidebar); + testUtils.form.clickDiscard(form); + assert.isVisible(form.sidebar); + form.destroy(); }); @@ -1675,7 +1593,7 @@ QUnit.module('Views', { }, }); - assert.strictEqual(form.$('input').val(), "default foo value", "should have correct default"); + assert.strictEqual(form.$('input[name=foo]').val(), "default foo value", "should have correct default"); assert.strictEqual(count, 1, "should do only one rpc"); form.destroy(); }); @@ -1710,11 +1628,11 @@ QUnit.module('Views', { return this._super(route, args); }, }); - assert.ok(form.$('td:contains(new foo1)').length, + assert.containsOnce(form, 'td:contains(new foo1)', "should have new foo1 value in one2many"); - assert.ok(form.$('td:contains(new foo2)').length, + assert.containsOnce(form, 'td:contains(new foo2)', "should have new foo2 value in one2many"); - assert.ok(form.$('td:contains(xphone)').length, + assert.containsOnce(form, 'td:contains(xphone)', "should have a cell with the name field 'product_id', set to xphone"); assert.strictEqual(nameGetCount, 1, "should have done only 1 nameget"); form.destroy(); @@ -1767,7 +1685,7 @@ QUnit.module('Views', { assert.strictEqual(form.get('title'), 'first record', "should have the display name of the record as title"); - form.$buttons.find('.o_form_button_create').click(); + testUtils.form.clickCreate(form); assert.strictEqual(form.get('title'), _t("New"), "should have the display name of the record as title"); form.destroy(); @@ -1789,7 +1707,9 @@ QUnit.module('Views', { assert.strictEqual(form.get('title'), 'first record', "should have the display name of the record as title"); - form.sidebar.$('a:contains(Duplicate)').click(); + + testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + testUtils.dom.click(form.sidebar.$('a:contains(Duplicate)')); assert.strictEqual(form.get('title'), 'first record (copy)', "should have duplicated the record"); @@ -1821,7 +1741,8 @@ QUnit.module('Views', { }, }); - form.sidebar.$('a:contains(Duplicate)').click(); + testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + testUtils.dom.click(form.sidebar.$('a:contains(Duplicate)')); form.destroy(); }); @@ -1842,7 +1763,7 @@ QUnit.module('Views', { assert.strictEqual(form.get('title'), 'first record', "should have the display name of the record as title"); - assert.ok(form.sidebar.$('a:contains(Duplicate)').length === 0, + assert.containsNone(form.sidebar, 'a:contains(Duplicate)', "should not contains a 'Duplicate' action"); form.destroy(); }); @@ -1865,8 +1786,8 @@ QUnit.module('Views', { viewOptions: {footerToButtons: true}, }); - assert.ok(form.$buttons.find('button.infooter').length, "footer button should be in footer"); - assert.ok(!form.$('button.infooter').length, "footer button should not be in form"); + assert.containsOnce(form.$buttons, 'button.infooter', "footer button should be in footer"); + assert.containsNone(form, 'button.infooter', "footer button should not be in form"); form.destroy(); }); @@ -1899,20 +1820,19 @@ QUnit.module('Views', { }, }); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); var count = 0; - testUtils.intercept(form, "execute_action", function (event) { + testUtils.mock.intercept(form, "execute_action", function (event) { event.stopPropagation(); count++; }); - form.$('.oe_stat_button').first().click(); + testUtils.dom.click('.oe_stat_button'); assert.strictEqual(count, 1, "should have triggered a execute action"); assert.strictEqual(form.mode, "edit", "form view should be in edit mode"); - - form.$('input').val("tralala").trigger('input'); - form.$('.oe_stat_button').first().click(); + testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); + testUtils.dom.click('.oe_stat_button:first'); assert.strictEqual(form.mode, "edit", "form view should be in edit mode"); assert.strictEqual(count, 2, "should have triggered a execute action"); @@ -1952,10 +1872,10 @@ QUnit.module('Views', { assert.strictEqual(form.getTitle(), 'second record', "should have correct display_name"); - form.$buttons.find('.o_form_button_edit').click(); - form.$('input[name="name"]').val('some other name').trigger('input'); + testUtils.form.clickEdit(form); + testUtils.fields.editInput(form.$('input[name=name]'), 'some other name'); - form.$('.oe_stat_button').first().click(); + testUtils.dom.click('.oe_stat_button'); assert.strictEqual(form.getTitle(), 'GOLDORAK', "should have correct display_name"); @@ -1985,24 +1905,22 @@ QUnit.module('Views', { return this._super(route, args); } }); - testUtils.intercept(form, "execute_action", function () { + testUtils.mock.intercept(form, "execute_action", function () { executeActionCount++; }); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); // make the record dirty - form.$('input').val("tralala").trigger('input'); - + testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); - form.$('button').eq(0).click(); + testUtils.dom.clickFirst(form.$('button')); assert.strictEqual(writeCount, 1, "should have triggered a write"); assert.strictEqual(executeActionCount, 1, "should have triggered a execute action"); - form.$buttons.find('.o_form_button_edit').click(); - form.$('input').val("abcdef").trigger('input'); + testUtils.fields.editInput(form.$('input[name=foo]'), 'abcdef'); - form.$('button').eq(1).click(); + testUtils.dom.click(form.$('button').eq(1)); assert.strictEqual(writeCount, 1, "should not have triggered a write"); assert.strictEqual(executeActionCount, 2, "should have triggered a execute action"); @@ -2035,8 +1953,8 @@ QUnit.module('Views', { }, }); - form.$('input').val("tralala").trigger('input'); // make the record dirty - form.$('button').click(); // click on Save + testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); + testUtils.dom.click(form.$('button')); assert.verifySteps(['read', 'write', 'read', 'execute_action']); form.destroy(); @@ -2055,7 +1973,7 @@ QUnit.module('Views', { '', res_id: 1, }); - assert.strictEqual(form.$('.o_field_widget').length, 1, "should have rendered an abstract field"); + assert.containsOnce(form, '.o_field_widget'); form.destroy(); }); @@ -2081,18 +1999,18 @@ QUnit.module('Views', { res_id: 1, }); - assert.strictEqual(form.$("label.o_form_label").length, 2, "should have rendered only two label"); + assert.containsN(form, "label.o_form_label", 2); assert.strictEqual(form.$("label.o_form_label").first().text(), "Product", "one should be the one for the product field"); assert.strictEqual(form.$("label.o_form_label").eq(1).text(), "Bar", "one should be the one for the bar field"); - assert.strictEqual(form.$('.firstgroup td').first().attr('colspan'), undefined, + assert.hasAttrValue(form.$('.firstgroup td').first(), 'colspan', undefined, "foo td should have a default colspan (1)"); - assert.strictEqual(form.$('.secondgroup tr').length, 2, + assert.containsN(form, '.secondgroup tr', 2, "int_field and qux should have same tr"); - assert.strictEqual(form.$('.secondgroup tr:first td').length, 2, + assert.containsN(form, '.secondgroup tr:first td', 2, "product_id field should be on its own tr"); form.destroy(); }); @@ -2116,8 +2034,7 @@ QUnit.module('Views', { '', res_id: 1, }); - assert.strictEqual(form.$('td:contains(xphone)').length, 1, - "should display the name of the many2one"); + assert.containsOnce(form, 'td:contains(xphone)', "should display the name of the many2one"); form.destroy(); }); @@ -2151,18 +2068,17 @@ QUnit.module('Views', { res_id: 1, }); - assert.strictEqual(form.$('td:contains(gold)').length, 1, + assert.containsOnce(form, 'td:contains(gold)', "should display the name of the many2many on the original form"); - form.$('td:contains(gold)').click(); + testUtils.dom.click(form.$('td:contains(gold)')); - assert.strictEqual($('.modal').length, 1, - 'The partner_type modal should have opened'); - assert.strictEqual($('.modal').find('td:contains(first record)').length, 1, + assert.containsOnce(document.body, '.modal'); + assert.containsOnce($('.modal'), 'td:contains(first record)', "should display the name of the many2many on the modal form"); - $('.modal').find('td:contains(first record)').click(); - assert.strictEqual($('.modal').length, 2, - 'There should be 2 modals (partner on top of partner_type) opened'); + testUtils.dom.click('.modal td:contains(first record)'); + assert.containsN(document.body, '.modal', 2, + "there should be 2 modals (partner on top of partner_type) opened"); form.destroy(); }); @@ -2186,12 +2102,12 @@ QUnit.module('Views', { }); // switch to edit mode - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); assert.strictEqual(form.$('input').val(), 'yop', 'input should contain yop'); // click on discard - form.$buttons.find('.o_form_button_cancel').click(); - assert.ok(!$('.modal:visible').length, 'no confirm modal should be displayed'); + testUtils.form.clickDiscard(form); + assert.containsNone(document.body, '.modal', 'no confirm modal should be displayed'); assert.strictEqual(form.$('.o_field_widget').text(), 'yop', 'field in readonly should display yop'); assert.strictEqual(nbWrite, 0, 'no write RPC should have been done'); @@ -2217,21 +2133,21 @@ QUnit.module('Views', { }); // switch to edit mode and edit the foo field - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); assert.strictEqual(form.$('input').val(), 'yop', 'input should contain yop'); - form.$('input').val('new value').trigger('input'); + testUtils.fields.editInput(form.$('input[name=foo]'), 'new value'); assert.strictEqual(form.$('input').val(), 'new value', 'input should contain new value'); // click on discard and cancel the confirm request - form.$buttons.find('.o_form_button_cancel').click(); - assert.ok($('.modal').length, 'a confirm modal should be displayed'); - $('.modal-footer .btn-secondary').click(); // click on cancel + testUtils.form.clickDiscard(form); + assert.containsOnce(document.body, '.modal', "a confirm modal should be displayed"); + testUtils.dom.click('.modal-footer .btn-secondary'); assert.strictEqual(form.$('input').val(), 'new value', 'input should still contain new value'); // click on discard and confirm - form.$buttons.find('.o_form_button_cancel').click(); - assert.ok($('.modal').length, 'a confirm modal should be displayed'); - $('.modal-footer .btn-primary').click(); // click on confirm + testUtils.form.clickDiscard(form); + assert.containsOnce(document.body, '.modal', "a confirm modal should be displayed"); + testUtils.dom.click('.modal-footer .btn-primary'); assert.strictEqual(form.$('.o_field_widget').text(), 'yop', 'field in readonly should display yop'); assert.strictEqual(nbWrite, 0, 'no write RPC should have been done'); @@ -2263,10 +2179,11 @@ QUnit.module('Views', { // really happens (mostly because the datepicker lib need that focus // event to properly focusout the input, otherwise it crashes later on // when the 'blur' event is triggered by the re-rendering) - form.$buttons.find('.o_form_button_cancel').focus().click(); - form.$buttons.find('.o_form_button_save').focus().click(); - assert.strictEqual(form.$('span:contains(2017)').length, 1, - "should have a span with the year somewhere"); + form.$buttons.find('.o_form_button_cancel').focus(); + testUtils.dom.click('.o_form_button_cancel'); + form.$buttons.find('.o_form_button_save').focus(); + testUtils.dom.click('.o_form_button_save'); + assert.containsOnce(form, 'span:contains(2017)'); form.destroy(); }); @@ -2274,7 +2191,6 @@ QUnit.module('Views', { QUnit.test('discard changes on relational data on new record', function (assert) { assert.expect(3); - var nbWrite = 0; var form = createView({ View: FormView, model: 'partner', @@ -2286,12 +2202,6 @@ QUnit.module('Views', { '' + '
' + '', - mockRPC: function (route) { - if (route === '/web/dataset/call_kw/partner/write') { - nbWrite++; - } - return this._super.apply(this, arguments); - }, intercepts: { history_back: function () { assert.ok(true, "should have sent correct event"); @@ -2304,18 +2214,16 @@ QUnit.module('Views', { }, }); - // switch to edit mode and edit the p field - form.$buttons.find('.o_form_button_edit').click(); - form.$('.o_field_x2many_list_row_add a').click(); - form.$('.o_field_many2one input').click(); - var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); - $dropdown.find('li:first()').click(); + // edit the p field + testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + testUtils.fields.many2one.clickOpenDropdown('product_id'); + testUtils.fields.many2one.clickHighlightedItem('product_id'); assert.strictEqual(form.$('input').val(), 'xphone', 'input should contain xphone'); // click on discard and confirm - form.$buttons.find('.o_form_button_cancel').click(); - $('.modal-footer .btn-primary').click(); // click on confirm + testUtils.form.clickDiscard(form); + testUtils.dom.click('.modal-footer .btn-primary'); // click on confirm assert.notOk(form.$el.prop('outerHTML').match('xphone'), "the string xphone should not be present after discarding"); @@ -2339,14 +2247,12 @@ QUnit.module('Views', { } }); - // switch to edit mode and edit the foo field - form.$buttons.find('.o_form_button_edit').click(); + // edit the foo field assert.strictEqual(form.$('input').val(), 'ABC', 'input should contain ABC'); - form.$buttons.find('.o_form_button_cancel').click(); + testUtils.form.clickDiscard(form); - assert.strictEqual($('.modal').length, 0, - 'there should not be a confirm modal'); + assert.containsNone(document.body, '.modal', 'there should not be a confirm modal'); form.destroy(); }); @@ -2374,22 +2280,21 @@ QUnit.module('Views', { }); // edit the foo field - assert.strictEqual(form.$('input').val(), 'ABC', 'input should contain ABC'); - form.$('input').val('DEF').trigger('input'); + assert.strictEqual(form.$('input').val(), 'ABC', 'input should contain ABC'); + testUtils.fields.editInput(form.$('input[name=foo]'), 'DEF'); // discard the changes and check it has properly been discarded - form.$buttons.find('.o_form_button_cancel').click(); - assert.strictEqual($('.modal').length, 1, - 'there should be a confirm modal'); + testUtils.form.clickDiscard(form); + assert.containsOnce(document.body, '.modal', 'there should be a confirm modal'); assert.strictEqual(form.$('input').val(), 'DEF', 'input should be DEF'); - $('.modal-footer .btn-primary').click(); // click on confirm + testUtils.dom.click('.modal-footer .btn-primary'); // click on confirm assert.strictEqual(form.$('input').val(), 'ABC', 'input should now be ABC'); // redirty and discard the field foo (to make sure initial changes haven't been lost) - form.$('input').val('GHI').trigger('input'); - form.$buttons.find('.o_form_button_cancel').click(); + testUtils.fields.editInput(form.$('input[name=foo]'), 'GHI'); + testUtils.form.clickDiscard(form); assert.strictEqual(form.$('input').val(), 'GHI', 'input should be GHI'); - $('.modal-footer .btn-primary').click(); // click on confirm + testUtils.dom.click('.modal-footer .btn-primary') // click on confirm assert.strictEqual(form.$('input').val(), 'ABC', 'input should now be ABC'); form.destroy(); @@ -2406,19 +2311,18 @@ QUnit.module('Views', { res_id: 1, viewOptions: {hasSidebar: true}, }); + testUtils.form.clickEdit(form); + testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); + testUtils.form.clickSave(form); - form.$buttons.find('.o_form_button_edit').click(); - form.$('input').val("tralala").trigger('input'); - form.$buttons.find('.o_form_button_save').click(); - - form.sidebar.$('a:contains(Duplicate)').click(); + testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + testUtils.dom.click(form.sidebar.$('a:contains(Duplicate)')); assert.strictEqual(form.$('input').val(), 'tralala', 'input should contain ABC'); - form.$buttons.find('.o_form_button_cancel').click(); + testUtils.form.clickDiscard(form); - assert.strictEqual($('.modal').length, 0, - 'there should not be a confirm modal'); + assert.containsNone(document.body, '.modal', 'there should not be a confirm modal'); form.destroy(); }); @@ -2449,24 +2353,24 @@ QUnit.module('Views', { assert.strictEqual(form.pager.$('.o_pager_limit').text(), "2", 'pager limit should be 2'); // switch to edit mode - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); assert.strictEqual(form.$('input').val(), 'yop', 'input should contain yop'); // edit the foo field - form.$('input').val('new value').trigger('input'); + testUtils.fields.editInput(form.$('input[name=foo]'), 'new value'); assert.strictEqual(form.$('input').val(), 'new value', 'input should contain new value'); // click on the pager to switch to the next record and cancel the confirm request - form.pager.$('.o_pager_next').click(); // click on next - assert.ok($('.modal').length, 'a confirm modal should be displayed'); - $('.modal-footer .btn-secondary').click(); // click on cancel + testUtils.dom.click('.o_pager_next'); // click on next + assert.containsOnce(document.body, '.modal', 'a confirm modal should be displayed'); + testUtils.dom.click('.modal-footer .btn-secondary'); // click on cancel assert.strictEqual(form.$('input').val(), 'new value', 'input should still contain new value'); assert.strictEqual(form.pager.$('.o_pager_value').text(), "1", 'pager value should still be 1'); // click on the pager to switch to the next record and confirm - form.pager.$('.o_pager_next').click(); // click on next - assert.ok($('.modal').length, 'a confirm modal should be displayed'); - $('.modal-footer .btn-primary').click(); // click on confirm + testUtils.dom.click('.o_pager_next'); // click on next + assert.containsOnce(document.body, '.modal', 'a confirm modal should be displayed'); + testUtils.dom.click('.modal-footer .btn-primary'); // click on confirm assert.strictEqual(form.$('input').val(), 'blip', 'input should contain blip'); assert.strictEqual(form.pager.$('.o_pager_value').text(), "2", 'pager value should be 2'); @@ -2494,43 +2398,43 @@ QUnit.module('Views', { assert.strictEqual(form.pager.$('.o_pager_value').text(), "1", 'pager value should be 1'); // switch to edit mode - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); assert.strictEqual(form.$('input').val(), 'yop', 'input should contain yop'); // edit the foo field - form.$('input').val('new value').trigger('input'); + testUtils.fields.editInput(form.$('input[name=foo]'), 'new value'); assert.strictEqual(form.$('input').val(), 'new value', 'input should contain new value'); - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickSave(form); // click on the pager to switch to the next record and cancel the confirm request - form.pager.$('.o_pager_next').click(); // click on next - assert.strictEqual($('.modal:visible').length, 0, 'no confirm modal should be displayed'); + testUtils.dom.click('.o_pager_next'); // click on next + assert.containsNone(document.body, '.modal:visible', 'no confirm modal should be displayed'); assert.strictEqual(form.pager.$('.o_pager_value').text(), "2", 'pager value should be 2'); - assert.strictEqual(form.$('.o_priority .fa-star-o').length, 2, + assert.containsN(form, '.o_priority .fa-star-o', 2, 'priority widget should have been rendered with correct value'); // edit the value in readonly - form.$('.o_priority .fa-star-o:first').click(); // click on the first star - assert.strictEqual(form.$('.o_priority .fa-star').length, 1, + testUtils.dom.click(form.$('.o_priority .fa-star-o:first')); // click on the first star + assert.containsOnce(form, '.o_priority .fa-star', 'priority widget should have been updated'); - form.pager.$('.o_pager_next').click(); // click on next - assert.strictEqual($('.modal:visible').length, 0, 'no confirm modal should be displayed'); + testUtils.dom.click('.o_pager_next'); // click on next + assert.containsNone(document.body, '.modal:visible', 'no confirm modal should be displayed'); assert.strictEqual(form.pager.$('.o_pager_value').text(), "1", 'pager value should be 1'); // switch to edit mode - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); assert.strictEqual(form.$('input').val(), 'new value', 'input should contain yop'); // edit the foo field - form.$('input').val('wrong value').trigger('input'); + testUtils.fields.editInput(form.$('input[name=foo]'), 'wrong value'); - form.$buttons.find('.o_form_button_cancel').click(); - assert.strictEqual($('.modal').length, 1, 'a confirm modal should be displayed'); - $('.modal-footer .btn-primary').click(); // click on confirm - form.pager.$('.o_pager_next').click(); // click on next + testUtils.form.clickDiscard(form); + assert.containsOnce(document.body, '.modal', 'a confirm modal should be displayed'); + testUtils.dom.click('.modal-footer .btn-primary'); // click on confirm + testUtils.dom.click('.o_pager_next'); // click on next assert.strictEqual(form.pager.$('.o_pager_value').text(), "2", 'pager value should be 2'); form.destroy(); }); @@ -2560,20 +2464,16 @@ QUnit.module('Views', { }); // click on second page tab - form.$('.o_notebook .nav-link:eq(1)').click(); + testUtils.dom.click(form.$('.o_notebook .nav-link:eq(1)')); - assert.notOk(form.$('.o_notebook .nav-link:eq(0)').hasClass('active'), - "first tab should not be active"); - assert.ok(form.$('.o_notebook .nav-link:eq(1)').hasClass('active'), - "second tab should be active"); + assert.doesNotHaveClass(form.$('.o_notebook .nav-link:eq(0)'), 'active'); + assert.hasClass(form.$('.o_notebook .nav-link:eq(1)'), 'active'); // click on the pager to switch to the next record - form.pager.$('.o_pager_next').click(); + testUtils.dom.click('.o_pager_next'); - assert.notOk(form.$('.o_notebook .nav-link:eq(0)').hasClass('active'), - "first tab should not be active"); - assert.ok(form.$('.o_notebook .nav-link:eq(1)').hasClass('active'), - "second tab should be active"); + assert.doesNotHaveClass(form.$('.o_notebook .nav-link:eq(0)'), 'active'); + assert.hasClass(form.$('.o_notebook .nav-link:eq(1)'), 'active'); form.destroy(); }); @@ -2594,21 +2494,22 @@ QUnit.module('Views', { }, }); - assert.ok(form.pager.$el.is(':visible'), "pager should be visible"); + assert.isVisible(form.pager); assert.strictEqual(form.pager.$('.o_pager_value').text(), "1", "current pager value should be 1"); assert.strictEqual(form.pager.$('.o_pager_limit').text(), "2", "current pager limit should be 1"); - form.$buttons.find('.o_form_button_create').click(); - assert.notOk(form.pager.$el.is(':visible'), "pager should not be visible"); - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickCreate(form); + assert.isNotVisible(form.pager); - assert.ok(form.pager.$el.is(':visible'), "pager should be visible"); + testUtils.form.clickSave(form); + assert.isVisible(form.pager); assert.strictEqual(form.pager.$('.o_pager_value').text(), "3", "current pager value should be 3"); assert.strictEqual(form.pager.$('.o_pager_limit').text(), "3", "current pager limit should be 3"); + form.destroy(); }); @@ -2636,7 +2537,7 @@ QUnit.module('Views', { assert.strictEqual(form.mode, 'readonly', 'form view should be in readonly mode'); assert.strictEqual(form.pager.$('.o_pager_value').text(), "1", 'pager value should be 1'); - form.pager.$('.o_pager_next').click(); // click on next + testUtils.dom.click(form.pager.$('.o_pager_next')); assert.strictEqual(form.pager.$('.o_pager_value').text(), "2", 'pager value should be 2'); assert.strictEqual(form.mode, 'readonly', 'form view should be in readonly mode'); @@ -2661,18 +2562,14 @@ QUnit.module('Views', { res_id: 1, }); - assert.strictEqual(form.$('span.foo_field').length, 1, "should have a span foo field"); - - assert.ok(!form.$('span.foo_field').is(':visible'), - "foo field should not be visible"); + assert.containsOnce(form, 'span.foo_field'); + assert.isNotVisible(form.$('span.foo_field')); - form.$buttons.find('.o_form_button_create').click(); + testUtils.form.clickCreate(form); - assert.strictEqual(form.$('input.foo_field').length, 1, - "should have a visible input for foo field"); + assert.containsOnce(form, 'input.foo_field'); + assert.isVisible(form.$('input.foo_field')); - assert.ok(form.$('input.foo_field').is(':visible'), - "foo field should be visible"); form.destroy(); }); @@ -2695,13 +2592,11 @@ QUnit.module('Views', { res_id: 1, }); - assert.strictEqual(form.$('.o_field_empty').length, 1, - 'readonly field should be invisible on an existing record'); + assert.containsOnce(form, '.o_field_empty'); - form.$buttons.find('.o_form_button_create').click(); + testUtils.form.clickCreate(form); - assert.strictEqual(form.$('.o_field_empty').length, 0, - 'readonly field should be visible on a new record'); + assert.containsNone(form, '.o_field_empty'); form.destroy(); }); @@ -2725,10 +2620,8 @@ QUnit.module('Views', { res_id: 1, }); - assert.ok(form.$('.inner_group').hasClass('o_group_col_6'), - "inner groups should have classname 'o_group_col_6'"); - assert.ok(form.$('.inner_div').hasClass('o_group_col_6'), - "divs inside groups should have classname 'o_group_col_6'"); + assert.hasClass(form.$('.inner_group'), 'o_group_col_6'), + assert.hasClass(form.$('.inner_div'), 'o_group_col_6'), form.destroy(); }); @@ -2754,14 +2647,14 @@ QUnit.module('Views', { 'should have a field with foo value for record 1'); assert.ok(!$('.modal:visible').length, 'no confirm modal should be displayed'); - // open sidebar - form.sidebar.$('button.o_dropdown_toggler_btn').click(); - form.sidebar.$('a:contains(Delete)').click(); + // open sidebar and delete + testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + testUtils.dom.click(form.sidebar.$('a:contains(Delete)')); assert.ok($('.modal').length, 'a confirm modal should be displayed'); // confirm the delete - $('.modal-footer button.btn-primary').click(); + testUtils.dom.click($('.modal-footer button.btn-primary')); assert.strictEqual(form.pager.$('.o_pager_value').text(), "1", 'pager value should be 1'); assert.strictEqual(form.pager.$('.o_pager_limit').text(), "2", 'pager limit should be 2'); @@ -2791,14 +2684,14 @@ QUnit.module('Views', { }); // open sidebar, click on delete and confirm - form.sidebar.$('button.o_dropdown_toggler_btn').click(); - form.sidebar.$('a:contains(Delete)').click(); + testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + testUtils.dom.click(form.sidebar.$('a:contains(Delete)')); - testUtils.intercept(form, 'history_back', function () { + testUtils.mock.intercept(form, 'history_back', function () { assert.step('history_back'); }); assert.strictEqual($('.modal').length, 1, 'a confirm modal should be displayed'); - $('.modal-footer button.btn-primary').click(); + testUtils.dom.click($('.modal-footer button.btn-primary')); assert.strictEqual($('.modal').length, 0, 'no confirm modal should be displayed'); assert.verifySteps(['read', 'unlink', 'history_back']); @@ -2833,15 +2726,15 @@ QUnit.module('Views', { }, }); - form.$buttons.find('.o_form_button_save').click(); - assert.ok(form.$('label').hasClass('o_field_invalid'), + testUtils.form.clickSave(form); + assert.hasClass(form.$('label'),'o_field_invalid', "label should be tagged as invalid"); - assert.ok(form.$('input').hasClass('o_field_invalid'), + assert.hasClass(form.$('input'),'o_field_invalid', "input should be tagged as invalid"); - form.$('input').val("tralala").trigger('input'); + testUtils.fields.editInput(form.$('input'), "tralala"); - assert.strictEqual(form.$('.o_field_invalid').length, 0, + assert.containsNone(form, '.o_field_invalid', "nothing should be marked as invalid"); form.destroy(); }); @@ -2869,32 +2762,32 @@ QUnit.module('Views', { res_id: 1, }); - assert.strictEqual(form.$('.o_priority .o_priority_star').length, 2, + assert.containsN(form, '.o_priority .o_priority_star', 2, 'priority widget should have been rendered'); - assert.strictEqual(form.$('.o_priority .fa-star-o').length, 2, + assert.containsN(form, '.o_priority .fa-star-o', 2, 'priority widget should have been rendered with correct value'); // edit the value in readonly - form.$('.o_priority .fa-star-o:first').click(); // click on the first star + testUtils.dom.click(form.$('.o_priority .fa-star-o:first')); assert.strictEqual(nbWrite, 1, 'should have saved directly'); - assert.strictEqual(form.$('.o_priority .fa-star').length, 1, + assert.containsOnce(form, '.o_priority .fa-star', 'priority widget should have been updated'); // switch to edit mode and edit the value again - form.$buttons.find('.o_form_button_edit').click(); - assert.strictEqual(form.$('.o_priority .o_priority_star').length, 2, + testUtils.form.clickEdit(form); + assert.containsN(form, '.o_priority .o_priority_star', 2, 'priority widget should have been correctly rendered'); - assert.strictEqual(form.$('.o_priority .fa-star').length, 1, + assert.containsOnce(form, '.o_priority .fa-star', 'priority widget should have correct value'); - form.$('.o_priority .fa-star-o:first').click(); // click on the second star + testUtils.dom.click(form.$('.o_priority .fa-star-o:first')); assert.strictEqual(nbWrite, 1, 'should not have saved directly'); - assert.strictEqual(form.$('.o_priority .fa-star').length, 2, + assert.containsN(form, '.o_priority .fa-star', 2, 'priority widget should have been updated'); // save - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickSave(form); assert.strictEqual(nbWrite, 2, 'should not have saved directly'); - assert.strictEqual(form.$('.o_priority .fa-star').length, 2, + assert.containsN(form, '.o_priority .fa-star', 2, 'priority widget should have correct value'); form.destroy(); }); @@ -2936,12 +2829,12 @@ QUnit.module('Views', { }, }); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); assert.strictEqual(form.$('input[name=int_field]').val(), '9', "'int_field' value should be 9 before the change"); - form.$('input').first().val("tralala").trigger('input'); + testUtils.fields.editInput(form.$('input').first(), "tralala"); assert.strictEqual(form.$('input[name=int_field]').val(), '10', "the onchange should have been correctly applied"); @@ -3022,9 +2915,9 @@ QUnit.module('Views', { }); // go to edit mode, click to add a record in the o2m - form.$buttons.find('.o_form_button_edit').click(); - form.$('.o_field_x2many_list_row_add a').click(); - assert.strictEqual(form.$('tr.o_data_row').length, 0, + testUtils.form.clickEdit(form); + testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + assert.containsNone(form, 'tr.o_data_row', "should not have added a line"); form.destroy(); }); @@ -3095,7 +2988,7 @@ QUnit.module('Views', { assert.strictEqual(form.$('.o_stat_text.o_not_hover:contains(Production Environment)').length, 1, "button should contain correct string"); - form.$buttons.find('.o_form_button_create').click(); + testUtils.form.clickCreate(form); assert.strictEqual(form.$('.o_stat_text.o_not_hover:contains(Test Environment)').length, 1, "button should contain correct string"); @@ -3121,19 +3014,19 @@ QUnit.module('Views', { }); // readonly mode - assert.strictEqual(form.$('.oe_stat_button').length, 1, + assert.containsOnce(form, '.oe_stat_button', "button box should be displayed in readonly"); // edit mode - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); - assert.strictEqual(form.$('.oe_stat_button').length, 1, + assert.containsOnce(form, '.oe_stat_button', "button box should be displayed in edit on an existing record"); // create mode (leave edition first!) - form.$buttons.find('.o_form_button_cancel').click(); - form.$buttons.find('.o_form_button_create').click(); - assert.strictEqual(form.$('.oe_stat_button').length, 1, + testUtils.form.clickDiscard(form); + testUtils.form.clickCreate(form); + assert.containsOnce(form, '.oe_stat_button', "button box should be displayed when creating a new record as well"); form.destroy(); @@ -3167,14 +3060,14 @@ QUnit.module('Views', { res_id: 1, }); - assert.strictEqual(form.$('.o_field_one2many .o_data_row').length, 1, + assert.containsOnce(form, '.o_field_one2many .o_data_row', "there should be one one2many record linked at first"); assert.strictEqual(form.$('.o_field_one2many .o_data_row td:first').text(), 'aaa', "the 'display_name' of the one2many record should be correct"); // switch to edit mode - form.$buttons.find('.o_form_button_edit').click(); - form.$('input').val('let us trigger an onchange').trigger('input'); + testUtils.form.clickEdit(form); + testUtils.fields.editInput(form.$('input'), 'let us trigger an onchange'); var $o2m = form.$('.o_field_one2many'); assert.strictEqual($o2m.find('.o_data_row').length, 2, "there should be two linked record"); @@ -3218,9 +3111,9 @@ QUnit.module('Views', { }); assert.strictEqual($('div[name="p"] .o_data_row td').text().trim(), "aaaNo records", "should have proper initial content"); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); - form.$('input').val("tralala").trigger('input'); + testUtils.fields.editInput(form.$('input'), "tralala"); assert.strictEqual($('div[name="p"] .o_data_row td').text().trim(), "goldNo records", "should have proper initial content"); @@ -3246,19 +3139,19 @@ QUnit.module('Views', { res_id: 1, }); - form.$buttons.find('.o_form_button_edit').click(); - form.$('.o_data_cell').first().click(); // edit first row - form.$('input').val('').trigger('input'); - form.$('.fa-trash-o').eq(1).click(); // delete second row + testUtils.form.clickEdit(form); + testUtils.dom.click(form.$('.o_data_cell').first()); + testUtils.fields.editInput(form.$('input'), ''); + testUtils.dom.click(form.$('.fa-trash-o').eq(1)); assert.strictEqual($('.modal').find('.modal-title').first().text(), "Warning", "Clicking out of a dirty line while editing should trigger a warning modal."); - $('.modal').find('.btn-primary').click(); // discard changes + testUtils.dom.click($('.modal').find('.btn-primary')); assert.strictEqual(form.$('.o_data_cell').first().text(), "first record", "Value should have been reset to what it was before editing began."); - assert.strictEqual(form.$('.o_data_row').length, 1, + assert.containsOnce(form, '.o_data_row', "The other line should have been deleted."); form.destroy(); }); @@ -3303,12 +3196,12 @@ QUnit.module('Views', { res_id: 2, }); - assert.strictEqual(form.$('.o_field_many2many .o_data_row').length, 0, + assert.containsNone(form, '.o_field_many2many .o_data_row', "there should be no many2many record linked at first"); // switch to edit mode - form.$buttons.find('.o_form_button_edit').click(); - form.$('input').val('let us trigger an onchange').trigger('input'); + testUtils.form.clickEdit(form); + testUtils.fields.editInput(form.$('input'), 'let us trigger an onchange'); var $m2m = form.$('.o_field_many2many'); assert.strictEqual($m2m.find('.o_data_row').length, 2, "there should be two linked records"); @@ -3317,7 +3210,7 @@ QUnit.module('Views', { assert.strictEqual($m2m.find('.o_data_row:nth(1) td:first').text(), 'silver', "the 'display_name' of the second m2m record should be correctly displayed"); - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickSave(form); assert.verifySteps(['read', 'onchange', 'read', 'write', 'read', 'read']); @@ -3400,12 +3293,12 @@ QUnit.module('Views', { }); // trigger the onchange - form.$('.o_field_widget[name=foo]').val("coucou").trigger('input'); + testUtils.fields.editInput(form.$('.o_field_widget[name=foo]'), "coucou"); // open a subrecord and trigger an onchange readInModal = true; - form.$('.o_data_row .o_data_cell:first').click(); - $('.modal .o_field_widget[name=name]').val("new name").trigger('input'); + testUtils.dom.click(form.$('.o_data_row .o_data_cell:first')); + testUtils.fields.editInput($('.modal .o_field_widget[name=name]'), "new name"); form.destroy(); }); @@ -3443,7 +3336,7 @@ QUnit.module('Views', { assert.strictEqual(form.$('.o_field_widget[name=datetime]').text(), '12/12/2016 12:55:05', "the initial datetime should be correct"); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_widget[name=date] input').val(), '01/25/2017', "the initial date should be correct in edit"); @@ -3451,7 +3344,7 @@ QUnit.module('Views', { '12/12/2016 12:55:05', "the initial datetime should be correct in edit"); // trigger the onchange - form.$('.o_field_widget[name="foo"]').val("coucou").trigger('input'); + testUtils.fields.editInput(form.$('.o_field_widget[name="foo"]'), "coucou"); assert.strictEqual(form.$('.o_field_widget[name=date] input').val(), '12/12/2021', "the initial date should be correct in edit"); @@ -3495,18 +3388,18 @@ QUnit.module('Views', { }, }); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); - form.$('input').first().val("1").trigger('input'); + testUtils.fields.editInput(form.$('input').first(), "1"); assert.strictEqual(onchangeNbr, 0, "no onchange has been called yet"); - form.$('input').first().val("12").trigger('input'); + testUtils.fields.editInput(form.$('input').first(), "12"); assert.strictEqual(onchangeNbr, 0, "no onchange has been called yet"); return waitForFinishedOnChange().then(function () { assert.strictEqual(onchangeNbr, 1, "one onchange has been called"); // add something in the input, then focus another input - form.$('input').first().val("123").trigger('input'); + testUtils.fields.editInput(form.$('input').first(), "123"); form.$('input').first().change(); assert.strictEqual(onchangeNbr, 2, "one onchange has been called immediately"); @@ -3550,22 +3443,22 @@ QUnit.module('Views', { }, }); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); // edit int_field, and check that an onchange has been applied - form.$('input[name="int_field"]').val("123").trigger('input'); + testUtils.fields.editInput(form.$('input[name="int_field"]'), "123"); assert.strictEqual(form.$('input[name="foo"]').val(), "123", "the onchange has been applied"); // enter an invalid value in a float, and check that no onchange has // been applied - form.$('input[name="int_field"]').val("123a").trigger('input'); + testUtils.fields.editInput(form.$('input[name="int_field"]'), "123a"); assert.strictEqual(form.$('input[name="foo"]').val(), "123", "the onchange has not been applied"); // save, and check that the int_field input is marked as invalid - form.$buttons.find('.o_form_button_save').click(); - assert.ok(form.$('input[name="int_field"]').hasClass('o_field_invalid'), + testUtils.form.clickSave(form); + assert.hasClass(form.$('input[name="int_field"]'),'o_field_invalid', "input int_field is marked as invalid"); assert.verifySteps(['read', 'onchange']); @@ -3590,7 +3483,7 @@ QUnit.module('Views', { } } }); - form.$('.o_form_button_update').click(); + testUtils.dom.click(form.$('.o_form_button_update')); }); QUnit.test('onchanges that complete after discarding', function (assert) { @@ -3626,14 +3519,14 @@ QUnit.module('Views', { // go into edit mode assert.strictEqual(form.$('span[name="foo"]').text(), "blip", "field foo should be displayed to initial value"); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); // edit a value - form.$('input').first().val("1234").trigger('input'); + testUtils.fields.editInput(form.$('input').first(), "1234"); // discard changes - form.$buttons.find('.o_form_button_cancel').click(); - $('.modal-footer .btn-primary').click(); + testUtils.form.clickDiscard(form); + testUtils.dom.click($('.modal-footer .btn-primary')); assert.strictEqual(form.$('span[name="foo"]').text(), "blip", "field foo should still be displayed to initial value"); @@ -3670,10 +3563,10 @@ QUnit.module('Views', { }, }); - form.$('input').val("1234").trigger('input'); + testUtils.fields.editInput(form.$('input'), "1234"); // save the value and discard directly - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickSave(form); form.discardChanges(); // Simulate click on breadcrumb assert.strictEqual(form.$('.o_field_widget[name="foo"]').val(), "1234", @@ -3727,8 +3620,8 @@ QUnit.module('Views', { }, }); - form.$buttons.find('.o_form_button_edit').click(); - form.$('input:first').val('trigger an onchange').trigger('input'); + testUtils.form.clickEdit(form); + testUtils.fields.editInput(form.$('input:first'), 'trigger an onchange'); form.destroy(); }); @@ -3788,13 +3681,13 @@ QUnit.module('Views', { }, }); - form.$buttons.find('.o_form_button_edit').click(); - form.$('input:first').val('trigger an onchange').trigger('input'); + testUtils.form.clickEdit(form); + testUtils.fields.editInput(form.$('input:first'), 'trigger an onchange'); assert.strictEqual(form.$('.o_data_row td:first').text(), 'foo changed', "onchange should have been correctly applied on field in o2m list"); - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickSave(form); form.destroy(); }); @@ -3844,17 +3737,17 @@ QUnit.module('Views', { }, }); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_data_row td:first').text(), 'My little Foo Value', "the initial value should be the default one"); - form.$('input:first').val('trigger an onchange').trigger('input'); + testUtils.fields.editInput(form.$('input:first'), 'trigger an onchange'); assert.strictEqual(form.$('.o_data_row td:first').text(), 'foo changed', "onchange should have been correctly applied on field in o2m list"); - form.$('.o_data_row').click(); // edit the o2m in the dialog + testUtils.dom.click(form.$('.o_data_row')); assert.strictEqual($('.modal .modal-title').text().trim(), 'Open: one2many field', "the field string is displayed in the modal title"); assert.strictEqual($('.modal .o_field_widget').val(), 'foo changed', @@ -3892,19 +3785,19 @@ QUnit.module('Views', { res_id: 2, }); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_data_row td:first').text(), 'My little Foo Value', "the initial value should be the default one"); - form.$('.o_data_row td:nth(1)').click(); // edit the o2m inline - form.$('.o_data_row input:nth(1)').val(77).trigger('input'); + testUtils.dom.click(form.$('.o_data_row td:nth(1)')); + testUtils.fields.editInput(form.$('.o_data_row input:nth(1)'), 77); assert.strictEqual(form.$('.o_data_row input:first').val(), '[blip] 77', "onchange should have been correctly applied"); // create a new o2m record - form.$('.o_field_x2many_list_row_add a').click(); + testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual(form.$('.o_data_row input:first').val(), '[blip] 14', "onchange should have been correctly applied after default get"); @@ -3943,26 +3836,26 @@ QUnit.module('Views', { res_id: 2, }); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_data_row td:first').text(), 'My little Foo Value', "the initial value should be the default one"); - form.$('.o_data_row td:first').click(); // edit the o2m in a dialog - $('.modal input:nth(1)').val(77).trigger('input'); + testUtils.dom.click(form.$('.o_data_row td:first')); + testUtils.fields.editInput($('.modal input:nth(1)'), 77); assert.strictEqual($('.modal input:first').val(), '[blip] 77', "onchange should have been correctly applied"); - $('.modal-footer .btn-primary').click(); // save the dialog + testUtils.dom.click($('.modal-footer .btn-primary')); assert.strictEqual(form.$('.o_data_row td:first').text(), '[blip] 77', "onchange should have been correctly applied"); // create a new o2m record - form.$('.o_field_x2many_list_row_add a').click(); + testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual($('.modal .modal-title').text().trim(), 'Create custom label', "the custom field label is applied in the modal title"); assert.strictEqual($('.modal input:first').val(), '[blip] 14', "onchange should have been correctly applied after default get"); - $('.modal-footer .btn-primary').click(); // save the dialog + testUtils.dom.clickFirst($('.modal-footer .btn-primary')); assert.strictEqual(form.$('.o_data_row:nth(1) td:first').text(), '[blip] 14', "onchange should have been correctly applied after default get"); @@ -3997,8 +3890,8 @@ QUnit.module('Views', { res_id: 2, }); - form.$buttons.find('.o_form_button_edit').click(); - form.$('input:first').val('coucou').trigger('input'); + testUtils.form.clickEdit(form); + testUtils.fields.editInput(form.$('input:first'), 'coucou'); form.destroy(); }); @@ -4023,7 +3916,7 @@ QUnit.module('Views', { }); // go to edit mode - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); // focus first input, trigger tab form.$('input[name="foo"]').focus(); @@ -4110,7 +4003,7 @@ QUnit.module('Views', { res_id: 1, }); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); form.$('input[name="foo"]').focus(); form.$('input[name="foo"]').trigger($.Event('keydown', {which: $.ui.keyCode.TAB})); assert.strictEqual(form.$('input[name="int_field"]')[0], document.activeElement, @@ -4149,7 +4042,7 @@ QUnit.module('Views', { }, }); - form.$('.oe_stat_button').click(); + testUtils.dom.click(form.$('.oe_stat_button')); form.destroy(); }); @@ -4183,7 +4076,7 @@ QUnit.module('Views', { }, }); - form.$('.oe_stat_button').click(); + testUtils.dom.click(form.$('.oe_stat_button')); form.destroy(); }); @@ -4206,7 +4099,7 @@ QUnit.module('Views', { res_id: 2, }); - assert.strictEqual(form.$('button .o_field_widget').length, 1, + assert.containsOnce(form, 'button .o_field_widget', "a field widget should be display inside the button"); assert.strictEqual(form.$('button .o_field_widget').children().length, 2, "the field widget should have 2 children, the text and the value"); @@ -4235,9 +4128,9 @@ QUnit.module('Views', { assert.strictEqual(form.$('.oe_button_box').children().length, 2, "button box should contain two children"); - assert.strictEqual(form.$('.oe_button_box > .oe_stat_button').length, 1, + assert.containsOnce(form, '.oe_button_box > .oe_stat_button', "button box should only contain one button"); - assert.strictEqual(form.$('.oe_button_box > label').length, 1, + assert.containsOnce(form, '.oe_button_box > label', "button box should only contain one label"); form.destroy(); @@ -4309,7 +4202,7 @@ QUnit.module('Views', { return this._super.apply(this, arguments); }, }); - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickSave(form); form.destroy(); }); @@ -4341,13 +4234,11 @@ QUnit.module('Views', { }); // add a o2m subrecord with a m2m tag - form.$('.o_field_x2many_list_row_add a').click(); - form.$('.o_many2many_tags_cell').click(); - form.$('.o_field_many2one input').click(); - var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); - $dropdown.find('li:first()').click(); // select the first tag + testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + testUtils.fields.many2one.clickOpenDropdown('timmy'); + testUtils.fields.many2one.clickHighlightedItem('timmy'); - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickSave(form); form.destroy(); }); @@ -4390,10 +4281,10 @@ QUnit.module('Views', { }); // add a o2m subrecord - form.$('.o_field_x2many_list_row_add a').click(); - $('.modal-body .o_field_one2many .o_field_x2many_list_row_add a').click(); - $('.modal-body input').val('xtv').trigger('input'); - $('.modal-footer button:first').click(); // save & close + testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + testUtils.dom.click($('.modal-body .o_field_one2many .o_field_x2many_list_row_add a')); + testUtils.fields.editInput($('.modal-body input'), 'xtv'); + testUtils.dom.click($('.modal-footer button:first')); assert.strictEqual($('.modal').length, 0, "dialog should be closed"); @@ -4401,7 +4292,7 @@ QUnit.module('Views', { assert.strictEqual(row.children()[0].textContent, '1 record', "the cell should contains the number of record: 1"); - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickSave(form); form.destroy(); }); @@ -4476,13 +4367,13 @@ QUnit.module('Views', { // bar being set to true, foo shouldn't be readonly and thus its value // could be saved, even if in its field description it is readonly - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); - assert.strictEqual(form.$('input[name="foo"]').length, 1, + assert.containsOnce(form, 'input[name="foo"]', "foo field should be editable"); - form.$('input[name="foo"]').val('New foo value').trigger('input'); + testUtils.fields.editInput(form.$('input[name="foo"]'), 'New foo value'); - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_widget[name=foo]').text(), 'New foo value', "new value for foo field should have been saved"); @@ -4520,8 +4411,7 @@ QUnit.module('Views', { }, }); - form.$buttons.find('.o_form_button_edit').click(); - form.$('.o_field_x2many_list_row_add a').click(); + testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); form.destroy(); }); @@ -4553,7 +4443,7 @@ QUnit.module('Views', { assert.deepEqual(getVisibleButtonTexts(), ["Hello", "World"], "only the first button section should be visible"); - form.$(".o_field_boolean input").click(); + testUtils.dom.click(form.$(".o_field_boolean input")); assert.deepEqual(getVisibleButtonTexts(), ["Foo"], "only the second button section should be visible"); @@ -4588,10 +4478,10 @@ QUnit.module('Views', { }, }); - assert.strictEqual(form.$('footer').length, 0, + assert.containsNone(form, 'footer', "footer should have been moved outside of the form view"); form.reload(); - assert.strictEqual(form.$('footer').length, 0, + assert.containsNone(form, 'footer', "footer should still have been moved outside of the form view"); form.destroy(); }); @@ -4633,7 +4523,7 @@ QUnit.module('Views', { '', res_id: 1, }).then(function (form) { - assert.strictEqual(form.$('.ace_editor').length, 1, + assert.containsOnce(form, '.ace_editor', "should have waited for ace to load its dependencies"); form.destroy(); done(); @@ -4689,7 +4579,7 @@ QUnit.module('Views', { var row = form.$('.o_field_one2many .o_list_view .o_data_row'); assert.strictEqual(row.children()[1].textContent, '1 record', "the cell should contains the number of record: 1"); - row.click(); + testUtils.dom.click(row); var modal_row = $('.modal-body .o_form_sheet .o_field_one2many .o_list_view .o_data_row'); assert.strictEqual(modal_row.children().length, 2, "the row should contains the 2 fields defined in the form view"); @@ -4713,7 +4603,7 @@ QUnit.module('Views', { '', res_id: 1, }); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); assert.strictEqual(document.activeElement, form.$('input[name="foo"]')[0], "foo field should have focus"); @@ -4736,7 +4626,7 @@ QUnit.module('Views', { '', res_id: 1, }); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); assert.strictEqual(document.activeElement, form.$('input[name="foo"]')[0], "foo field should have focus"); @@ -4779,7 +4669,7 @@ QUnit.module('Views', { }, }); - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickSave(form); form.destroy(); }); @@ -4870,7 +4760,7 @@ QUnit.module('Views', { }, }); var row = form.$('.o_field_one2many .o_list_view .o_data_row'); - row.click(); + testUtils.dom.click(row); assert.verifySteps(['read', 'read', 'read'], "there should be 3 read rpcs"); form.destroy(); @@ -4913,9 +4803,9 @@ QUnit.module('Views', { }, }); - form.$buttons.find('.o_form_button_edit').click(); - form.$('input[name="foo"]').val('').trigger("input"); - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickEdit(form); + testUtils.fields.editInput(form.$('input[name="foo"]'), ''); + testUtils.form.clickSave(form); def.resolve(); @@ -4969,14 +4859,15 @@ QUnit.module('Views', { var $customAction = $('.o_web_client .o_control_panel .btn-group .dropdown-menu:last .dropdown-item:nth(2)'); assert.strictEqual($customAction.text().trim(), 'Action partner', "the custom action should have 'Action partner' as name"); - testUtils.intercept(form, 'do_action', function (event) { + testUtils.mock.intercept(form, 'do_action', function (event) { var context = event.data.action.context.__contexts[1]; assert.strictEqual(context.active_id, 1, "the active_id shoud be 1."); assert.deepEqual(context.active_ids, [1], "the active_ids should be an array with 1 inside."); }); - $customAction.click(); + testUtils.dom.click($dropdowns); + testUtils.dom.click($customAction); form.destroy(); }); @@ -5027,9 +4918,9 @@ QUnit.module('Views', { }, }); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); // Open first dialog - form.$('.o_external_button').click(); + testUtils.dom.click(form.$('.o_external_button')); assert.strictEqual($('.modal').length, 1, "One FormViewDialog should be opened"); var $firstModal = $('.modal'); @@ -5039,20 +4930,20 @@ QUnit.module('Views', { "display_name should be correctly displayed"); // Open second dialog - $firstModal.find('.o_field_x2many_list_row_add a').click(); + testUtils.dom.click($firstModal.find('.o_field_x2many_list_row_add a')); assert.strictEqual($('.modal').length, 2, "two FormViewDialogs should be opened"); var $secondModal = $('.modal:nth(1)'); // Add new value - $secondModal.find('input').val('xtv').trigger('input'); - $secondModal.find('.modal-footer button:first').click(); // Save & close + testUtils.fields.editInput($secondModal.find('input'), 'xtv'); + testUtils.dom.click($secondModal.find('.modal-footer button:first')); assert.strictEqual($('.modal').length, 1, "last opened dialog should be closed"); // Check that data in first dialog is correctly updated assert.strictEqual($firstModal.find('tr.o_data_row td').text(), 'xtv', "should have added a line with xtv as new record"); - $firstModal.find('.modal-footer button:first').click(); // Save & close + testUtils.dom.click($firstModal.find('.modal-footer button:first')); form.destroy(); }); @@ -5084,7 +4975,7 @@ QUnit.module('Views', { }, }); - form.$('input:first').click(); // trigger the name_search + testUtils.dom.click(form.$('input:first')); form.destroy(); }); @@ -5117,21 +5008,21 @@ QUnit.module('Views', { assert.strictEqual(form.$('.o_notebook .nav-item:not(.o_invisible_modifier)').length, 2, "both tabs should be visible"); - assert.ok(form.$('.o_notebook .nav-link:first').hasClass('active'), + assert.hasClass(form.$('.o_notebook .nav-link:first'),'active', "first tab should be active"); // click on the pager to switch to the next record - form.pager.$('.o_pager_next').click(); + testUtils.dom.click(form.pager.$('.o_pager_next')); assert.strictEqual(form.$('.o_notebook .nav-item:not(.o_invisible_modifier)').length, 1, "only the second tab should be visible"); - assert.ok(form.$('.o_notebook .nav-item:not(.o_invisible_modifier) .nav-link').hasClass('active'), + assert.hasClass(form.$('.o_notebook .nav-item:not(.o_invisible_modifier) .nav-link'),'active', "the visible tab should be active"); // click on the pager to switch back to the previous record - form.pager.$('.o_pager_previous').click(); + testUtils.dom.click(form.pager.$('.o_pager_previous')); assert.strictEqual(form.$('.o_notebook .nav-item:not(.o_invisible_modifier)').length, 2, "both tabs should be visible again"); - assert.ok(form.$('.o_notebook .nav-link:nth(1)').hasClass('active'), + assert.hasClass(form.$('.o_notebook .nav-link:nth(1)'),'active', "second tab should be active"); form.destroy(); @@ -5155,7 +5046,7 @@ QUnit.module('Views', { } } }); - form.$('a[type="action"]').click(); + testUtils.dom.click(form.$('a[type="action"]')); form.destroy(); }); @@ -5252,14 +5143,14 @@ QUnit.module('Views', { assert.ok(form.$('.o_field_one2many tbody tr:first td:contains(yop)').length, "record 1 should be first"); - form.$buttons.find('.o_form_button_edit').click(); - form.$('.o_field_x2many_list_row_add a').click(); + testUtils.form.clickEdit(form); + testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual($('.modal').length, 1, "FormViewDialog should be opened"); - $('.modal input[name="foo"]').val('xop').trigger("input"); - $('.modal-footer button:eq(1)').click(); // Save & new - $('.modal input[name="foo"]').val('zop').trigger("input"); - $('.modal-footer button:first').click(); // Save & close + testUtils.fields.editInput($('.modal input[name="foo"]'), 'xop') + testUtils.dom.click($('.modal-footer button:eq(1)')); + testUtils.fields.editInput($('.modal input[name="foo"]'), 'zop') + testUtils.dom.click($('.modal-footer button:first')); // client-side sort assert.ok(form.$('.o_field_one2many tbody tr:eq(0) td:contains(zop)').length, @@ -5270,7 +5161,7 @@ QUnit.module('Views', { "record xop should be third"); // server-side sort - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickSave(form); assert.ok(form.$('.o_field_one2many tbody tr:eq(0) td:contains(zop)').length, "record zop should be first"); assert.ok(form.$('.o_field_one2many tbody tr:eq(1) td:contains(yop)').length, @@ -5279,10 +5170,10 @@ QUnit.module('Views', { "record xop should be third"); // client-side sort on edit - form.$buttons.find('.o_form_button_edit').click(); - form.$('.o_field_one2many tbody tr:eq(1) td:contains(yop)').click(); - $('.modal input[name="foo"]').val('zzz').trigger("input"); - $('.modal-footer button:first').click(); // Save + testUtils.form.clickEdit(form); + testUtils.dom.click(form.$('.o_field_one2many tbody tr:eq(1) td:contains(yop)')); + testUtils.fields.editInput($('.modal input[name="foo"]'), 'zzz') + testUtils.dom.click($('.modal-footer button:first')); assert.ok(form.$('.o_field_one2many tbody tr:eq(0) td:contains(zzz)').length, "record zzz should be first"); assert.ok(form.$('.o_field_one2many tbody tr:eq(1) td:contains(zop)').length, @@ -5317,8 +5208,8 @@ QUnit.module('Views', { return this._super.apply(this, arguments); }, }); - form.$buttons.find('.o_form_button_edit').click(); - form.$('div[name="trululu"] input').click(); + testUtils.form.clickEdit(form); + testUtils.dom.click(form.$('div[name="trululu"] input')); form.destroy(); }); @@ -5391,18 +5282,18 @@ QUnit.module('Views', { assert.strictEqual($group4rows.length, 3, "there should be 3 rows in .group_4"); var $group4firstRowTd = $group4rows.eq(0).children('td'); assert.strictEqual($group4firstRowTd.length, 1, "there should be 1 td in first row"); - assert.strictEqual($group4firstRowTd.attr('colspan'), "3", "the first td colspan should be 3"); + assert.hasAttrValue($group4firstRowTd, 'colspan', "3", "the first td colspan should be 3"); assert.strictEqual($group4firstRowTd.attr('style').substr(0, 9), "width: 75", "the first td should be 75% width"); assert.strictEqual($group4firstRowTd.children()[0].tagName, "DIV", "the first td should contain a div"); var $group4secondRowTds = $group4rows.eq(1).children('td'); assert.strictEqual($group4secondRowTds.length, 2, "there should be 2 tds in second row"); - assert.strictEqual($group4secondRowTds.eq(0).attr('colspan'), "2", "the first td colspan should be 2"); + assert.hasAttrValue($group4secondRowTds.eq(0), 'colspan', "2", "the first td colspan should be 2"); assert.strictEqual($group4secondRowTds.eq(0).attr('style').substr(0, 9), "width: 50", "the first td be 50% width"); - assert.strictEqual($group4secondRowTds.eq(1).attr('colspan'), undefined, "the second td colspan should be default one (1)"); + assert.hasAttrValue($group4secondRowTds.eq(1), 'colspan', undefined, "the second td colspan should be default one (1)"); assert.strictEqual($group4secondRowTds.eq(1).attr('style').substr(0, 9), "width: 25", "the second td be 75% width"); var $group4thirdRowTd = $group4rows.eq(2).children('td'); assert.strictEqual($group4thirdRowTd.length, 1, "there should be 1 td in third row"); - assert.strictEqual($group4thirdRowTd.attr('colspan'), "4", "the first td colspan should be 4"); + assert.hasAttrValue($group4thirdRowTd, 'colspan', "4", "the first td colspan should be 4"); assert.strictEqual($group4thirdRowTd.attr('style').substr(0, 10), "width: 100", "the first td should be 100% width"); // Verify .group_3 content @@ -5417,32 +5308,32 @@ QUnit.module('Views', { assert.strictEqual($fieldGroupRows.length, 5, "there should be 5 rows in .field_group"); var $fieldGroupFirstRowTds = $fieldGroupRows.eq(0).children('td'); assert.strictEqual($fieldGroupFirstRowTds.length, 2, "there should be 2 tds in first row"); - assert.ok($fieldGroupFirstRowTds.eq(0).hasClass('o_td_label'), "first td should be a label td"); - assert.strictEqual($fieldGroupFirstRowTds.eq(1).attr('colspan'), "2", "second td colspan should be given colspan (3) - 1 (label)"); + assert.hasClass($fieldGroupFirstRowTds.eq(0),'o_td_label', "first td should be a label td"); + assert.hasAttrValue($fieldGroupFirstRowTds.eq(1), 'colspan', "2", "second td colspan should be given colspan (3) - 1 (label)"); assert.strictEqual($fieldGroupFirstRowTds.eq(1).attr('style').substr(0, 10), "width: 100", "second td width should be 100%"); var $fieldGroupSecondRowTds = $fieldGroupRows.eq(1).children('td'); assert.strictEqual($fieldGroupSecondRowTds.length, 2, "there should be 2 tds in second row"); - assert.strictEqual($fieldGroupSecondRowTds.eq(0).attr('colspan'), undefined, "first td colspan should be default one (1)"); + assert.hasAttrValue($fieldGroupSecondRowTds.eq(0), 'colspan', undefined, "first td colspan should be default one (1)"); assert.strictEqual($fieldGroupSecondRowTds.eq(0).attr('style').substr(0, 9), "width: 33", "first td width should be 33.3333%"); - assert.strictEqual($fieldGroupSecondRowTds.eq(1).attr('colspan'), undefined, "second td colspan should be default one (1)"); + assert.hasAttrValue($fieldGroupSecondRowTds.eq(1), 'colspan', undefined, "second td colspan should be default one (1)"); assert.strictEqual($fieldGroupSecondRowTds.eq(1).attr('style').substr(0, 9), "width: 33", "second td width should be 33.3333%"); var $fieldGroupThirdRowTds = $fieldGroupRows.eq(2).children('td'); // new row as label/field pair colspan is greater than remaining space assert.strictEqual($fieldGroupThirdRowTds.length, 2, "there should be 2 tds in third row"); - assert.ok($fieldGroupThirdRowTds.eq(0).hasClass('o_td_label'), "first td should be a label td"); - assert.strictEqual($fieldGroupThirdRowTds.eq(1).attr('colspan'), undefined, "second td colspan should be default one (1)"); + assert.hasClass($fieldGroupThirdRowTds.eq(0),'o_td_label', "first td should be a label td"); + assert.hasAttrValue($fieldGroupThirdRowTds.eq(1), 'colspan', undefined, "second td colspan should be default one (1)"); assert.strictEqual($fieldGroupThirdRowTds.eq(1).attr('style').substr(0, 9), "width: 50", "second td should be 50% width"); var $fieldGroupFourthRowTds = $fieldGroupRows.eq(3).children('td'); assert.strictEqual($fieldGroupFourthRowTds.length, 1, "there should be 1 td in fourth row"); - assert.strictEqual($fieldGroupFourthRowTds.attr('colspan'), "3", "the td should have a colspan equal to 3"); + assert.hasAttrValue($fieldGroupFourthRowTds, 'colspan', "3", "the td should have a colspan equal to 3"); assert.strictEqual($fieldGroupFourthRowTds.attr('style').substr(0, 10), "width: 100", "the td should have 100% width"); var $fieldGroupFifthRowTds = $fieldGroupRows.eq(4).children('td'); // label/field pair can be put after the 1-colspan span assert.strictEqual($fieldGroupFifthRowTds.length, 3, "there should be 3 tds in fourth row"); assert.strictEqual($fieldGroupFifthRowTds.eq(0).attr('style').substr(0, 9), "width: 50", "the first td should 50% width"); - assert.ok($fieldGroupFifthRowTds.eq(1).hasClass('o_td_label'), "the second td should be a label td"); + assert.hasClass($fieldGroupFifthRowTds.eq(1),'o_td_label', "the second td should be a label td"); assert.strictEqual($fieldGroupFifthRowTds.eq(2).attr('style').substr(0, 9), "width: 50", "the third td should 50% width"); // Verify that one2many list table hasn't been impacted - assert.strictEqual(form.$('.o_field_one2many th:first').attr('style'), undefined, + assert.hasAttrValue(form.$('.o_field_one2many th:first'), 'style', undefined, "o2m list columns should have no width harcoded"); form.destroy(); @@ -5474,7 +5365,7 @@ QUnit.module('Views', { var $group1 = form.$('.group_1'); var $group2 = form.$('.group_2'); - assert.strictEqual(form.$('table.o_inner_group').length, 2, + assert.containsN(form, 'table.o_inner_group', 2, "should contain two inner groups"); assert.strictEqual($group1.find('.o_horizontal_separator').length, 1, "inner group should contain one string separator"); @@ -5527,15 +5418,15 @@ QUnit.module('Views', { }); // Inner group - assert.strictEqual(form.$('.main_inner_group > tbody > tr').length, 2, + assert.containsN(form, '.main_inner_group > tbody > tr', 2, "there should be 2 rows in the group"); - assert.strictEqual(form.$('.main_inner_group > tbody > tr:first > .o_td_label').length, 1, + assert.containsOnce(form, '.main_inner_group > tbody > tr:first > .o_td_label', "there should be only one label in the first row"); - assert.strictEqual(form.$('.main_inner_group > tbody > tr:first .o_field_widget').length, 1, + assert.containsOnce(form, '.main_inner_group > tbody > tr:first .o_field_widget', "there should be only one widget in the first row"); - assert.strictEqual(form.$('.main_inner_group > tbody > tr:last > .o_td_label').length, 2, + assert.containsN(form, '.main_inner_group > tbody > tr:last > .o_td_label', 2, "there should be two labels in the second row"); - assert.strictEqual(form.$('.main_inner_group > tbody > tr:last .o_field_widget').length, 2, + assert.containsN(form, '.main_inner_group > tbody > tr:last .o_field_widget', 2, "there should be two widgets in the second row"); // Outer group @@ -5567,7 +5458,7 @@ QUnit.module('Views', { res_id: 1, }); - form.$('.o_data_row:first').click(); + testUtils.dom.click(form.$('.o_data_row:first')); assert.strictEqual($('.modal .modal-title').first().text().trim(), 'Open: custom label', "modal should use the python field string as title"); @@ -5596,11 +5487,11 @@ QUnit.module('Views', { res_id: 1, }); - form.$buttons.find('.o_form_button_edit').click(); - form.$('input[name="foo"]').val("test").trigger("input"); - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickEdit(form); + testUtils.fields.editInput(form.$('input[name="foo"]'), "test"); + testUtils.form.clickSave(form); - assert.strictEqual(form.$('.o_form_view > .alert > div').length, 1,"should have a translation alert"); + assert.containsOnce(form, '.o_form_view > .alert > div',"should have a translation alert"); form.destroy(); @@ -5651,13 +5542,13 @@ QUnit.module('Views', { }, }); - form.$buttons.find('.o_form_button_edit').click(); - form.$('[name="product_id"] .o_external_button').click(); + testUtils.form.clickEdit(form); + testUtils.dom.click(form.$('[name="product_id"] .o_external_button')); assert.strictEqual($('.modal-body .o_field_translate').length, 1, "there should be a translate button in the modal"); - $('.modal-body .o_field_translate').click(); + testUtils.dom.click($('.modal-body .o_field_translate')); assert.strictEqual(nbTranslateCalls, 1, "should call_button translate once"); @@ -5707,14 +5598,14 @@ QUnit.module('Views', { assert.strictEqual(form.$('.oe_button_box button:not(:disabled)').length, 1, "stat buttons should be enabled"); - form.$('.o_form_statusbar button').click(); + testUtils.dom.clickFirst(form.$('.o_form_statusbar button')); // The unresolved deferred lets us check the state of the buttons assert.strictEqual(form.$buttons.find('button:disabled').length, 4, "control panel buttons should be disabled"); - assert.strictEqual(form.$('.o_form_statusbar button:disabled').length, 2, + assert.containsN(form, '.o_form_statusbar button:disabled', 2, "status bar buttons should be disabled"); - assert.strictEqual(form.$('.oe_button_box button:disabled').length, 1, + assert.containsOnce(form, '.oe_button_box button:disabled', "stat buttons should be disabled"); def.resolve(); @@ -5771,14 +5662,14 @@ QUnit.module('Views', { assert.strictEqual(form.$('.oe_button_box button:not(:disabled)').length, 1, "stat buttons should be enabled"); - form.$('.oe_button_box button').click(); + testUtils.dom.click(form.$('.oe_button_box button')); // The unresolved deferred lets us check the state of the buttons assert.strictEqual(form.$buttons.find('button:disabled').length, 4, "control panel buttons should be disabled"); - assert.strictEqual(form.$('.o_form_statusbar button:disabled').length, 2, + assert.containsN(form, '.o_form_statusbar button:disabled', 2, "status bar buttons should be disabled"); - assert.strictEqual(form.$('.oe_button_box button:disabled').length, 1, + assert.containsOnce(form, '.oe_button_box button:disabled', "stat buttons should be disabled"); def.resolve(); @@ -5821,19 +5712,19 @@ QUnit.module('Views', { }); // click on button, and cancel in confirm dialog - form.$('.o_statusbar_buttons button').click(); + testUtils.dom.click(form.$('.o_statusbar_buttons button')); assert.ok(form.$('.o_statusbar_buttons button').prop('disabled'), 'button should be disabled'); - $('.modal-footer button.btn-secondary').click(); + testUtils.dom.click($('.modal-footer button.btn-secondary')); assert.ok(!form.$('.o_statusbar_buttons button').prop('disabled'), 'button should no longer be disabled'); assert.verifySteps(['default_get']); // click on button, and click on ok in confirm dialog - form.$('.o_statusbar_buttons button').click(); + testUtils.dom.click(form.$('.o_statusbar_buttons button')); assert.verifySteps(['default_get']); - $('.modal-footer button.btn-primary').click(); + testUtils.dom.click($('.modal-footer button.btn-primary')); assert.verifySteps(['default_get', 'create', 'read', 'execute_action']); @@ -5887,12 +5778,12 @@ QUnit.module('Views', { }, }); - form.$('.o_external_button').click(); + testUtils.dom.click(form.$('.o_external_button')); assert.notOk($('.modal .oe_button_box button').attr('disabled'), "stat buttons should be enabled"); - $('.modal .oe_button_box button').click(); + testUtils.dom.click($('.modal .oe_button_box button')); assert.ok($('.modal .oe_button_box button').attr('disabled'), "stat buttons should be disabled"); @@ -5935,10 +5826,10 @@ QUnit.module('Views', { }, }); - form.$buttons.find('.o_form_button_edit').click(); - form.$('input[name="foo"]').val("test").trigger("input"); - form.$buttons.find('.o_form_button_save').click(); - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickEdit(form); + testUtils.fields.editInput(form.$('input[name="foo"]'), "test"); + testUtils.form.clickSave(form); + testUtils.form.clickSave(form); def.resolve(); @@ -5975,13 +5866,13 @@ QUnit.module('Views', { }, }); - form.$buttons.find('.o_form_button_edit').click(); - form.$('input[name="foo"]').val("incorrect value").trigger("input"); - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickEdit(form); + testUtils.fields.editInput(form.$('input[name="foo"]'), "incorrect value"); + testUtils.form.clickSave(form); - form.$('input[name="foo"]').val("correct value").trigger("input"); + testUtils.fields.editInput(form.$('input[name="foo"]'), "correct value"); - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickSave(form); assert.verifySteps([ 'read', // initial read to render the view @@ -6008,7 +5899,7 @@ QUnit.module('Views', { assert.strictEqual(form.$('span[name="foo"]').text(), '***', "password should be displayed with stars"); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); assert.strictEqual(form.$('input[name="foo"]').val(), 'yop', "input value should be the password"); assert.strictEqual(form.$('input[name="foo"]').prop('type'), 'password', @@ -6029,8 +5920,8 @@ QUnit.module('Views', { res_id: 1, }); - form.$buttons.find('.o_form_button_edit').click(); - assert.strictEqual(form.$('input[name="display_name"]').attr('autocomplete'), 'coucou', + testUtils.form.clickEdit(form); + assert.hasAttrValue(form.$('input[name="display_name"]'), 'autocomplete', 'coucou', "attribute autocomplete should be set"); form.destroy(); }); @@ -6071,18 +5962,17 @@ QUnit.module('Views', { }, }); - form.$buttons.find('.o_form_button_edit').click(); - form.$('.o_field_x2many_list_row_add a').click(); + testUtils.form.clickEdit(form); + testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual($('.modal').length, 1, "One FormViewDialog should be opened"); // set a value on the m2o - var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); - $('.modal .o_field_many2one input').click(); - $dropdown.find('li:first()').click(); + testUtils.fields.many2one.clickOpenDropdown('partner_type_id'); + testUtils.fields.many2one.clickHighlightedItem('partner_type_id'); - $('.modal-footer button:eq(1)').click(); // Save & new - $('.modal .o_field_many2one input').click(); - $('.modal-footer button:first').click(); // Save & close + testUtils.dom.click($('.modal-footer button:eq(1)')); + testUtils.dom.click($('.modal .o_field_many2one input')); + testUtils.dom.click($('.modal-footer button:first')); form.destroy(); }); @@ -6119,7 +6009,7 @@ QUnit.module('Views', { assert.strictEqual(form.$('.o_field_widget[name="display_name"]').text(), "Select a model to add a filter.", "should contain an error message saying the model is missing"); - form.$('input[name="model_name"]').val("test").trigger("input"); + testUtils.fields.editInput(form.$('input[name="model_name"]'), "test"); assert.notStrictEqual(form.$('.o_field_widget[name="display_name"]').text(), "Select a model to add a filter.", "should not contain an error message anymore"); form.destroy(); @@ -6171,23 +6061,23 @@ QUnit.module('Views', { }, }); - form.$('.o_field_x2many_list_row_add a').click(); + testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual($('.modal input.o_field_widget[name=foo]').length, 1, 'foo should be editable'); checkOnchange = true; - $('.modal .o_field_widget[name=foo]').val('foo value').trigger('input'); - $('.modal .o_field_widget[name=display_name]').val('readonly').trigger('input'); + testUtils.fields.editInput($('.modal .o_field_widget[name=foo]'), 'foo value'); + testUtils.fields.editInput($('.modal .o_field_widget[name=display_name]'), 'readonly'); assert.strictEqual($('.modal span.o_field_widget[name=foo]').length, 1, 'foo should be readonly'); - $('.modal-footer .btn-primary').click(); // close the modal + testUtils.dom.clickFirst($('.modal-footer .btn-primary')); checkOnchange = false; - form.$('.o_data_row').click(); // re-open previous record + testUtils.dom.click(form.$('.o_data_row')); assert.strictEqual($('.modal .o_field_widget[name=foo]').text(), 'foo value', "the edited value should have been kept"); - $('.modal-footer .btn-primary').click(); // close the modal + testUtils.dom.clickFirst($('.modal-footer .btn-primary')); - form.$buttons.find('.o_form_button_save').click(); // save the record + testUtils.form.clickSave(form); // save the record form.destroy(); }); @@ -6205,13 +6095,13 @@ QUnit.module('Views', { '', }); - assert.ok(form.$('.o_field_widget[name=foo]').hasClass('o_readonly_modifier'), + assert.hasClass(form.$('.o_field_widget[name=foo]'),'o_readonly_modifier', "foo should be readonly in 'Create' mode"); - form.$buttons.find('.o_form_button_save').click(); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickSave(form); + testUtils.form.clickEdit(form); - assert.notOk(form.$('.o_field_widget[name=foo]').hasClass('o_readonly_modifier'), + assert.doesNotHaveClass(form.$('.o_field_widget[name=foo]'), 'o_readonly_modifier', "foo should not be readonly anymore"); form.destroy(); @@ -6246,16 +6136,21 @@ QUnit.module('Views', { }, }); - form.sidebar.$('a:contains(Duplicate)').click(); // duplicate record 1 - assert.strictEqual(form.$('.o_form_editable').length, 1, + // duplicate record 1 + testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + testUtils.dom.click(form.sidebar.$('a:contains(Duplicate)')); + assert.containsOnce(form, '.o_form_editable', "form should be in edit mode"); assert.strictEqual(form.$('.o_field_widget').val(), 'first record (copy)', "duplicated record should have correct name"); - form.$buttons.find('.o_form_button_save').click(); // save duplicated record + testUtils.form.clickSave(form); // save duplicated record + + // delete duplicated record + testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + testUtils.dom.click(form.sidebar.$('a:contains(Delete)')); - form.sidebar.$('a:contains(Delete)').click(); // delete duplicated record assert.strictEqual($('.modal').length, 1, "should have opened a confirm dialog"); - $('.modal-footer .btn-primary').click(); // confirm + testUtils.dom.click($('.modal-footer .btn-primary')); assert.strictEqual(form.$('.o_field_widget').text(), 'first record', "should have come back to previous record"); @@ -6335,11 +6230,11 @@ QUnit.module('Views', { }, }); - form.$('.o_external_button').click(); // open the many2one record in a modal - $('.modal .o_external_button').click(); // in the modal, open the many2one record in another modal + testUtils.dom.click(form.$('.o_external_button')); + testUtils.dom.click($('.modal .o_external_button')); - $('.modal:nth(1) .o_field_widget[name=display_name]').val('new name').trigger('input'); - $('.modal:nth(1) footer .btn-primary').first().click(); // save changes + testUtils.fields.editInput($('.modal:nth(1) .o_field_widget[name=display_name]'), 'new name'); + testUtils.dom.click($('.modal:nth(1) footer .btn-primary').first()); assert.strictEqual($('.modal .o_field_widget[name=trululu] input').val(), 'new name', "record should have been reloaded"); @@ -6380,7 +6275,7 @@ QUnit.module('Views', { context: {hide_bar: true}, }, }); - assert.strictEqual(form.$('.o_list_view thead tr th').length, 1, + assert.containsOnce(form, '.o_list_view thead tr th', "there should be only one column"); form.destroy(); }); @@ -6408,7 +6303,7 @@ QUnit.module('Views', { context: {hide_bar: true}, }, }); - assert.strictEqual(form.$('.o_list_view thead tr th').length, 1, + assert.containsOnce(form, '.o_list_view thead tr th', "there should be only one column"); form.destroy(); }); @@ -6443,11 +6338,11 @@ QUnit.module('Views', { }, viewOptions: {mode: 'edit'}, }); - form.$('.o_external_button').click(); + testUtils.dom.click(form.$('.o_external_button')); assert.strictEqual($('.modal-body .o_form_view .o_list_view .o_data_cell').text(), "yopblip", "table has some initial order"); - $('.modal-body .o_form_view .o_list_view th').click(); + testUtils.dom.click($('.modal-body .o_form_view .o_list_view th')); assert.strictEqual($('.modal-body .o_form_view .o_list_view .o_data_cell').text(), "blipyop", "table is now sorted"); form.destroy(); @@ -6473,7 +6368,7 @@ QUnit.module('Views', { } }); - form.$('.o_form_statusbar .btn-secondary').click(); + testUtils.dom.click(form.$('.o_form_statusbar .btn-secondary')); form.destroy(); }); @@ -6522,7 +6417,7 @@ QUnit.module('Views', { '', }); - assert.strictEqual(form.$('button.o_attachment_button').length, 1, + assert.containsOnce(form, 'button.o_attachment_button', "should have 1 attach_document widget in the statusbar"); assert.strictEqual(form.$('span.o_attach_document').text().trim(), 'Attach document', "widget should have been instantiated"); @@ -6556,7 +6451,7 @@ QUnit.module('Views', { '', }); - form.$('input[name="foo"]').val("I am alive").trigger('input'); + testUtils.fields.editInput(form.$('input[name="foo"]'), "I am alive"); assert.strictEqual(form.$('.o_widget').text(), 'I am alive!', "widget should have been updated"); @@ -6586,12 +6481,12 @@ QUnit.module('Views', { }); // in readonly - form.$('[name="display_name"]').click(); + testUtils.dom.click(form.$('[name="display_name"]')); assert.verifySteps(['bounce']); // in edit - form.$buttons.find('.o_form_button_edit').click(); - form.$('[name="display_name"]').click(); + testUtils.form.clickEdit(form); + testUtils.dom.click(form.$('[name="display_name"]')); assert.verifySteps(['bounce']); form.destroy(); @@ -6653,7 +6548,7 @@ QUnit.module('Views', { }); // switch to edit mode to ensure that autoresize is correctly done - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); var height = form.$('.o_field_widget[name=text_field]').height(); // focus the field to manually trigger autoresize form.$('.o_field_widget[name=text_field]').trigger('focus'); @@ -6664,8 +6559,8 @@ QUnit.module('Views', { assert.ok(height > 80, "textarea should have an height of at least 80px"); // save and create a new record to ensure that autoresize is correctly done - form.$buttons.find('.o_form_button_save').click(); - form.$buttons.find('.o_form_button_create').click(); + testUtils.form.clickSave(form); + testUtils.form.clickCreate(form); height = form.$('.o_field_widget[name=text_field]').height(); // focus the field to manually trigger autoresize form.$('.o_field_widget[name=text_field]').trigger('focus'); @@ -6680,7 +6575,7 @@ QUnit.module('Views', { assert.expect(1); var instanceNumber = 0; - testUtils.patch(mixins.ParentedMixin, { + testUtils.mock.patch(mixins.ParentedMixin, { init: function () { instanceNumber++; return this._super.apply(this, arguments); @@ -6751,7 +6646,7 @@ QUnit.module('Views', { form.destroy(); - testUtils.unpatch(mixins.ParentedMixin); + testUtils.mock.unpatch(mixins.ParentedMixin); }); QUnit.test('do not change pager when discarding current record', function (assert) { @@ -6774,8 +6669,8 @@ QUnit.module('Views', { assert.strictEqual(form.pager.$('.o_pager_counter').text().trim(), '2 / 2', 'pager should indicate that we are on second record'); - form.$buttons.find('.o_form_button_edit').click(); - form.$buttons.find('.o_form_button_cancel').click(); + testUtils.form.clickEdit(form); + testUtils.form.clickDiscard(form); assert.strictEqual(form.pager.$('.o_pager_counter').text().trim(), '2 / 2', 'pager should not have changed'); @@ -6786,7 +6681,7 @@ QUnit.module('Views', { QUnit.test('edition in form view on a "noCache" model', function (assert) { assert.expect(4); - testUtils.patch(BasicModel, { + testUtils.mock.patch(BasicModel, { noCacheModels: BasicModel.prototype.noCacheModels.concat(['partner']), }); @@ -6812,19 +6707,19 @@ QUnit.module('Views', { }); core.bus.on('clear_cache', form, assert.step.bind(assert, 'clear_cache')); - form.$('.o_field_widget[name=display_name]').val('new value').trigger('input'); - form.$buttons.find('.o_form_button_save').click(); + testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'new value'); + testUtils.form.clickSave(form); assert.verifySteps(['write', 'clear_cache']); form.destroy(); - testUtils.unpatch(BasicModel); + testUtils.mock.unpatch(BasicModel); }); QUnit.test('creation in form view on a "noCache" model', function (assert) { assert.expect(4); - testUtils.patch(BasicModel, { + testUtils.mock.patch(BasicModel, { noCacheModels: BasicModel.prototype.noCacheModels.concat(['partner']), }); @@ -6846,19 +6741,19 @@ QUnit.module('Views', { }); core.bus.on('clear_cache', form, assert.step.bind(assert, 'clear_cache')); - form.$('.o_field_widget[name=display_name]').val('value').trigger('input'); - form.$buttons.find('.o_form_button_save').click(); + testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'value'); + testUtils.form.clickSave(form); assert.verifySteps(['create', 'clear_cache']); form.destroy(); - testUtils.unpatch(BasicModel); + testUtils.mock.unpatch(BasicModel); }); QUnit.test('deletion in form view on a "noCache" model', function (assert) { assert.expect(4); - testUtils.patch(BasicModel, { + testUtils.mock.patch(BasicModel, { noCacheModels: BasicModel.prototype.noCacheModels.concat(['partner']), }); @@ -6877,19 +6772,21 @@ QUnit.module('Views', { } return this._super.apply(this, arguments); }, + res_id: 1, viewOptions: { hasSidebar: true, }, }); core.bus.on('clear_cache', form, assert.step.bind(assert, 'clear_cache')); - form.sidebar.$('a:contains(Delete)').click(); - $('.modal-footer .btn-primary').click(); // confirm + testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + testUtils.dom.click(form.sidebar.$('a:contains(Delete)')); + testUtils.dom.click($('.modal-footer .btn-primary')); assert.verifySteps(['unlink', 'clear_cache']); form.destroy(); - testUtils.unpatch(BasicModel); + testUtils.mock.unpatch(BasicModel); }); QUnit.test('a popup window should automatically close after a do_action event', function (assert) { @@ -6926,10 +6823,10 @@ QUnit.module('Views', { }, }); // Open one2many - form.$('.o_data_row').click(); + testUtils.dom.click(form.$('.o_data_row')); assert.strictEqual($('.modal-content').length, 1, "a popup window should have opened"); // Click on many2one and trigger do_action - $('.modal-content a[name="partner_type_id"]').click(); + testUtils.dom.click($('.modal-content a[name="partner_type_id"]')); assert.strictEqual($('.modal-content').length, 0, "the popup window should have closed"); form.destroy(); @@ -6976,11 +6873,11 @@ QUnit.module('Views', { }, }); // Open two one2manys - form.$('.o_data_row').click(); - $('.modal-content .o_data_row').click(); + testUtils.dom.click(form.$('.o_data_row')); + testUtils.dom.click($('.modal-content .o_data_row')); assert.strictEqual($('.modal-content').length, 2, "Two popup windows should have opened."); // Click on many2one and trigger do_action - $('.modal-content a[name="partner_type_id"]').click(); + testUtils.dom.click($('.modal-content a[name="partner_type_id"]')); assert.strictEqual($('.modal-content').length, 0, "All popup windows should have closed."); form.destroy(); @@ -7070,7 +6967,7 @@ QUnit.module('Views', { '' + '', }).then(function(form) { - assert.strictEqual(form.$('div.o_widget').length, 1, + assert.containsOnce(form, 'div.o_widget', "there should be a div with widget class"); form.destroy(); delete widgetRegistry.map.test; @@ -7106,12 +7003,12 @@ QUnit.module('Views', { fieldDebounce: 5000, }); - form.$('input').val('some foo value').trigger('input'); + testUtils.fields.editInput(form.$('input'), 'some foo value'); // manually save the record, to prevent the field widget to notify the model of its new // value before being requested to form.saveRecord(); - assert.strictEqual(form.$('.o_form_readonly').length, 1, + assert.containsOnce(form, '.o_form_readonly', "form view should be in readonly"); assert.strictEqual(form.$el.text().trim(), 'some foo value', "foo field should have correct value"); @@ -7161,7 +7058,7 @@ QUnit.module('Views', { }, }); - form.$('.o_field_x2many_list_row_add a').click(); + testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual(form.$('.o_field_widget[name=display_name]').val(), '', "display_name should be the empty string by default"); @@ -7170,9 +7067,9 @@ QUnit.module('Views', { onchangeDef = $.Deferred(); // delay the onchange - form.$('.o_field_widget[name=display_name]').val('some value').trigger('input'); + testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'some value'); - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickSave(form); assert.step('resolve'); onchangeDef.resolve(); @@ -7201,13 +7098,13 @@ QUnit.module('Views', { '', }); - form.$('input[name=display_name]').click(); + testUtils.dom.click(form.$('input[name=display_name]')); assert.strictEqual(form.$('input[name="display_name"]')[0], document.activeElement, "display_name should be focused"); form.$('input[name="display_name"]').trigger($.Event('keydown', {which: $.ui.keyCode.TAB})); assert.strictEqual(form.$('input[name="display_name"]')[0], document.activeElement, "display_name should still be focused because it is empty and required"); - assert.strictEqual(form.$('input[name="display_name"]').hasClass("o_field_invalid"), true, + assert.hasClass(form.$('input[name="display_name"]'), 'o_field_invalid', "display_name should have the o_field_invalid class"); form.destroy(); }); @@ -7229,7 +7126,7 @@ QUnit.module('Views', { '', }); - form.$('input[name=date]').click(); + testUtils.dom.click(form.$('input[name=date]')); assert.strictEqual(form.$('input[name="date"]')[0], document.activeElement, "display_name should be focused"); form.$('input[name="date"]').trigger($.Event('keydown', {which: $.ui.keyCode.TAB})); @@ -7255,7 +7152,7 @@ QUnit.module('Views', { }); // in edit - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); form.$('input[name="display_name"]').focus().trigger($.Event('keydown', {which: $.ui.keyCode.TAB})); assert.strictEqual(form.$buttons.find('.btn-primary:visible')[0], document.activeElement, "the first primary button (save) should be focused"); @@ -7401,7 +7298,7 @@ QUnit.module('Views', { }, }); - form.$('div[name="bar"]>input').click(); + testUtils.dom.click(form.$('div[name="bar"]>input')); form.$('input[name="foo"]').focus(); $(document.activeElement).trigger($.Event('keydown', {which: $.ui.keyCode.TAB})); diff --git a/addons/web/static/tests/views/graph_tests.js b/addons/web/static/tests/views/graph_tests.js index 6dcafc20072..30d295e1f8f 100644 --- a/addons/web/static/tests/views/graph_tests.js +++ b/addons/web/static/tests/views/graph_tests.js @@ -73,7 +73,7 @@ QUnit.module('Views', { var done = assert.async(); return concurrency.delay(0).then(function () { - assert.strictEqual(graph.$('div.o_graph_svg_container svg.nvd3-svg').length, 1, + assert.containsOnce(graph, 'div.o_graph_svg_container svg.nvd3-svg', "should contain a div with a svg element"); assert.strictEqual(graph.renderer.state.mode, "bar", @@ -134,15 +134,15 @@ QUnit.module('Views', { '', }); assert.strictEqual(graph.renderer.state.mode, "line", "should be in line chart mode by default"); - assert.notOk(graph.$buttons.find('button[data-mode="bar"]').hasClass('active'), + assert.doesNotHaveClass(graph.$buttons.find('button[data-mode="bar"]'), 'active', 'bar type button should not be active'); - assert.ok(graph.$buttons.find('button[data-mode="line"]').hasClass('active'), + assert.hasClass(graph.$buttons.find('button[data-mode="line"]'),'active', 'line type button should be active'); - graph.$buttons.find('button[data-mode="bar"]').click(); + testUtils.dom.click(graph.$buttons.find('button[data-mode="bar"]')); assert.strictEqual(graph.renderer.state.mode, "bar", "should be in bar chart mode by default"); - assert.notOk(graph.$buttons.find('button[data-mode="line"]').hasClass('active'), + assert.doesNotHaveClass(graph.$buttons.find('button[data-mode="line"]'), 'active', 'line type button should not be active'); - assert.ok(graph.$buttons.find('button[data-mode="bar"]').hasClass('active'), + assert.hasClass(graph.$buttons.find('button[data-mode="bar"]'),'active', 'bar type button should be active'); graph.destroy(); }); @@ -214,7 +214,8 @@ QUnit.module('Views', { "should have used the correct measure"); assert.ok(graph.$buttons.find('.dropdown-item[data-field="foo"]').length, "should have foo in the list of measures"); - graph.$buttons.find('.dropdown-item[data-field="foo"]').click(); + testUtils.dom.click(graph.$buttons.find('.dropdown-toggle:contains(Measures)')); + testUtils.dom.click(graph.$buttons.find('.dropdown-item[data-field="foo"]')); return concurrency.delay(0); }).then(function () { @@ -238,9 +239,9 @@ QUnit.module('Views', { '' + '', }); - assert.strictEqual(graph.$('div.o_graph_svg_container svg.nvd3-svg').length, 0, + assert.containsNone(graph, 'div.o_graph_svg_container svg.nvd3-svg', "should not contain a div with a svg element"); - assert.strictEqual(graph.$('div.o_view_nocontent').length, 1, + assert.containsOnce(graph, 'div.o_view_nocontent', "should display the no content helper"); graph.destroy(); }); @@ -257,9 +258,9 @@ QUnit.module('Views', { '' + '', }); - assert.strictEqual(graph.$('div.o_graph_svg_container svg.nvd3-svg').length, 0, + assert.containsNone(graph, 'div.o_graph_svg_container svg.nvd3-svg', "should not contain a div with a svg element"); - assert.strictEqual(graph.$('div.o_view_nocontent').length, 1, + assert.containsOnce(graph, 'div.o_view_nocontent', "should display the no content helper"); graph.destroy(); }); @@ -286,7 +287,7 @@ QUnit.module('Views', { '', }); - assert.strictEqual(graph.$('div.o_view_nocontent').length, 0, + assert.containsNone(graph, 'div.o_view_nocontent', "should not display the no content helper"); assert.strictEqual($('.o_graph_svg_container svg > text').text(), "No data to displayNo data to display", "should display two empty pie charts instead"); @@ -310,7 +311,7 @@ QUnit.module('Views', { "should contain a div with a svg element"); assert.notOk(graph.$('div.o_view_nocontent').length, "should not display the no content helper"); - graph.update({domain: [['product_id', '=', 4]]}); + testUtils.graph.reload(graph, {domain: [['product_id', '=', 4]]}); assert.notOk(graph.$('div.o_graph_svg_container svg.nvd3-svg').length, "should not contain a div with a svg element"); @@ -339,7 +340,7 @@ QUnit.module('Views', { assert.notOk(graph.$('text:contains(red)').length, "should not contain a text element with color in legend"); - graph.update({groupBy: ['color_id']}); + testUtils.graph.reload(graph, {groupBy: ['color_id']}); return concurrency.delay(0); }).then(function () { @@ -372,7 +373,8 @@ QUnit.module('Views', { graph_intervalMapping: {}, }, "context should be correct"); - graph.$buttons.find('.dropdown-item[data-field="foo"]').click(); // change measure + testUtils.dom.click(graph.$buttons.find('.dropdown-toggle:contains(Measures)')); + testUtils.dom.click(graph.$buttons.find('.dropdown-item[data-field="foo"]')); return concurrency.delay(0); }).then(function () { @@ -383,7 +385,7 @@ QUnit.module('Views', { graph_intervalMapping: {}, }, "context should be correct"); - graph.$buttons.find('button[data-mode="line"]').click(); // change mode + testUtils.dom.click(graph.$buttons.find('button[data-mode="line"]')); return concurrency.delay(0); }).then(function () { @@ -394,7 +396,7 @@ QUnit.module('Views', { graph_intervalMapping: {}, }, "context should be correct"); - graph.update({groupBy: ['product_id', 'color_id']}); // change groupbys + testUtils.graph.reload(graph, {groupBy: ['product_id', 'color_id']}); // change groupbys return concurrency.delay(0); }).then(function () { @@ -413,7 +415,7 @@ QUnit.module('Views', { QUnit.test('correctly uses graph_ keys from the context', function (assert) { var done = assert.async(); assert.expect(6); - + var lastOne = _.last(this.data.foo.records); lastOne.color_id = 14; @@ -437,9 +439,9 @@ QUnit.module('Views', { // check mode assert.strictEqual(graph.renderer.state.mode, "line", "should be in line chart mode"); - assert.notOk(graph.$buttons.find('button[data-mode="bar"]').hasClass('active'), + assert.doesNotHaveClass(graph.$buttons.find('button[data-mode="bar"]'), 'active', 'bar chart button should not be active'); - assert.ok(graph.$buttons.find('button[data-mode="line"]').hasClass('active'), + assert.hasClass(graph.$buttons.find('button[data-mode="line"]'),'active', 'line chart button should be active'); // check groupbys @@ -498,7 +500,7 @@ QUnit.module('Views', { }); assert.strictEqual(graph.renderer.state.mode, "bar", "should be in bar chart mode"); - assert.ok(graph.$buttons.find('button[data-mode="bar"]').hasClass('active'), + assert.hasClass(graph.$buttons.find('button[data-mode="bar"]'),'active', 'bar chart button should be active'); var reloadParams = { @@ -508,7 +510,7 @@ QUnit.module('Views', { graph_groupbys: ['color_id'], }, }; - graph.reload(reloadParams); + testUtils.graph.reload(graph, reloadParams); return concurrency.delay(0).then(function () { // check measure assert.strictEqual(graph.$('text.nv-legend-text:contains(Foo)').length, 1, @@ -516,9 +518,9 @@ QUnit.module('Views', { // check mode assert.strictEqual(graph.renderer.state.mode, "line", "should be in line chart mode"); - assert.notOk(graph.$buttons.find('button[data-mode="bar"]').hasClass('active'), + assert.doesNotHaveClass(graph.$buttons.find('button[data-mode="bar"]'), 'active', 'bar chart button should not be active'); - assert.ok(graph.$buttons.find('button[data-mode="line"]').hasClass('active'), + assert.hasClass(graph.$buttons.find('button[data-mode="line"]'),'active', 'line chart button should be active'); // check groupbys @@ -552,7 +554,7 @@ QUnit.module('Views', { }, }); - graph.reload({groupBy: []}); + testUtils.graph.reload(graph, {groupBy: []}); graph.destroy(); }); @@ -580,12 +582,12 @@ QUnit.module('Views', { return concurrency.delay(0).then(function () { - assert.strictEqual(graph.$('.nv-groups rect').length, 2, + assert.containsN(graph, '.nv-groups rect', 2, "should display two groups"); - graph.reload({groupBy: []}); + testUtils.graph.reload(graph, {groupBy: []}); return concurrency.delay(0).then(function () { - assert.strictEqual(graph.$('.nv-groups rect').length, 2, + assert.containsN(graph, '.nv-groups rect', 2, "should still display two groups"); graph.destroy(); @@ -607,7 +609,7 @@ QUnit.module('Views', { }); var done = assert.async(); return concurrency.delay(0).then(function () { - assert.strictEqual(graph.$('div.o_graph_svg_container svg.nvd3-svg').length, 1, + assert.containsOnce(graph, 'div.o_graph_svg_container svg.nvd3-svg', "should contain a div with a svg element"); assert.strictEqual(graph.renderer.state.mode, "bar", @@ -633,7 +635,7 @@ QUnit.module('Views', { }); var done = assert.async(); return concurrency.delay(0).then(function () { - assert.strictEqual(graph.$('div.o_graph_svg_container svg.nvd3-svg').length, 1, + assert.containsOnce(graph, 'div.o_graph_svg_container svg.nvd3-svg', "should contain a div with a svg element"); assert.strictEqual(graph.renderer.state.mode, "bar", @@ -665,7 +667,8 @@ QUnit.module('Views', { return concurrency.delay(0).then(function () { // need to set the measure this way because it cannot be set in the // arch. - graph.$buttons.find('.dropdown-item[data-field="product_id"]').click(); + testUtils.dom.click(graph.$buttons.find('.dropdown-toggle:contains(Measures)')); + testUtils.dom.click(graph.$buttons.find('.dropdown-item[data-field="product_id"]')); assert.strictEqual(graph.model.chart.data[0].value, 1, "should have first datapoint with value 1"); @@ -756,13 +759,13 @@ QUnit.module('Views', { "Bouh should be the first measure"); assert.strictEqual(graph.$buttons.find('.o_graph_measures_list .dropdown-item:last').data('field'), '__count__', "Count should be the last measure"); - + graph.destroy(); }); QUnit.test('Undefined should appear in bar, pie graph but not in line graph', function (assert) { assert.expect(4); - + var graph = createView({ View: GraphView, model: "foo", @@ -776,7 +779,7 @@ QUnit.module('Views', { assert.strictEqual(graph.$("svg.nvd3-svg:contains('Undefined')").length, 0); assert.strictEqual(graph.$("svg.nvd3-svg:contains('March')").length, 1); - graph.$buttons.find('.o_graph_button[data-mode=bar]').click(); + testUtils.dom.click(graph.$buttons.find('.o_graph_button[data-mode=bar]')); assert.strictEqual(graph.$("svg.nvd3-svg:contains('Undefined')").length, 1); assert.strictEqual(graph.$("svg.nvd3-svg:contains('January')").length, 1); diff --git a/addons/web/static/tests/views/kanban_mobile_tests.js b/addons/web/static/tests/views/kanban_mobile_tests.js index 0ee35c2c9c3..98e65d1ea9c 100644 --- a/addons/web/static/tests/views/kanban_mobile_tests.js +++ b/addons/web/static/tests/views/kanban_mobile_tests.js @@ -72,29 +72,29 @@ QUnit.module('Views', { }); // basic rendering tests - assert.strictEqual(kanban.$('.o_kanban_group').length, 2, "should have 2 columns" ); - assert.ok(kanban.$('.o_kanban_mobile_tab:first').hasClass('o_current'), + assert.containsN(kanban, '.o_kanban_group', 2, "should have 2 columns" ); + assert.hasClass(kanban.$('.o_kanban_mobile_tab:first'),'o_current', "first tab is the active tab with class 'o_current'"); - assert.strictEqual(kanban.$('.o_kanban_group:first > div.o_kanban_record').length, 2, + assert.containsN(kanban, '.o_kanban_group:first > div.o_kanban_record', 2, "there are 2 records in active tab"); assert.strictEqual(kanban.$('.o_kanban_group:nth(1) > div.o_kanban_record').length, 0, "there is no records in next tab. Records will be loaded when it will be opened"); // quick create in first column - kanban.$buttons.find('.o-kanban-button-new').click(); - assert.ok(kanban.$('.o_kanban_group:nth(0) > div:nth(1)').hasClass('o_kanban_quick_create'), + testUtils.dom.click(kanban.$buttons.find('.o-kanban-button-new')); + assert.hasClass(kanban.$('.o_kanban_group:nth(0) > div:nth(1)'),'o_kanban_quick_create', "clicking on create should open the quick_create in the first column"); // move to second column kanban.$('.o_kanban_mobile_tab:nth(1)').trigger('click'); - assert.ok(kanban.$('.o_kanban_mobile_tab:nth(1)').hasClass('o_current'), + assert.hasClass(kanban.$('.o_kanban_mobile_tab:nth(1)'),'o_current', "second tab is now active with class 'o_current'"); assert.strictEqual(kanban.$('.o_kanban_group:nth(1) > div.o_kanban_record').length, 2, "the 2 records of the second group have now been loaded"); // quick create in second column - kanban.$buttons.find('.o-kanban-button-new').click(); - assert.ok(kanban.$('.o_kanban_group:nth(1) > div:nth(1)').hasClass('o_kanban_quick_create'), + testUtils.dom.click(kanban.$buttons.find('.o-kanban-button-new')); + assert.hasClass(kanban.$('.o_kanban_group:nth(1) > div:nth(1)'),'o_kanban_quick_create', "clicking on create should open the quick_create in the second column"); // kanban column should match kanban mobile tabs @@ -120,8 +120,8 @@ QUnit.module('Views', { }); // first column should be undefined with framework unique identifier - assert.strictEqual(kanban.$('.o_kanban_group').length, 3, "should have 3 columns" ); - assert.strictEqual(kanban.$('.o_kanban_group:first-child[data-id^="partner_"]').length, 1, + assert.containsN(kanban, '.o_kanban_group', 3, "should have 3 columns" ); + assert.containsOnce(kanban, '.o_kanban_group:first-child[data-id^="partner_"]', "Undefined column should be first and have unique framework identifier as data-id") // kanban column should match kanban mobile tabs @@ -147,8 +147,8 @@ QUnit.module('Views', { }); // basic rendering tests - assert.strictEqual(kanban.$('.o_kanban_group').length, 4, "should have 4 columns" ); - assert.strictEqual(kanban.$('.o_kanban_group[data-id^="partner_"]').length, 4, + assert.containsN(kanban, '.o_kanban_group', 4, "should have 4 columns" ); + assert.containsN(kanban, '.o_kanban_group[data-id^="partner_"]', 4, "all column should have framework unique identifiers"); // kanban column should match kanban mobile tabs diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js index 435c13cd3f1..bb66243fed8 100644 --- a/addons/web/static/tests/views/kanban_tests.js +++ b/addons/web/static/tests/views/kanban_tests.js @@ -84,16 +84,11 @@ QUnit.module('Views', { '', }); - assert.ok(kanban.$('.o_kanban_view').hasClass('o_kanban_ungrouped'), - "should have classname 'o_kanban_ungrouped'"); - assert.ok(kanban.$('.o_kanban_view').hasClass('o_kanban_test'), - "should have classname 'o_kanban_test'"); - - assert.strictEqual(kanban.$('.o_kanban_record:not(.o_kanban_ghost)').length, 4, - "should have 4 records"); - assert.strictEqual(kanban.$('.o_kanban_ghost').length, 6, "should have 6 ghosts"); - assert.strictEqual(kanban.$('.o_kanban_record:contains(gnap)').length, 1, - "should contain gnap"); + assert.hasClass(kanban.$('.o_kanban_view'), 'o_kanban_ungrouped'); + assert.hasClass(kanban.$('.o_kanban_view'), 'o_kanban_test'); + assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 4); + assert.containsN(kanban,'.o_kanban_ghost', 6); + assert.containsOnce(kanban, '.o_kanban_record:contains(gnap)'); kanban.destroy(); }); @@ -120,31 +115,25 @@ QUnit.module('Views', { }, }); - assert.ok(kanban.$('.o_kanban_view').hasClass('o_kanban_grouped'), - "should have classname 'o_kanban_grouped'"); - assert.ok(kanban.$('.o_kanban_view').hasClass('o_kanban_test'), - "should have classname 'o_kanban_test'"); - assert.strictEqual(kanban.$('.o_kanban_group').length, 2, "should have " + 2 + " columns"); - assert.strictEqual(kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record').length, 1, - "column should contain " + 1 + " record(s)"); - assert.strictEqual(kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record').length, 3, - "column should contain " + 3 + " record(s)"); + assert.hasClass(kanban.$('.o_kanban_view'), 'o_kanban_grouped'); + assert.hasClass(kanban.$('.o_kanban_view'), 'o_kanban_test'); + assert.containsN(kanban, '.o_kanban_group', 2); + assert.containsOnce(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record'); + assert.containsN(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record', 3); + // check available actions in kanban header's config dropdown - assert.ok(kanban.$('.o_kanban_header:first .o_kanban_config .o_kanban_toggle_fold').length, - "should be able to fold the column"); - assert.ok(!kanban.$('.o_kanban_header:first .o_kanban_config .o_column_edit').length, - "should not be able to edit the column"); - assert.ok(!kanban.$('.o_kanban_header:first .o_kanban_config .o_column_delete').length, - "should not be able to delete the column"); - assert.ok(!kanban.$('.o_kanban_header:first .o_kanban_config .o_column_archive_records').length, "should not be able to archive all the records"); - assert.ok(!kanban.$('.o_kanban_header:first .o_kanban_config .o_column_unarchive_records').length, "should not be able to restore all the records"); + assert.containsOnce(kanban, '.o_kanban_header:first .o_kanban_config .o_kanban_toggle_fold'); + assert.containsNone(kanban, '.o_kanban_header:first .o_kanban_config .o_column_edit'); + assert.containsNone(kanban, '.o_kanban_header:first .o_kanban_config .o_column_delete'); + assert.containsNone(kanban, '.o_kanban_header:first .o_kanban_config .o_column_archive_records'); + assert.containsNone(kanban, '.o_kanban_header:first .o_kanban_config .o_column_unarchive_records'); // the next line makes sure that reload works properly. It looks useless, // but it actually test that a grouped local record can be reloaded without // changing its result. - kanban.reload(); - assert.strictEqual(kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record').length, 3, - "column should contain " + 3 + " record(s)"); + testUtils.kanban.reload(kanban); + assert.containsN(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record', 3); + kanban.destroy(); }); @@ -175,21 +164,23 @@ QUnit.module('Views', { }); // check archive/restore all actions in kanban header's config dropdown - assert.ok(kanban.$('.o_kanban_header:first .o_kanban_config .o_column_archive_records').length, "should be able to archive all the records"); - assert.ok(kanban.$('.o_kanban_header:first .o_kanban_config .o_column_unarchive_records').length, "should be able to restore all the records"); + assert.containsOnce(kanban, '.o_kanban_header:first .o_kanban_config .o_column_archive_records'); + assert.containsOnce(kanban, '.o_kanban_header:first .o_kanban_config .o_column_unarchive_records'); // archive the records of the first column - assert.strictEqual(kanban.$('.o_kanban_group:last .o_kanban_record').length, 3, - "last column should contain 3 records"); + assert.containsN(kanban, '.o_kanban_group:last .o_kanban_record', 3); envIDs = [4]; - kanban.$('.o_kanban_group:last .o_column_archive_records').click(); // Click on 'Archive All' - assert.ok($('.modal').length, 'a confirm modal should be displayed'); - $('.modal-footer .btn-secondary').click(); // Click on 'Cancel' - assert.strictEqual(kanban.$('.o_kanban_group:last .o_kanban_record').length, 3, "still last column should contain 3 records"); - kanban.$('.o_kanban_group:last .o_column_archive_records').click(); + + testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last')); + testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_archive_records')); + assert.containsOnce(document.body, '.modal', "a confirm modal should be displayed"); + testUtils.modal.clickButton('Cancel'); + assert.containsN(kanban, '.o_kanban_group:last .o_kanban_record', 3, "still last column should contain 3 records"); + testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last')); + testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_archive_records')); assert.ok($('.modal').length, 'a confirm modal should be displayed'); - $('.modal-footer .btn-primary').click(); // Click on 'Ok' - assert.strictEqual(kanban.$('.o_kanban_group:last .o_kanban_record').length, 0, "last column should not contain any records"); + testUtils.modal.clickButton('Ok'); + assert.containsNone(kanban, '.o_kanban_group:last .o_kanban_record', "last column should not contain any records"); kanban.destroy(); }); @@ -218,17 +209,19 @@ QUnit.module('Views', { assert.ok(kanban.$('.o_kanban_header:first .o_kanban_config .o_column_unarchive_records').length, "should be able to restore all the records"); // archive the records of the first column - assert.strictEqual(kanban.$('.o_kanban_group:last .o_kanban_record').length, 3, + assert.containsN(kanban, '.o_kanban_group:last .o_kanban_record', 3, "last column should contain 3 records"); envIDs = [4]; - kanban.$('.o_kanban_group:last .o_column_archive_records').click(); // Click on 'Archive All' + testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last')); + testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_archive_records')); assert.ok($('.modal').length, 'a confirm modal should be displayed'); - $('.modal-footer .btn-secondary').click(); // Click on 'Cancel' - assert.strictEqual(kanban.$('.o_kanban_group:last .o_kanban_record').length, 3, "still last column should contain 3 records"); - kanban.$('.o_kanban_group:last .o_column_archive_records').click(); + testUtils.modal.clickButton('Cancel'); // Click on 'Cancel' + assert.containsN(kanban, '.o_kanban_group:last .o_kanban_record', 3, "still last column should contain 3 records"); + testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last')); + testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_archive_records')); assert.ok($('.modal').length, 'a confirm modal should be displayed'); - $('.modal-footer .btn-primary').click(); // Click on 'Ok' - assert.strictEqual(kanban.$('.o_kanban_group:last .o_kanban_record').length, 0, "last column should not contain any records"); + testUtils.modal.clickButton('Ok'); // Click on 'Ok' + assert.containsNone(kanban, '.o_kanban_group:last .o_kanban_record', "last column should not contain any records"); kanban.destroy(); }); @@ -278,7 +271,7 @@ QUnit.module('Views', { }); kanban.renderPager(); - assert.ok(kanban.pager.$el.hasClass('o_hidden'), + assert.isNotVisible(kanban.pager.$el, "pager should be hidden in grouped kanban"); kanban.destroy(); }); @@ -300,7 +293,7 @@ QUnit.module('Views', { }, }); - assert.ok(!kanban.pager.$el.hasClass('o_hidden'), + assert.isVisible(kanban.pager.$el, "pager should be visible in ungrouped kanban"); assert.strictEqual(kanban.pager.state.size, 4, "pager's size should be 4"); kanban.destroy(); @@ -372,17 +365,16 @@ QUnit.module('Views', { '', groupBy: ['product_id'], }); - kanban.renderButtons(); - assert.ok(kanban.$('.o_kanban_view').hasClass('ui-sortable'), + assert.hasClass(kanban.$('.o_kanban_view'),'ui-sortable', "columns are sortable when grouped by a m2o field"); - assert.ok(kanban.$buttons.find('.o-kanban-button-new').hasClass('btn-primary'), + assert.hasClass(kanban.$buttons.find('.o-kanban-button-new'),'btn-primary', "'create' button should be btn-primary for grouped kanban with at least one column"); - assert.ok(kanban.$('.o_kanban_view > div:last').hasClass('o_column_quick_create'), + assert.hasClass(kanban.$('.o_kanban_view > div:last'),'o_column_quick_create', "column quick create should be enabled when grouped by a many2one field)"); - kanban.$buttons.find('.o-kanban-button-new').click(); // Click on 'Create' - assert.ok(kanban.$('.o_kanban_group:first() > div:nth(1)').hasClass('o_kanban_quick_create'), + testUtils.kanban.clickCreate(kanban); // Click on 'Create' + assert.hasClass(kanban.$('.o_kanban_group:first() > div:nth(1)'),'o_kanban_quick_create', "clicking on create should open the quick_create in the first column"); assert.ok(kanban.$('span.o_column_title:contains(hello)').length, @@ -405,12 +397,12 @@ QUnit.module('Views', { groupBy: ['foo'], }); - assert.ok(!kanban.$('.o_kanban_view').hasClass('ui-sortable'), + assert.doesNotHaveClass(kanban.$('.o_kanban_view'), 'ui-sortable', "columns aren't sortable when not grouped by a m2o field"); - assert.strictEqual(kanban.$('.o_kanban_group').length, 3, "should have " + 3 + " columns"); + assert.containsN(kanban, '.o_kanban_group', 3, "should have " + 3 + " columns"); assert.strictEqual(kanban.$('.o_kanban_group:first() .o_column_title').text(), "yop", "'yop' column should be the first column"); - assert.ok(!kanban.$('.o_kanban_view > div:last').hasClass('o_column_quick_create'), + assert.doesNotHaveClass(kanban.$('.o_kanban_view > div:last'), 'o_column_quick_create', "column quick create should be disabled when not grouped by a many2one field)"); kanban.destroy(); }); @@ -438,11 +430,11 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1, + assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "first column should contain one record"); // click on 'Create' -> should open the quick create in the first column - kanban.$buttons.find('.o-kanban-button-new').click(); + testUtils.kanban.clickCreate(kanban); var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create'); assert.strictEqual($quickCreate.length, 1, @@ -451,16 +443,16 @@ QUnit.module('Views', { "should have rendered an XXS form view"); assert.strictEqual($quickCreate.find('input').length, 1, "should have only one input"); - assert.ok($quickCreate.find('input').hasClass('o_required_modifier'), + assert.hasClass($quickCreate.find('input'),'o_required_modifier', "the field should be required"); assert.strictEqual($quickCreate.find('input[placeholder=Title]').length, 1, "input placeholder should be 'Title'"); // fill the quick create and validate - $quickCreate.find('input').val('new partner').trigger('input'); - $quickCreate.find('button.o_kanban_add').click(); + testUtils.fields.editInput($quickCreate.find('input'), 'new partner'); + testUtils.dom.click($quickCreate.find('button.o_kanban_add')); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 2, + assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "first column should contain two records"); assert.verifySteps([ @@ -509,11 +501,11 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1, + assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "first column should contain one record"); // click on 'Create' -> should open the quick create in the first column - kanban.$buttons.find('.o-kanban-button-new').click(); + testUtils.kanban.clickCreate(kanban); var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create'); assert.strictEqual($quickCreate.length, 1, @@ -526,12 +518,12 @@ QUnit.module('Views', { "should have rendered three widgets"); // fill the quick create and validate - $quickCreate.find('.o_field_widget[name=foo]').val('new partner').trigger('input'); - $quickCreate.find('.o_field_widget[name=int_field]').val('4').trigger('input'); - $quickCreate.find('.o_field_widget[name=state] .o_priority_star:first').click(); - $quickCreate.find('button.o_kanban_add').click(); + testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=foo]'), 'new partner'); + testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=int_field]'), '4'); + testUtils.dom.click($quickCreate.find('.o_field_widget[name=state] .o_priority_star:first')); + testUtils.dom.click($quickCreate.find('button.o_kanban_add')); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 2, + assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "first column should contain two records"); assert.verifySteps([ @@ -580,16 +572,16 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 2, + assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "first column should contain two records"); // click on 'Create', fill the quick create and validate - kanban.$buttons.find('.o-kanban-button-new').click(); + testUtils.kanban.clickCreate(kanban); var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create'); - $quickCreate.find('input').val('new partner').trigger('input'); - $quickCreate.find('button.o_kanban_add').click(); + testUtils.fields.editInput($quickCreate.find('input'), 'new partner'); + testUtils.dom.click($quickCreate.find('button.o_kanban_add')); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 3, + assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 3, "first column should contain three records"); assert.verifySteps([ @@ -645,18 +637,18 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 2, + assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "first column should contain two records"); // click on 'Create', fill the quick create and validate - kanban.$buttons.find('.o-kanban-button-new').click(); + testUtils.kanban.clickCreate(kanban); var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create'); - $quickCreate.find('.o_field_widget[name=foo]').val('new partner').trigger('input'); - $quickCreate.find('.o_field_widget[name=int_field]').val('4').trigger('input'); - $quickCreate.find('.o_field_widget[name=state] .o_priority_star:first').click(); - $quickCreate.find('button.o_kanban_add').click(); + testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=foo]'), 'new partner'); + testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=int_field]'), '4'); + testUtils.dom.click($quickCreate.find('.o_field_widget[name=state] .o_priority_star:first')); + testUtils.dom.click($quickCreate.find('button.o_kanban_add')); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 3, + assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 3, "first column should contain three records"); assert.verifySteps([ @@ -709,7 +701,7 @@ QUnit.module('Views', { }); // click on 'Create' -> should open the quick create in the first column - kanban.$buttons.find('.o-kanban-button-new').click(); + testUtils.kanban.clickCreate(kanban); var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create'); assert.strictEqual($quickCreate.length, 1, @@ -718,7 +710,7 @@ QUnit.module('Views', { "default value should be set"); // fill the 'foo' field -> should trigger the onchange - $quickCreate.find('.o_field_widget[name=foo]').val('new partner').trigger('input'); + testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=foo]'), 'new partner'); assert.strictEqual($quickCreate.find('.o_field_widget[name=int_field]').val(), '8', "onchange should have been triggered"); @@ -758,18 +750,18 @@ QUnit.module('Views', { }); // create a new record - kanban.$('.o_kanban_group:first .o_kanban_quick_add').click(); + testUtils.dom.click(kanban.$('.o_kanban_group:first .o_kanban_quick_add')); var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create'); - assert.ok($quickCreate.find('.o_field_widget[name=foo]').hasClass('o_required_modifier'), + assert.hasClass($quickCreate.find('.o_field_widget[name=foo]'),'o_required_modifier', "foo field should be required"); - assert.ok($quickCreate.find('.o_field_widget[name=int_field]').hasClass('o_invisible_modifier'), + assert.hasClass($quickCreate.find('.o_field_widget[name=int_field]'),'o_invisible_modifier', "int_field should be invisible"); // fill 'foo' field - $quickCreate.find('.o_field_widget[name=foo]').val('new partner').trigger('input'); + testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=foo]'), 'new partner'); - assert.notOk($quickCreate.find('.o_field_widget[name=int_field]').hasClass('o_invisible_modifier'), + assert.doesNotHaveClass($quickCreate.find('.o_field_widget[name=int_field]'), 'o_invisible_modifier', "int_field should now be visible"); kanban.destroy(); @@ -800,15 +792,15 @@ QUnit.module('Views', { }); // Quick create kanban record - kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click(); + testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); var $quickAdd = kanban.$('.o_kanban_quick_create'); $quickAdd.find('.o_input').val('Test'); - $quickAdd.find('.o_kanban_add').click(); + testUtils.dom.click($quickAdd.find('.o_kanban_add')); // Select state in kanban - kanban.$('.o_status').first().click(); - kanban.$('.o_selection .dropdown-item:first').click(); - assert.ok(kanban.$('.o_status').first().hasClass('o_status_green'), + testUtils.dom.click(kanban.$('.o_status').first()); + testUtils.dom.click(kanban.$('.o_selection .dropdown-item:first')); + assert.hasClass(kanban.$('.o_status').first(),'o_status_green', "Kanban state should be done (Green)"); kanban.destroy(); }); @@ -836,7 +828,7 @@ QUnit.module('Views', { }); // click to add an element and cancel the quick creation by pressing ESC - kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click(); + testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); var $quickCreate = kanban.$('.o_kanban_quick_create'); assert.strictEqual($quickCreate.length, 1, "should have a quick create element"); @@ -845,23 +837,23 @@ QUnit.module('Views', { keyCode: $.ui.keyCode.ESCAPE, which: $.ui.keyCode.ESCAPE, })); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 0, + assert.containsNone(kanban, '.o_kanban_quick_create', "should have destroyed the quick create element"); // click to add and element and click outside, should cancel the quick creation - kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click(); - kanban.$('.o_kanban_group .o_kanban_record:first').click(); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 0, + testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first')); + assert.containsNone(kanban, '.o_kanban_quick_create', "the quick create should be destroyed when the user clicks outside"); // click to really add an element - kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click(); + testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); $quickCreate = kanban.$('.o_kanban_quick_create'); - $quickCreate.find('input').val('new partner').trigger('input'); + testUtils.fields.editInput($quickCreate.find('input'), 'new partner'); // clicking outside should no longer destroy the quick create as it is dirty - kanban.$('.o_kanban_group .o_kanban_record:first').click(); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 1, + testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first')); + assert.containsOnce(kanban, '.o_kanban_quick_create', "the quick create should not have been destroyed"); // confirm by pressing ENTER @@ -908,20 +900,15 @@ QUnit.module('Views', { fieldDebounce: 5000, }); - assert.strictEqual(kanban.$('.o_kanban_record').length, 4, + assert.containsN(kanban, '.o_kanban_record', 4, "should have 4 records at the beginning"); // add an element and confirm by pressing ENTER - kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click(); - kanban.$('.o_kanban_quick_create').find('input[name=foo]') - .val('new partner') - .trigger('input') // changes aren't notified here thanks to the fieldDebounce - .trigger($.Event('keydown', { - keyCode: $.ui.keyCode.ENTER, - which: $.ui.keyCode.ENTER, - })); // triggers a navigation event, leading to the 'commitChanges' and record creation - - assert.strictEqual(kanban.$('.o_kanban_record').length, 5, + testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + testUtils.kanban.quickCreate(kanban, 'new partner', 'foo'); + // triggers a navigation event, leading to the 'commitChanges' and record creation + + assert.containsN(kanban, '.o_kanban_record', 5, "should have created a new record"); assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), '', "quick create should now be empty"); @@ -963,36 +950,36 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_kanban_record').length, 4, + assert.containsN(kanban, '.o_kanban_record', 4, "should have 4 records at the beginning"); // add an element and press ENTER twice def = $.Deferred(); - kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click(); + testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); var enterEvent = { keyCode: $.ui.keyCode.ENTER, which: $.ui.keyCode.ENTER, }; - kanban.$('.o_kanban_quick_create').find('input[name=foo]') - .val('new partner') - .trigger('input') - .trigger($.Event('keydown', enterEvent)) - .trigger($.Event('keydown', enterEvent)); + testUtils.fields.editAndTrigger( + kanban.$('.o_kanban_quick_create').find('input[name=foo]'), + 'new partner', + ['input', $.Event('keydown', enterEvent), $.Event('keydown', enterEvent)] + ); - assert.strictEqual(kanban.$('.o_kanban_record').length, 4, + assert.containsN(kanban, '.o_kanban_record', 4, "should not have created the record yet"); assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), 'new partner', "quick create should not be empty yet"); - assert.ok(kanban.$('.o_kanban_quick_create').hasClass('o_disabled'), + assert.hasClass(kanban.$('.o_kanban_quick_create'),'o_disabled', "quick create should be disabled"); def.resolve(); - assert.strictEqual(kanban.$('.o_kanban_record').length, 5, + assert.containsN(kanban, '.o_kanban_record', 5, "should have created a new record"); assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), '', "quick create should now be empty"); - assert.notOk(kanban.$('.o_kanban_quick_create').hasClass('o_disabled'), + assert.doesNotHaveClass(kanban.$('.o_kanban_quick_create'), 'o_disabled', "quick create should be enabled"); assert.verifySteps(['create']); @@ -1030,33 +1017,30 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_kanban_record').length, 4, + assert.containsN(kanban, '.o_kanban_record', 4, "should have 4 records at the beginning"); // add an element and click 'Add' twice def = $.Deferred(); - kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click(); - kanban.$('.o_kanban_quick_create').find('input[name=foo]') - .val('new partner') - .trigger('input'); - kanban.$('.o_kanban_quick_create').find('.o_kanban_add') - .click() - .click(); - - assert.strictEqual(kanban.$('.o_kanban_record').length, 4, + testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + testUtils.fields.editInput(kanban.$('.o_kanban_quick_create').find('input[name=foo]'), 'new partner'); + testUtils.dom.click(kanban.$('.o_kanban_quick_create').find('.o_kanban_add')); + testUtils.dom.click(kanban.$('.o_kanban_quick_create').find('.o_kanban_add')); + + assert.containsN(kanban, '.o_kanban_record', 4, "should not have created the record yet"); assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), 'new partner', "quick create should not be empty yet"); - assert.ok(kanban.$('.o_kanban_quick_create').hasClass('o_disabled'), + assert.hasClass(kanban.$('.o_kanban_quick_create'),'o_disabled', "quick create should be disabled"); def.resolve(); - assert.strictEqual(kanban.$('.o_kanban_record').length, 5, + assert.containsN(kanban, '.o_kanban_record', 5, "should have created a new record"); assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), '', "quick create should now be empty"); - assert.notOk(kanban.$('.o_kanban_quick_create').hasClass('o_disabled'), + assert.doesNotHaveClass(kanban.$('.o_kanban_quick_create'), 'o_disabled', "quick create should be enabled"); assert.verifySteps(['create']); @@ -1110,36 +1094,37 @@ QUnit.module('Views', { fieldDebounce: 5000, }); - assert.strictEqual(kanban.$('.o_kanban_record').length, 4, + assert.containsN(kanban, '.o_kanban_record', 4, "should have 4 records at the beginning"); // add an element and press ENTER twice - kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click(); + testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); def = $.Deferred(); var enterEvent = { keyCode: $.ui.keyCode.ENTER, which: $.ui.keyCode.ENTER, }; - kanban.$('.o_kanban_quick_create').find('input[name=foo]') - .val('new partner') - .trigger('input') - .trigger($.Event('keydown', enterEvent)) - .trigger($.Event('keydown', enterEvent)); - assert.strictEqual(kanban.$('.o_kanban_record').length, 4, + testUtils.fields.editAndTrigger( + kanban.$('.o_kanban_quick_create').find('input[name=foo]'), + 'new partner', + ['input', $.Event('keydown', enterEvent), $.Event('keydown', enterEvent)] + ); + + assert.containsN(kanban, '.o_kanban_record', 4, "should not have created the record yet"); assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), 'new partner', "quick create should not be empty yet"); - assert.ok(kanban.$('.o_kanban_quick_create').hasClass('o_disabled'), + assert.hasClass(kanban.$('.o_kanban_quick_create'),'o_disabled', "quick create should be disabled"); def.resolve(); - assert.strictEqual(kanban.$('.o_kanban_record').length, 5, + assert.containsN(kanban, '.o_kanban_record', 5, "should have created a new record"); assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), '', "quick create should now be empty"); - assert.notOk(kanban.$('.o_kanban_quick_create').hasClass('o_disabled'), + assert.doesNotHaveClass(kanban.$('.o_kanban_quick_create'), 'o_disabled', "quick create should be enabled"); assert.verifySteps([ @@ -1194,31 +1179,29 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_kanban_record').length, 4, + assert.containsN(kanban, '.o_kanban_record', 4, "should have 4 records at the beginning"); // add an element and click 'add' - kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click(); + testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); def = $.Deferred(); - kanban.$('.o_kanban_quick_create').find('input[name=foo]') - .val('new partner') - .trigger('input'); - kanban.$('.o_kanban_quick_create').find('.o_kanban_add').click(); + testUtils.fields.editInput(kanban.$('.o_kanban_quick_create').find('input[name=foo]'), 'new partner'); + testUtils.dom.click(kanban.$('.o_kanban_quick_create').find('.o_kanban_add')); - assert.strictEqual(kanban.$('.o_kanban_record').length, 4, + assert.containsN(kanban, '.o_kanban_record', 4, "should not have created the record yet"); assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), 'new partner', "quick create should not be empty yet"); - assert.ok(kanban.$('.o_kanban_quick_create').hasClass('o_disabled'), + assert.hasClass(kanban.$('.o_kanban_quick_create'),'o_disabled', "quick create should be disabled"); def.resolve(); // the onchange returns - assert.strictEqual(kanban.$('.o_kanban_record').length, 5, + assert.containsN(kanban, '.o_kanban_record', 5, "should have created a new record"); assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), '', "quick create should now be empty"); - assert.notOk(kanban.$('.o_kanban_quick_create').hasClass('o_disabled'), + assert.doesNotHaveClass(kanban.$('.o_kanban_quick_create'), 'o_disabled', "quick create should be enabled"); assert.verifySteps([ @@ -1246,30 +1229,32 @@ QUnit.module('Views', { groupBy: ['bar'], }); - assert.notOk(kanban.$('.o_kanban_group:first').hasClass('o_column_folded'), + assert.doesNotHaveClass(kanban.$('.o_kanban_group:first'), 'o_column_folded', "first column should not be folded"); // fold the first column - kanban.$('.o_kanban_group:first .o_kanban_toggle_fold').click(); + testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:first')); + testUtils.dom.click(kanban.$('.o_kanban_group:first .o_kanban_toggle_fold')); - assert.ok(kanban.$('.o_kanban_group:first').hasClass('o_column_folded'), + assert.hasClass(kanban.$('.o_kanban_group:first'),'o_column_folded', "first column should be folded"); // click on 'Create' to open the quick create in the first column - kanban.$buttons.find('.o-kanban-button-new').click(); + testUtils.kanban.clickCreate(kanban); - assert.notOk(kanban.$('.o_kanban_group:first').hasClass('o_column_folded'), + assert.doesNotHaveClass(kanban.$('.o_kanban_group:first'), 'o_column_folded', "first column should no longer be folded"); var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create'); assert.strictEqual($quickCreate.length, 1, "should have added a quick create element in first column"); // fold again the first column - kanban.$('.o_kanban_group:first .o_kanban_toggle_fold').click(); + testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:first')); + testUtils.dom.click(kanban.$('.o_kanban_group:first .o_kanban_toggle_fold')); - assert.ok(kanban.$('.o_kanban_group:first').hasClass('o_column_folded'), + assert.hasClass(kanban.$('.o_kanban_group:first'),'o_column_folded', "first column should be folded"); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 0, + assert.containsNone(kanban, '.o_kanban_quick_create', "there should be no more quick create"); kanban.destroy(); @@ -1290,27 +1275,27 @@ QUnit.module('Views', { groupBy: ['bar'], }); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1, + assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "first column should contain one record"); // click to add an element - kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click(); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 1, + testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + assert.containsOnce(kanban, '.o_kanban_quick_create', "should have open the quick create widget"); // click again to add an element -> should have kept the quick create open - kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click(); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 1, + testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + assert.containsOnce(kanban, '.o_kanban_quick_create', "should have kept the quick create open"); // click outside: should remove the quick create - kanban.$('.o_kanban_group .o_kanban_record:first').click(); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 0, + testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first')); + assert.containsNone(kanban, '.o_kanban_quick_create', "the quick create should not have been destroyed"); // click to reopen the quick create - kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click(); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 1, + testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + assert.containsOnce(kanban, '.o_kanban_quick_create', "should have open the quick create widget"); // press ESC: should remove the quick create @@ -1318,21 +1303,21 @@ QUnit.module('Views', { keyCode: $.ui.keyCode.ESCAPE, which: $.ui.keyCode.ESCAPE, })); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 0, + assert.containsNone(kanban, '.o_kanban_quick_create', "quick create widget should have been removed"); // click to reopen the quick create - kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click(); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 1, + testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + assert.containsOnce(kanban, '.o_kanban_quick_create', "should have open the quick create widget"); // click on 'Discard': should remove the quick create - kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click(); - kanban.$('.o_kanban_group .o_kanban_record:first').click(); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 0, + testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first')); + assert.containsNone(kanban, '.o_kanban_quick_create', "the quick create should be destroyed when the user clicks outside"); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1, + assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "first column should still contain one record"); kanban.destroy(); @@ -1353,20 +1338,20 @@ QUnit.module('Views', { groupBy: ['bar'], }); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1, + assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "first column should contain one record"); // click to add an element and edit it - kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click(); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 1, + testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + assert.containsOnce(kanban, '.o_kanban_quick_create', "should have open the quick create widget"); var $quickCreate = kanban.$('.o_kanban_quick_create'); - $quickCreate.find('input').val('some value').trigger('input'); + testUtils.fields.editInput($quickCreate.find('input'), 'some value'); // click outside: should not remove the quick create - kanban.$('.o_kanban_group .o_kanban_record:first').click(); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 1, + testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first')); + assert.containsOnce(kanban, '.o_kanban_quick_create', "the quick create should not have been destroyed"); // press ESC: should remove the quick create @@ -1374,23 +1359,23 @@ QUnit.module('Views', { keyCode: $.ui.keyCode.ESCAPE, which: $.ui.keyCode.ESCAPE, })); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 0, + assert.containsNone(kanban, '.o_kanban_quick_create', "quick create widget should have been removed"); // click to reopen quick create and edit it - kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click(); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 1, + testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + assert.containsOnce(kanban, '.o_kanban_quick_create', "should have open the quick create widget"); $quickCreate = kanban.$('.o_kanban_quick_create'); - $quickCreate.find('input').val('some value').trigger('input'); + testUtils.fields.editInput($quickCreate.find('input'), 'some value'); // click on 'Discard': should remove the quick create - kanban.$('.o_kanban_quick_create .o_kanban_cancel').click(); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 0, + testUtils.dom.click(kanban.$('.o_kanban_quick_create .o_kanban_cancel')); + assert.containsNone(kanban, '.o_kanban_quick_create', "the quick create should be destroyed when the user clicks outside"); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1, + assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "first column should still contain one record"); kanban.destroy(); @@ -1429,21 +1414,21 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1, + assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "first column should contain one record"); // click to add and edit an element var $quickCreate = kanban.$('.o_kanban_quick_create'); - kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click(); + testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); $quickCreate = kanban.$('.o_kanban_quick_create'); - $quickCreate.find('input').val('new partner').trigger('input'); - $quickCreate.find('button.o_kanban_edit').click(); + testUtils.fields.editInput($quickCreate.find('input'), 'new partner'); + testUtils.dom.click($quickCreate.find('button.o_kanban_edit')); assert.strictEqual(this.data.partner.records.length, 5, "should have created a partner"); assert.strictEqual(_.last(this.data.partner.records).name, "new partner", "should have correct name"); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 2, + assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "first column should now contain two records"); kanban.destroy(); @@ -1464,40 +1449,28 @@ QUnit.module('Views', { groupBy: ['bar'], }); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1, + assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "first column should contain one record"); // click to add an element, fill the input and press ENTER - kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click(); + testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 1, + assert.containsOnce(kanban, '.o_kanban_quick_create', "the quick create should be open"); - kanban.$('.o_kanban_quick_create input') - .val('new partner 1') - .trigger('input') - .trigger($.Event('keydown', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); + testUtils.kanban.quickCreate(kanban, 'new partner 1'); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 2, + assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "first column should now contain two records"); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 1, + assert.containsOnce(kanban, '.o_kanban_quick_create', "the quick create should still be open"); // create a second element in a row - kanban.$('.o_kanban_quick_create input') - .val('new partner 2') - .trigger('input') - .trigger($.Event('keydown', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 3, + testUtils.kanban.quickCreate(kanban, 'new partner 2'); + + assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 3, "first column should now contain three records"); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 1, + assert.containsOnce(kanban, '.o_kanban_quick_create', "the quick create should still be open"); kanban.destroy(); @@ -1526,32 +1499,27 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1, + assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "first column should contain one record"); // click to add a record, and add two in a row (first one will be delayed) - kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click(); + testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 1, + assert.containsOnce(kanban, '.o_kanban_quick_create', "the quick create should be open"); def = $.Deferred(); - kanban.$('.o_kanban_quick_create input') - .val('new partner 1') - .trigger('input') - .trigger($.Event('keydown', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1, + + testUtils.kanban.quickCreate(kanban, 'new partner 1'); + + assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "first column should still contain one record"); - assert.strictEqual(kanban.$('.o_kanban_quick_create.o_disabled').length, 1, + assert.containsOnce(kanban, '.o_kanban_quick_create.o_disabled', "quick create should be disabled"); def.resolve(); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 2, + assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "first column should now contain two records"); assert.strictEqual(kanban.$('.o_kanban_quick_create:not(.o_disabled)').length, 1, "quick create should be enabled"); @@ -1591,20 +1559,14 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 2, + assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "there should be 2 records in first column"); - kanban.$buttons.find('.o-kanban-button-new').click(); // Click on 'Create' - assert.ok(kanban.$('.o_kanban_group:first() > div:nth(1)').hasClass('o_kanban_quick_create'), + testUtils.kanban.clickCreate(kanban); // Click on 'Create' + assert.hasClass(kanban.$('.o_kanban_group:first() > div:nth(1)'),'o_kanban_quick_create', "clicking on create should open the quick_create in the first column"); - kanban.$('.o_kanban_quick_create input') - .val('test') - .trigger('input') - .trigger($.Event('keydown', { - keyCode: $.ui.keyCode.ENTER, - which: $.ui.keyCode.ENTER, - })); + testUtils.kanban.quickCreate(kanban, 'test'); assert.strictEqual($('.modal .o_form_view.o_form_editable').length, 1, "a form view dialog should have been opened (in edit)"); @@ -1612,11 +1574,11 @@ QUnit.module('Views', { "the correct product_id should already be set"); // specify a name and save - $('.modal input[name=foo]').val('test').trigger('input'); - $('.modal-footer .btn-primary').click(); + testUtils.fields.editInput($('.modal input[name=foo]'), 'test'); + testUtils.modal.clickButton('Save'); assert.strictEqual($('.modal').length, 0, "the modal should be closed"); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 3, + assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 3, "there should be 3 records in first column"); var $firstRecord = kanban.$('.o_kanban_group:first .o_kanban_record:first'); assert.strictEqual($firstRecord.text(), 'test', @@ -1656,26 +1618,19 @@ QUnit.module('Views', { }, $.Event()); } return this._super.apply(this, arguments); - }, + } }); - kanban.$buttons.find('.o-kanban-button-new').click(); // Click on 'Create' - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 1, + testUtils.kanban.clickCreate(kanban); + assert.containsOnce(kanban, '.o_kanban_quick_create', "should have a quick create widget"); - kanban.$('.o_kanban_quick_create input') - .val('test') - .trigger('input') - .trigger($.Event('keydown', { - keyCode: $.ui.keyCode.ENTER, - which: $.ui.keyCode.ENTER, - })); + testUtils.kanban.quickCreate(kanban, 'test'); assert.strictEqual($('.modal .o_form_view.o_form_editable').length, 1, "a form view dialog should have been opened (in edit)"); - // discard - $('.modal-footer .btn-secondary').click(); + testUtils.modal.clickButton('Discard'); assert.strictEqual($('.modal').length, 0, "the modal should be closed"); assert.strictEqual(kanban.$('.o_kanban_quick_create:not(.o_disabled)').length, 1, @@ -1720,22 +1675,21 @@ QUnit.module('Views', { groupBy: ['foo'], }); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1, + assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "there should be 1 record in first column"); - kanban.$('.o_kanban_header:first .o_kanban_quick_add i').click(); - kanban.$('.o_kanban_quick_create input').val('test').trigger('input'); - kanban.$('.o_kanban_add').click(); + testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i')); + testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'test'); + testUtils.dom.click(kanban.$('.o_kanban_add')); assert.strictEqual($('.modal .o_form_view.o_form_editable').length, 1, "a form view dialog should have been opened (in edit)"); assert.strictEqual($('.modal .o_field_widget[name=foo]').val(), 'yop', "the correct default value for foo should already be set"); - - $('.modal-footer .btn-primary').click(); + testUtils.modal.clickButton('Save'); assert.strictEqual($('.modal').length, 0, "the modal should be closed"); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 2, + assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "there should be 2 records in first column"); kanban.destroy(); @@ -1777,22 +1731,22 @@ QUnit.module('Views', { groupBy: ['state'], }); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1, + assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "there should be 1 record in first column"); - kanban.$('.o_kanban_header:first .o_kanban_quick_add i').click(); - kanban.$('.o_kanban_quick_create input').val('test').trigger('input'); - kanban.$('.o_kanban_add').click(); + testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i')); + testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'test'); + testUtils.dom.click(kanban.$('.o_kanban_add')); assert.strictEqual($('.modal .o_form_view.o_form_editable').length, 1, "a form view dialog should have been opened (in edit)"); assert.strictEqual($('.modal .o_field_widget[name=state]').val(), '"abc"', "the correct default value for state should already be set"); - $('.modal-footer .btn-primary').click(); + testUtils.modal.clickButton('Save'); assert.strictEqual($('.modal').length, 0, "the modal should be closed"); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 2, + assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "there should be 2 records in first column"); kanban.destroy(); @@ -1827,14 +1781,14 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_kanban_group').length, 2, + assert.containsN(kanban, '.o_kanban_group', 2, "there should be 2 columns"); - assert.strictEqual(kanban.$('.o_kanban_record').length, 0, + assert.containsNone(kanban, '.o_kanban_record', "both columns should be empty"); - kanban.$buttons.find('.o-kanban-button-new').click(); + testUtils.kanban.clickCreate(kanban); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_quick_create').length, 1, + assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_quick_create', "should have opened the quick create in the first column"); kanban.destroy(); @@ -1863,22 +1817,22 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_kanban_header .o_kanban_quick_add i').length, 0, + assert.containsNone(kanban, '.o_kanban_header .o_kanban_quick_add i', "quick create should be disabled when grouped on a date field"); // clicking on CREATE in control panel should not open a quick create - kanban.$buttons.find('.o-kanban-button-new').click(); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 0, + testUtils.kanban.clickCreate(kanban); + assert.containsNone(kanban, '.o_kanban_quick_create', "should not have opened the quick create widget"); kanban.reload({groupBy: ['datetime']}); - assert.strictEqual(kanban.$('.o_kanban_header .o_kanban_quick_add i').length, 0, + assert.containsNone(kanban, '.o_kanban_header .o_kanban_quick_add i', "quick create should be disabled when grouped on a datetime field"); // clicking on CREATE in control panel should not open a quick create - kanban.$buttons.find('.o-kanban-button-new').click(); - assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 0, + testUtils.kanban.clickCreate(kanban); + assert.containsNone(kanban, '.o_kanban_quick_create', "should not have opened the quick create widget"); kanban.destroy(); @@ -1899,17 +1853,17 @@ QUnit.module('Views', { groupBy: ['foo'], }); - assert.strictEqual(kanban.$('.o_kanban_header .o_kanban_quick_add i').length, 3, + assert.containsN(kanban, '.o_kanban_header .o_kanban_quick_add i', 3, "quick create should be enabled when grouped on a char field"); kanban.reload({groupBy: ['date']}); - assert.strictEqual(kanban.$('.o_kanban_header .o_kanban_quick_add i').length, 0, + assert.containsNone(kanban, '.o_kanban_header .o_kanban_quick_add i', "quick create should now be disabled (grouped on date field)"); kanban.reload({groupBy: ['bar']}); - assert.strictEqual(kanban.$('.o_kanban_header .o_kanban_quick_add i').length, 2, + assert.containsN(kanban, '.o_kanban_header .o_kanban_quick_add i', 2, "quick create should be enabled again (grouped on boolean field)"); kanban.destroy(); @@ -1937,16 +1891,16 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_kanban_header .o_kanban_quick_add i').length, 3, + assert.containsN(kanban, '.o_kanban_header .o_kanban_quick_add i', 3, "quick create should be enabled when grouped on a char field"); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1, + assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "first column should contain 1 record"); - kanban.$('.o_kanban_header:first .o_kanban_quick_add i').click(); - kanban.$('.o_kanban_quick_create input').val('new record').trigger('input'); - kanban.$('.o_kanban_add').click(); + testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i')); + testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'new record'); + testUtils.dom.click(kanban.$('.o_kanban_add')); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 2, + assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "first column should now contain 2 records"); kanban.destroy(); @@ -1974,14 +1928,14 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_kanban_header .o_kanban_quick_add i').length, 2, + assert.containsN(kanban, '.o_kanban_header .o_kanban_quick_add i', 2, "quick create should be enabled when grouped on a boolean field"); assert.strictEqual(kanban.$('.o_kanban_group:nth(1) .o_kanban_record').length, 3, "second column (true) should contain 3 records"); - kanban.$('.o_kanban_header:nth(1) .o_kanban_quick_add i').click(); - kanban.$('.o_kanban_quick_create input').val('new record').trigger('input'); - kanban.$('.o_kanban_add').click(); + testUtils.dom.click(kanban.$('.o_kanban_header:nth(1) .o_kanban_quick_add i')); + testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'new record'); + testUtils.dom.click(kanban.$('.o_kanban_add')); assert.strictEqual(kanban.$('.o_kanban_group:nth(1) .o_kanban_record').length, 4, "second column (true) should now contain 4 records"); @@ -2011,16 +1965,16 @@ QUnit.module('Views', { groupBy: ['state'], }); - assert.strictEqual(kanban.$('.o_kanban_header .o_kanban_quick_add i').length, 3, + assert.containsN(kanban, '.o_kanban_header .o_kanban_quick_add i', 3, "quick create should be enabled when grouped on a selection field"); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1, + assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "first column (abc) should contain 1 record"); - kanban.$('.o_kanban_header:first .o_kanban_quick_add i').click(); - kanban.$('.o_kanban_quick_create input').val('new record').trigger('input'); - kanban.$('.o_kanban_add').click(); + testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i')); + testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'new record'); + testUtils.dom.click(kanban.$('.o_kanban_add')); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 2, + assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "first column (abc) should contain 2 records"); kanban.destroy(); @@ -2055,17 +2009,17 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_kanban_header .o_kanban_quick_add i').length, 3, + assert.containsN(kanban, '.o_kanban_header .o_kanban_quick_add i', 3, "quick create should be enabled when grouped on a char field"); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1, + assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "first column should contain 1 record"); - kanban.$('.o_kanban_header:first .o_kanban_quick_add i').click(); + testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i')); assert.strictEqual(kanban.$('.o_kanban_quick_create input').val(), 'yop', "should have set the correct foo value by default"); - kanban.$('.o_kanban_add').click(); + testUtils.dom.click(kanban.$('.o_kanban_add')); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 2, + assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "first column should now contain 2 records"); kanban.destroy(); @@ -2100,15 +2054,15 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_kanban_header .o_kanban_quick_add i').length, 2, + assert.containsN(kanban, '.o_kanban_header .o_kanban_quick_add i', 2, "quick create should be enabled when grouped on a boolean field"); assert.strictEqual(kanban.$('.o_kanban_group:nth(1) .o_kanban_record').length, 3, "second column (true) should contain 3 records"); - kanban.$('.o_kanban_header:nth(1) .o_kanban_quick_add i').click(); + testUtils.dom.click(kanban.$('.o_kanban_header:nth(1) .o_kanban_quick_add i')); assert.ok(kanban.$('.o_kanban_quick_create .o_field_boolean input').is(':checked'), "should have set the correct bar value by default"); - kanban.$('.o_kanban_add').click(); + testUtils.dom.click(kanban.$('.o_kanban_add')); assert.strictEqual(kanban.$('.o_kanban_group:nth(1) .o_kanban_record').length, 4, "second column (true) should now contain 4 records"); @@ -2145,17 +2099,17 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_kanban_header .o_kanban_quick_add i').length, 3, + assert.containsN(kanban, '.o_kanban_header .o_kanban_quick_add i', 3, "quick create should be enabled when grouped on a selection field"); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1, + assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "first column (abc) should contain 1 record"); - kanban.$('.o_kanban_header:first .o_kanban_quick_add i').click(); + testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i')); assert.strictEqual(kanban.$('.o_kanban_quick_create select').val(), '"abc"', "should have set the correct state value by default"); - kanban.$('.o_kanban_add').click(); + testUtils.dom.click(kanban.$('.o_kanban_add')); - assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 2, + assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "first column (abc) should now contain 2 records"); kanban.destroy(); @@ -2204,7 +2158,7 @@ QUnit.module('Views', { var $first_record = kanban.$('.o_kanban_record:first()'); assert.strictEqual($first_record.find('.o_field_many2manytags .o_tag').length, 2, 'first record should contain 2 tags'); - assert.ok($first_record.find('.o_tag:first()').hasClass('o_tag_color_2'), + assert.hasClass($first_record.find('.o_tag:first()'),'o_tag_color_2', 'first tag should have color 2'); assert.verifySteps(['/web/dataset/search_read', '/web/dataset/call_kw/category/read'], 'two RPC should have been done (one search read and one read for the m2m)'); @@ -2214,7 +2168,7 @@ QUnit.module('Views', { 'there should be only one tag in second record'); // Write on the record using the priority widget to trigger a re-render in readonly - kanban.$('.o_field_widget.o_priority a.o_priority_star.fa-star-o').first().click(); + testUtils.dom.click(kanban.$('.o_field_widget.o_priority a.o_priority_star.fa-star-o').first()); assert.verifySteps([ '/web/dataset/search_read', '/web/dataset/call_kw/category/read', @@ -2226,7 +2180,7 @@ QUnit.module('Views', { 'first record should still contain only 2 tags'); // click on a tag (should trigger switch_view) - kanban.$('.o_tag:contains(gold):first').click(); + testUtils.dom.click(kanban.$('.o_tag:contains(gold):first')); kanban.destroy(); }); @@ -2291,7 +2245,7 @@ QUnit.module('Views', { ev.preventDefault(); }); - $testLink.click(); + testUtils.dom.click($testLink); $(document.body).off('click.o_test'); kanban.destroy(); @@ -2372,7 +2326,7 @@ QUnit.module('Views', { }, }); - kanban.reload(); + testUtils.kanban.reload(kanban); assert.verifySteps([ 'read_group', '/web/dataset/search_read', @@ -2461,22 +2415,18 @@ QUnit.module('Views', { }, }, }); - assert.strictEqual(kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record').length, 2, - "column should contain 2 record(s)"); - assert.strictEqual(kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record').length, 2, - "column should contain 2 record(s)"); + assert.containsN(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record', 2); + assert.containsN(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record', 2); + assert.containsN(kanban, '.thisiseditable', 4); - assert.strictEqual(kanban.$('.thisiseditable').length, 4, "all records should be editable"); var $record = kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record:first'); var $group = kanban.$('.o_kanban_group:nth-child(2)'); envIDs = [3, 2, 4, 1]; // first record of first column moved to the bottom of second column - testUtils.dragAndDrop($record, $group); + testUtils.dom.dragAndDrop($record, $group); - assert.strictEqual(kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record').length, 1, - "column should now contain 1 record(s)"); - assert.strictEqual(kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record').length, 3, - "column should contain 3 record(s)"); - assert.strictEqual(kanban.$('.thisiseditable').length, 4, "all records should be editable"); + assert.containsOnce(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record'); + assert.containsN(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record', 3); + assert.containsN(kanban, '.thisiseditable', 4); kanban.destroy(); }); @@ -2506,19 +2456,15 @@ QUnit.module('Views', { return this._super(route, args); }, }); - assert.strictEqual(kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record').length, 1, - "column should contain 1 record(s)"); - assert.strictEqual(kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record').length, 1, - "column should contain 1 record(s)"); + assert.containsOnce(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record'); + assert.containsOnce(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record'); var $record = kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record:first'); var $group = kanban.$('.o_kanban_group:nth-child(2)'); - testUtils.dragAndDrop($record, $group); + testUtils.dom.dragAndDrop($record, $group); - assert.strictEqual(kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record').length, 0, - "column should now contain 0 record(s)"); - assert.strictEqual(kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record').length, 2, - "column should contain 2 record(s)"); + assert.containsNone(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record'); + assert.containsN(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record', 2); kanban.destroy(); }); @@ -2550,42 +2496,36 @@ QUnit.module('Views', { }); // simulate an update coming from the searchview, with another groupby given kanban.update({groupBy: ['state']}); - assert.strictEqual(kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record').length, 1, - "column should contain 1 record(s)"); - assert.strictEqual(kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record').length, 1, - "column should contain 1 record(s)"); + assert.containsOnce(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record'); + assert.containsOnce(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record'); + // drag&drop a record in another column var $record = kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record:first'); var $group = kanban.$('.o_kanban_group:nth-child(2)'); - testUtils.dragAndDrop($record, $group); + testUtils.dom.dragAndDrop($record, $group); // should not be draggable - assert.strictEqual(kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record').length, 1, - "column should now contain 1 record(s)"); - assert.strictEqual(kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record').length, 1, - "column should contain 1 record(s)"); + assert.containsOnce(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record'); + assert.containsOnce(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record'); // simulate an update coming from the searchview, with another groupby given kanban.update({groupBy: ['foo']}); - assert.strictEqual(kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record').length, 1, - "column should contain 1 record(s)"); - assert.strictEqual(kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record').length, 2, - "column should contain 2 record(s)"); + assert.containsOnce(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record'); + assert.containsN(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record', 2); + // drag&drop a record in another column $record = kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record:first'); $group = kanban.$('.o_kanban_group:nth-child(2)'); - testUtils.dragAndDrop($record, $group); + testUtils.dom.dragAndDrop($record, $group); // should not be draggable - assert.strictEqual(kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record').length, 1, - "column should now contain 1 record(s)"); - assert.strictEqual(kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record').length, 2, - "column should contain 2 record(s)"); + assert.containsOnce(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record'); + assert.containsN(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record', 2); // drag&drop a record in the same column var $record1 = kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record:eq(0)'); var $record2 = kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record:eq(1)'); assert.strictEqual($record1.text(), "blipDEF", "first record should be DEF"); assert.strictEqual($record2.text(), "blipGHI", "second record should be GHI"); - testUtils.dragAndDrop($record2, $record1, {position: 'top'}); + testUtils.dom.dragAndDrop($record2, $record1, {position: 'top'}); // should still be able to resequence assert.strictEqual(kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record:eq(0)').text(), "blipGHI", "records should have been resequenced"); @@ -2627,17 +2567,16 @@ QUnit.module('Views', { }, }); - assert.ok(kanban.$('.o_kanban_view').hasClass('o_kanban_grouped'), - "should have classname 'o_kanban_grouped'"); - assert.strictEqual(kanban.$('.o_kanban_group').length, 2, "should have " + 2 + " columns"); + assert.hasClass(kanban.$('.o_kanban_view'), 'o_kanban_grouped'); + assert.containsN(kanban, '.o_kanban_group', 2, "should have " + 2 + " columns"); // simulate an update coming from the searchview, with another groupby given kanban.update({groupBy: ['product_id']}); - assert.strictEqual(kanban.$('.o_kanban_group').length, 2, "should now have " + 3 + " columns"); + assert.containsN(kanban, '.o_kanban_group', 2, "should now have " + 3 + " columns"); // simulate an update coming from the searchview, removing the previously set groupby kanban.update({groupBy: []}); - assert.strictEqual(kanban.$('.o_kanban_group').length, 2, "should have " + 2 + " columns again"); + assert.containsN(kanban, '.o_kanban_group', 2, "should have " + 2 + " columns again"); kanban.destroy(); }); @@ -2671,7 +2610,7 @@ QUnit.module('Views', { '', }); - testUtils.intercept(kanban, 'switch_view', function (event) { + testUtils.mock.intercept(kanban, 'switch_view', function (event) { assert.deepEqual(event.data, { view_type: 'form', res_id: 1, @@ -2679,7 +2618,7 @@ QUnit.module('Views', { model: 'partner', }); }); - kanban.$('a').first().click(); + testUtils.dom.click(kanban.$('a').first()); kanban.destroy(); }); @@ -2709,12 +2648,13 @@ QUnit.module('Views', { // fold the second group and check that the res_ids it contains are no // longer in the environment envIDs = [1, 3]; - kanban.$('.o_kanban_group:last .o_kanban_toggle_fold').click(); + testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last')); + testUtils.dom.click(kanban.$('.o_kanban_group:last .o_kanban_toggle_fold')); // re-open the second group and check that the res_ids it contains are // back in the environment envIDs = [1, 3, 2, 4]; - kanban.$('.o_kanban_group:last .o_kanban_toggle_fold').click(); + testUtils.dom.click(kanban.$('.o_kanban_group:last')); kanban.destroy(); }); @@ -2747,11 +2687,11 @@ QUnit.module('Views', { return this._super(route, args); }, }); - assert.strictEqual(kanban.$('.o_column_quick_create').length, 1, "should have a quick create column"); + assert.containsOnce(kanban, '.o_column_quick_create', "should have a quick create column"); assert.notOk(kanban.$('.o_column_quick_create input').is(':visible'), "the input should not be visible"); - kanban.$('.o_quick_create_folded').click(); + testUtils.dom.click(kanban.$('.o_quick_create_folded')); assert.ok(kanban.$('.o_column_quick_create input').is(':visible'), "the input should be visible"); @@ -2764,39 +2704,39 @@ QUnit.module('Views', { assert.notOk(kanban.$('.o_column_quick_create input').is(':visible'), "the input should not be visible after discard"); - kanban.$('.o_quick_create_folded').click(); + testUtils.dom.click(kanban.$('.o_quick_create_folded')); assert.ok(kanban.$('.o_column_quick_create input').is(':visible'), "the input should be visible"); kanban.$('.o_column_quick_create input').val('new value'); - kanban.$('.o_column_quick_create button.o_kanban_add').click(); + testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); assert.strictEqual(kanban.$('.o_kanban_group:last span:contains(new value)').length, 1, "the last column should be the newly created one"); assert.ok(_.isNumber(kanban.$('.o_kanban_group:last').data('id')), 'the created column should have the correct id'); - assert.ok(!kanban.$('.o_kanban_group:last').hasClass('o_column_folded'), + assert.doesNotHaveClass(kanban.$('.o_kanban_group:last'), 'o_column_folded', 'the created column should not be folded'); // fold and unfold the created column, and check that no RPC is done (as there is no record) nbRPCs = 0; - kanban.$('.o_kanban_group:last .o_kanban_toggle_fold').click(); // fold the group - assert.ok(kanban.$('.o_kanban_group:last').hasClass('o_column_folded'), + testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last')); + testUtils.dom.click(kanban.$('.o_kanban_group:last .o_kanban_toggle_fold')); + assert.hasClass(kanban.$('.o_kanban_group:last'),'o_column_folded', 'the created column should now be folded'); - kanban.$('.o_kanban_group:last').click(); - assert.ok(!kanban.$('.o_kanban_group:last').hasClass('o_column_folded'), - 'the created column should not be folded'); + testUtils.dom.click(kanban.$('.o_kanban_group:last')); + assert.doesNotHaveClass(kanban.$('.o_kanban_group:last'), 'o_column_folded'); assert.strictEqual(nbRPCs, 0, 'no rpc should have been done when folding/unfolding'); // quick create a record - kanban.$buttons.find('.o-kanban-button-new').click(); // Click on 'Create' - assert.ok(kanban.$('.o_kanban_group:first() > div:nth(1)').hasClass('o_kanban_quick_create'), + testUtils.kanban.clickCreate(kanban); + assert.hasClass(kanban.$('.o_kanban_group:first() > div:nth(1)'),'o_kanban_quick_create', "clicking on create should open the quick_create in the first column"); kanban.destroy(); }); QUnit.test('auto fold group when reach the limit', function (assert) { - assert.expect(7); + assert.expect(9); var data = this.data; for (var i = 0; i < 12; i++) { @@ -2835,28 +2775,19 @@ QUnit.module('Views', { }); // we look if column are fold/unfold according what is expected - assert.notOk(kanban.$('.o_kanban_group:nth-child(2)').hasClass('o_column_folded'), - 'second column must be unfolded by default'); - assert.notOk(kanban.$('.o_kanban_group:nth-child(4)').hasClass('o_column_folded'), - 'fourth column must be unfolded by default'); - assert.notOk(kanban.$('.o_kanban_group:nth-child(10)').hasClass('o_column_folded'), - 'tenth column must be unfolded by default'); - assert.ok( - kanban.$('.o_kanban_group:nth-child(3)').hasClass('o_column_folded') - && kanban.$('.o_kanban_group:nth-child(9)').hasClass('o_column_folded'), - 'third and ninth columns must be folded by default'); + assert.doesNotHaveClass(kanban.$('.o_kanban_group:nth-child(2)'), 'o_column_folded'); + assert.doesNotHaveClass(kanban.$('.o_kanban_group:nth-child(4)'), 'o_column_folded'); + assert.doesNotHaveClass(kanban.$('.o_kanban_group:nth-child(10)'), 'o_column_folded'); + assert.hasClass(kanban.$('.o_kanban_group:nth-child(3)'), 'o_column_folded'); + assert.hasClass(kanban.$('.o_kanban_group:nth-child(9)'), 'o_column_folded'); + // we look if columns are actually fold after we reached the limit - assert.ok( - kanban.$('.o_kanban_group:nth-child(13)').hasClass('o_column_folded') - && kanban.$('.o_kanban_group:nth-child(14)').hasClass('o_column_folded'), - 'thirteenth and fourtheenth columns must be folded because we reached the limit'); + assert.hasClass(kanban.$('.o_kanban_group:nth-child(13)'), 'o_column_folded'); + assert.hasClass(kanban.$('.o_kanban_group:nth-child(14)'), 'o_column_folded'); + // we look if we have the right count of folded/unfolded column - assert.strictEqual( - kanban.$('.o_kanban_group:not(.o_column_folded)').length, - 10, 'must have ten opened columns'); - assert.strictEqual( - kanban.$('.o_kanban_group.o_column_folded').length, - 4, 'must have four folded columns'); + assert.containsN(kanban, '.o_kanban_group:not(.o_column_folded)', 10); + assert.containsN(kanban, '.o_kanban_group.o_column_folded', 4); kanban.destroy(); }); @@ -2877,13 +2808,12 @@ QUnit.module('Views', { groupBy: ['product_id'], }); - kanban.$('.o_quick_create_folded').click(); - + testUtils.dom.click(kanban.$('.o_quick_create_folded')); assert.ok(kanban.$('.o_discard_msg').is(':visible'), 'the ESC to discard message is visible'); - // click in the column, but outside the input (to lose focus) - kanban.$('.o_kanban_header').click(); + // click outside the column (to lose focus) + testUtils.dom.clickFirst(kanban.$('.o_kanban_header')); assert.notOk(kanban.$('.o_discard_msg').is(':visible'), 'the ESC to discard message is no longer visible'); @@ -2893,7 +2823,7 @@ QUnit.module('Views', { QUnit.test('delete a column in grouped on m2o', function (assert) { assert.expect(36); - testUtils.patch(KanbanRenderer, { + testUtils.mock.patch(KanbanRenderer, { _renderGrouped: function () { this._super.apply(this, arguments); // set delay and revert animation time to 0 so dummy drag and drop works @@ -2931,14 +2861,14 @@ QUnit.module('Views', { }); // check the initial rendering - assert.strictEqual(kanban.$('.o_kanban_group').length, 2, "should have two columns"); + assert.containsN(kanban, '.o_kanban_group', 2, "should have two columns"); assert.strictEqual(kanban.$('.o_kanban_group:first').data('id'), 3, 'first column should be [3, "hello"]'); assert.strictEqual(kanban.$('.o_kanban_group:last').data('id'), 5, 'second column should be [5, "xmo"]'); assert.strictEqual(kanban.$('.o_kanban_group:last .o_column_title').text(), 'xmo', 'second column should have correct title'); - assert.strictEqual(kanban.$('.o_kanban_group:last .o_kanban_record').length, 2, + assert.containsN(kanban, '.o_kanban_group:last .o_kanban_record', 2, "second column should have two records"); // check available actions in kanban header's config dropdown @@ -2952,17 +2882,19 @@ QUnit.module('Views', { assert.ok(!kanban.$('.o_kanban_group:first .o_column_unarchive_records').length, "should not be able to restore all the records"); // delete second column (first cancel the confirm request, then confirm) - kanban.$('.o_kanban_group:last .o_column_delete').click(); // click on delete + testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last')); + testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_delete')); assert.ok($('.modal').length, 'a confirm modal should be displayed'); - $('.modal-footer .btn-secondary').click(); // click on cancel + testUtils.modal.clickButton('Cancel'); // click on cancel assert.strictEqual(kanban.$('.o_kanban_group:last').data('id'), 5, 'column [5, "xmo"] should still be there'); - kanban.$('.o_kanban_group:last .o_column_delete').click(); // click on delete + testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last')); + testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_delete')); assert.ok($('.modal').length, 'a confirm modal should be displayed'); - $('.modal-footer .btn-primary').click(); // click on confirm + testUtils.modal.clickButton('Ok'); // click on confirm assert.strictEqual(kanban.$('.o_kanban_group:last').data('id'), 3, 'last column should now be [3, "hello"]'); - assert.strictEqual(kanban.$('.o_kanban_group').length, 2, "should still have two columns"); + assert.containsN(kanban, '.o_kanban_group', 2, "should still have two columns"); assert.ok(!_.isNumber(kanban.$('.o_kanban_group:first').data('id')), 'first column should have no id (Undefined column)'); // check available actions on 'Undefined' column @@ -2979,22 +2911,23 @@ QUnit.module('Views', { "the old widgets should have been correctly deleted"); // test column drag and drop having an 'Undefined' column - testUtils.dragAndDrop( + testUtils.dom.dragAndDrop( kanban.$('.o_kanban_header_title:first'), kanban.$('.o_kanban_header_title:last'), {position: 'right'} ); assert.strictEqual(resequencedIDs, undefined, "resequencing require at least 2 not Undefined columns"); + testUtils.dom.click(kanban.$('.o_column_quick_create .o_quick_create_folded')); kanban.$('.o_column_quick_create input').val('once third column'); - kanban.$('.o_column_quick_create button.o_kanban_add').click(); + testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); var newColumnID = kanban.$('.o_kanban_group:last').data('id'); - testUtils.dragAndDrop( + testUtils.dom.dragAndDrop( kanban.$('.o_kanban_header_title:first'), kanban.$('.o_kanban_header_title:last'), {position: 'right'} ); assert.deepEqual([3, newColumnID], resequencedIDs, "moving the Undefined column should not affect order of other columns") - testUtils.dragAndDrop( + testUtils.dom.dragAndDrop( kanban.$('.o_kanban_header_title:first'), kanban.$('.o_kanban_header_title:nth(1)'), {position: 'right'} ); @@ -3002,7 +2935,7 @@ QUnit.module('Views', { "moved column should be resequenced accordingly") kanban.destroy(); - testUtils.unpatch(KanbanRenderer); + testUtils.mock.unpatch(KanbanRenderer); }); QUnit.test('create a column, delete it and create another one', function (assert) { @@ -3021,24 +2954,25 @@ QUnit.module('Views', { groupBy: ['product_id'], }); - assert.strictEqual(kanban.$('.o_kanban_group').length, 2, "should have two columns"); + assert.containsN(kanban, '.o_kanban_group', 2, "should have two columns"); - kanban.$('.o_column_quick_create').click(); + testUtils.dom.click(kanban.$('.o_column_quick_create .o_quick_create_folded')); kanban.$('.o_column_quick_create input').val('new column 1'); - kanban.$('.o_column_quick_create button.o_kanban_add').click(); + testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); - assert.strictEqual(kanban.$('.o_kanban_group').length, 3, "should have two columns"); + assert.containsN(kanban, '.o_kanban_group', 3, "should have two columns"); - kanban.$('.o_kanban_group:last .o_column_delete').click(); - $('.modal-footer .btn-primary').click(); + testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last')); + testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_delete')); + testUtils.modal.clickButton('Ok'); - assert.strictEqual(kanban.$('.o_kanban_group').length, 2, "should have twos columns"); + assert.containsN(kanban, '.o_kanban_group', 2, "should have twos columns"); - kanban.$('.o_column_quick_create').click(); + testUtils.dom.click(kanban.$('.o_column_quick_create .o_quick_create_folded')); kanban.$('.o_column_quick_create input').val('new column 2'); - kanban.$('.o_column_quick_create button.o_kanban_add').click(); + testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); - assert.strictEqual(kanban.$('.o_kanban_group').length, 3, "should have three columns"); + assert.containsN(kanban, '.o_kanban_group', 3, "should have three columns"); assert.strictEqual(kanban.$('.o_kanban_group:last span:contains(new column 2)').length, 1, "the last column should be the newly created one"); kanban.destroy(); @@ -3071,33 +3005,37 @@ QUnit.module('Views', { 'title of the column should be "xmo"'); // edit the title of column [5, 'xmo'] and close without saving - kanban.$('.o_kanban_group[data-id=5] .o_column_edit').click(); // click on 'Edit' - assert.ok($('.modal .o_form_editable').length, 'a form view should be open in a modal'); + testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group[data-id=5]')); + testUtils.dom.click(kanban.$('.o_kanban_group[data-id=5] .o_column_edit')); + assert.containsOnce(document.body, '.modal .o_form_editable', + "a form view should be open in a modal"); assert.strictEqual($('.modal .o_form_editable input').val(), 'xmo', 'the name should be "xmo"'); - $('.modal .o_form_editable input').val('ged').trigger('input'); // change the value + testUtils.fields.editInput($('.modal .o_form_editable input'), 'ged'); // change the value nbRPCs = 0; - $('.modal-header .close').click(); // click on the cross to close the modal - assert.ok(!$('.modal').length, 'the modal should be closed'); + testUtils.dom.click($('.modal-header .close')); + assert.containsNone(document.body, '.modal'); assert.strictEqual(kanban.$('.o_kanban_group[data-id=5] .o_column_title').text(), 'xmo', 'title of the column should still be "xmo"'); assert.strictEqual(nbRPCs, 0, 'no RPC should have been done'); // edit the title of column [5, 'xmo'] and discard - kanban.$('.o_kanban_group[data-id=5] .o_column_edit').click(); // click on 'Edit' - $('.modal .o_form_editable input').val('ged').trigger('input'); // change the value + testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group[data-id=5]')); + testUtils.dom.click(kanban.$('.o_kanban_group[data-id=5] .o_column_edit')); + testUtils.fields.editInput($('.modal .o_form_editable input'), 'ged'); // change the value nbRPCs = 0; - $('.modal-footer .btn-secondary').click(); // click on discard - assert.ok(!$('.modal').length, 'the modal should be closed'); + testUtils.modal.clickButton('Discard'); + assert.containsNone(document.body, '.modal'); assert.strictEqual(kanban.$('.o_kanban_group[data-id=5] .o_column_title').text(), 'xmo', 'title of the column should still be "xmo"'); assert.strictEqual(nbRPCs, 0, 'no RPC should have been done'); // edit the title of column [5, 'xmo'] and save - kanban.$('.o_kanban_group[data-id=5] .o_column_edit').click(); // click on 'Edit' - $('.modal .o_form_editable input').val('ged').trigger('input'); // change the value + testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group[data-id=5]')); + testUtils.dom.click(kanban.$('.o_kanban_group[data-id=5] .o_column_edit')); + testUtils.fields.editInput($('.modal .o_form_editable input'), 'ged'); // change the value nbRPCs = 0; - $('.modal-footer .btn-primary').click(); // click on save + testUtils.modal.clickButton('Save'); // click on save assert.ok(!$('.modal').length, 'the modal should be closed'); assert.strictEqual(kanban.$('.o_kanban_group[data-id=5] .o_column_title').text(), 'ged', 'title of the column should be "ged"'); @@ -3122,10 +3060,8 @@ QUnit.module('Views', { domain: [['foo', '=', 'norecord']], }); - assert.strictEqual(kanban.$('.o_kanban_group').length, 0, - "there should be no column"); - assert.strictEqual(kanban.$('.o_column_quick_create').length, 1, - "should have a quick create column"); + assert.containsNone(kanban, '.o_kanban_group'); + assert.containsOnce(kanban, '.o_column_quick_create'); assert.ok(kanban.$('.o_column_quick_create input').is(':visible'), "the quick create should be opened"); @@ -3148,34 +3084,34 @@ QUnit.module('Views', { groupBy: ['product_id'], }); - assert.strictEqual(kanban.$('.o_kanban_group').length, 2, + assert.containsN(kanban, '.o_kanban_group', 2, "should have two columns"); - assert.strictEqual(kanban.$('.o_column_quick_create').length, 1, + assert.containsOnce(kanban, '.o_column_quick_create', "should have a ColumnQuickCreate widget"); - assert.strictEqual(kanban.$('.o_column_quick_create .o_quick_create_folded:visible').length, 1, + assert.containsOnce(kanban, '.o_column_quick_create .o_quick_create_folded:visible', "the ColumnQuickCreate should be folded"); - assert.strictEqual(kanban.$('.o_column_quick_create .o_quick_create_unfolded:visible').length, 0, + assert.containsNone(kanban, '.o_column_quick_create .o_quick_create_unfolded:visible', "the ColumnQuickCreate should be folded"); // add a new column - kanban.$('.o_column_quick_create .o_quick_create_folded').click(); - assert.strictEqual(kanban.$('.o_column_quick_create .o_quick_create_folded:visible').length, 0, + testUtils.dom.click(kanban.$('.o_column_quick_create .o_quick_create_folded')); + assert.containsNone(kanban, '.o_column_quick_create .o_quick_create_folded:visible', "the ColumnQuickCreate should be unfolded"); - assert.strictEqual(kanban.$('.o_column_quick_create .o_quick_create_unfolded:visible').length, 1, + assert.containsOnce(kanban, '.o_column_quick_create .o_quick_create_unfolded:visible', "the ColumnQuickCreate should be unfolded"); kanban.$('.o_column_quick_create input').val('New Column 1'); - kanban.$('.o_column_quick_create .btn-primary').click(); - assert.strictEqual(kanban.$('.o_kanban_group').length, 3, + testUtils.dom.click(kanban.$('.o_column_quick_create .btn-primary')); + assert.containsN(kanban, '.o_kanban_group', 3, "should now have three columns"); // add another column - assert.strictEqual(kanban.$('.o_column_quick_create .o_quick_create_folded:visible').length, 0, + assert.containsNone(kanban, '.o_column_quick_create .o_quick_create_folded:visible', "the ColumnQuickCreate should still be unfolded"); - assert.strictEqual(kanban.$('.o_column_quick_create .o_quick_create_unfolded:visible').length, 1, + assert.containsOnce(kanban, '.o_column_quick_create .o_quick_create_unfolded:visible', "the ColumnQuickCreate should still be unfolded"); kanban.$('.o_column_quick_create input').val('New Column 2'); - kanban.$('.o_column_quick_create .btn-primary').click(); - assert.strictEqual(kanban.$('.o_kanban_group').length, 4, + testUtils.dom.click(kanban.$('.o_column_quick_create .btn-primary')); + assert.containsN(kanban, '.o_kanban_group', 4, "should now have four columns"); kanban.destroy(); @@ -3206,17 +3142,17 @@ QUnit.module('Views', { groupBy: ['product_id'], }); - assert.strictEqual(kanban.$('.o_column_quick_create').length, 1, + assert.containsOnce(kanban, '.o_column_quick_create', "should have a ColumnQuickCreate widget"); // open the quick create - kanban.$('.o_column_quick_create .o_quick_create_folded').click(); + testUtils.dom.click(kanban.$('.o_column_quick_create .o_quick_create_folded')); - assert.strictEqual(kanban.$('.o_column_quick_create .o_kanban_examples:visible').length, 1, + assert.containsOnce(kanban, '.o_column_quick_create .o_kanban_examples:visible', "should have a link to see examples"); // click to see the examples - kanban.$('.o_column_quick_create .o_kanban_examples').click(); + testUtils.dom.click(kanban.$('.o_column_quick_create .o_kanban_examples')); assert.strictEqual($('.modal .o_kanban_examples_dialog').length, 1, "should have open the examples dialog"); @@ -3263,7 +3199,7 @@ QUnit.module('Views', { }); kanban.update({groupBy: []}); - assert.ok(kanban.$('.o_kanban_view').hasClass('o_kanban_grouped')); + assert.hasClass(kanban.$('.o_kanban_view'), 'o_kanban_grouped'); kanban.destroy(); }); @@ -3281,11 +3217,11 @@ QUnit.module('Views', { '' + '', }); - assert.notOk(kanban.$('.o_kanban_view').hasClass('o_kanban_grouped'), "should not be grouped"); + assert.doesNotHaveClass(kanban.$('.o_kanban_view'), 'o_kanban_grouped', "should not be grouped"); kanban.update({groupBy: ['product_id']}); - assert.ok(kanban.$('.o_kanban_view').hasClass('o_kanban_grouped'), "should be grouped"); + assert.hasClass(kanban.$('.o_kanban_view'),'o_kanban_grouped', "should be grouped"); kanban.update({groupBy: []}); - assert.notOk(kanban.$('.o_kanban_view').hasClass('o_kanban_grouped'), "should not be grouped"); + assert.doesNotHaveClass(kanban.$('.o_kanban_view'), 'o_kanban_grouped', "should not be grouped"); kanban.destroy(); }); @@ -3313,7 +3249,7 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_view_nocontent').length, 1, + assert.containsOnce(kanban, '.o_view_nocontent', "should display the no content helper"); assert.strictEqual(kanban.$('.o_view_nocontent p.hello:contains(add a partner)').length, 1, @@ -3322,7 +3258,7 @@ QUnit.module('Views', { this.data.partner.records = records; kanban.reload(); - assert.strictEqual(kanban.$('.o_view_nocontent').length, 0, + assert.containsNone(kanban, '.o_view_nocontent', "should not display the no content helper"); kanban.destroy(); }); @@ -3362,9 +3298,9 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_kanban_group').length, 2, + assert.containsN(kanban, '.o_kanban_group', 2, "there should be two columns"); - assert.strictEqual(kanban.$('.o_kanban_record').length, 0, + assert.containsNone(kanban, '.o_kanban_record', "there should be no records"); kanban.destroy(); @@ -3392,13 +3328,13 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_kanban_group').length, 0, + assert.containsNone(kanban, '.o_kanban_group', "there should be no columns"); - assert.strictEqual(kanban.$('.o_kanban_record').length, 0, + assert.containsNone(kanban, '.o_kanban_record', "there should be no records"); - assert.strictEqual(kanban.$('.o_view_nocontent').length, 0, + assert.containsNone(kanban, '.o_view_nocontent', "there should be no nocontent helper (we are in 'column creation mode')"); - assert.strictEqual(kanban.$('.o_column_quick_create').length, 1, + assert.containsOnce(kanban, '.o_column_quick_create', "there should be a column quick create"); kanban.destroy(); }); @@ -3425,14 +3361,14 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_view_nocontent').length, 0, + assert.containsNone(kanban, '.o_view_nocontent', "there should be no nocontent helper (we are in 'column creation mode')"); // creating a new column kanban.$('.o_column_quick_create .o_input').val('applejack'); - kanban.$('.o_column_quick_create .o_kanban_add').click(); + testUtils.dom.click(kanban.$('.o_column_quick_create .o_kanban_add')); - assert.strictEqual(kanban.$('.o_view_nocontent').length, 0, + assert.containsNone(kanban, '.o_view_nocontent', "there should be no nocontent helper (still in 'column creation mode')"); // leaving column creation mode @@ -3441,7 +3377,7 @@ QUnit.module('Views', { which: $.ui.keyCode.ESCAPE, })); - assert.strictEqual(kanban.$('.o_view_nocontent').length, 1, + assert.containsOnce(kanban, '.o_view_nocontent', "there should be a nocontent helper"); kanban.destroy(); @@ -3478,12 +3414,12 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_view_nocontent').length, 1, + assert.containsOnce(kanban, '.o_view_nocontent', "there should be a nocontent helper"); - kanban.$('.o_kanban_add_column').click(); + testUtils.dom.click(kanban.$('.o_kanban_add_column')); - assert.strictEqual(kanban.$('.o_view_nocontent').length, 0, + assert.containsNone(kanban, '.o_view_nocontent', "there should be no nocontent helper (we are in 'column creation mode')"); kanban.destroy(); @@ -3520,15 +3456,15 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_view_nocontent').length, 1, + assert.containsOnce(kanban, '.o_view_nocontent', "there should be a nocontent helper"); // add a record - kanban.$('.o_kanban_quick_add').click(); - kanban.$('.o_kanban_quick_create .o_input').val('twilight sparkle').trigger('input'); - kanban.$('button.o_kanban_add').click(); + testUtils.dom.click(kanban.$('.o_kanban_quick_add')); + testUtils.fields.editInput(kanban.$('.o_kanban_quick_create .o_input'), 'twilight sparkle'); + testUtils.dom.click(kanban.$('.o_kanban_quick_create button.o_kanban_add')); - assert.strictEqual(kanban.$('.o_view_nocontent').length, 0, + assert.containsNone(kanban, '.o_view_nocontent', "there should be no nocontent helper (there is now one record)"); kanban.destroy(); @@ -3565,14 +3501,14 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_view_nocontent').length, 1, + assert.containsOnce(kanban, '.o_view_nocontent', "there should be a nocontent helper"); // add a record - kanban.$('.o_kanban_quick_add').click(); - kanban.$('.o_kanban_quick_create .o_input').val('twilight sparkle').trigger('input'); + testUtils.dom.click(kanban.$('.o_kanban_quick_add')); + testUtils.fields.editInput(kanban.$('.o_kanban_quick_create .o_input'), 'twilight sparkle'); - assert.strictEqual(kanban.$('.o_view_nocontent').length, 0, + assert.containsNone(kanban, '.o_view_nocontent', "there should be no nocontent helper (there is now one record)"); kanban.destroy(); @@ -3610,11 +3546,11 @@ QUnit.module('Views', { }); // add a record - kanban.$('.o_kanban_quick_add').click(); + testUtils.dom.click(kanban.$('.o_kanban_quick_add')); - kanban.$('.o_kanban_view').click(); + testUtils.dom.click(kanban.$('.o_kanban_view')); - assert.strictEqual(kanban.$('.o_view_nocontent').length, 1, + assert.containsOnce(kanban, '.o_view_nocontent', "there should be again a nocontent helper"); kanban.destroy(); @@ -3643,13 +3579,13 @@ QUnit.module('Views', { }, }); - assert.strictEqual(kanban.$('.o_kanban_group').length, 0, + assert.containsNone(kanban, '.o_kanban_group', "there should be no columns"); - assert.strictEqual(kanban.$('.o_kanban_record').length, 0, + assert.containsNone(kanban, '.o_kanban_record', "there should be no records"); - assert.strictEqual(kanban.$('.o_view_nocontent').length, 0, + assert.containsNone(kanban, '.o_view_nocontent', "there should not be a nocontent helper"); - assert.strictEqual(kanban.$('.o_column_quick_create').length, 0, + assert.containsNone(kanban, '.o_column_quick_create', "there should not be a column quick create"); kanban.destroy(); }); @@ -3678,9 +3614,9 @@ QUnit.module('Views', { '', }); - assert.strictEqual(kanban.$(".o_btn_test_1").length, 1, + assert.containsOnce(kanban, ".o_btn_test_1", "kanban should have one buttons of type 1"); - assert.strictEqual(kanban.$(".o_btn_test_2").length, 3, + assert.containsN(kanban, ".o_btn_test_2", 3, "kanban should have three buttons of type 2"); kanban.destroy(); }); @@ -3709,14 +3645,14 @@ QUnit.module('Views', { "kanban should have at least one button a1"); var count = 0; - testUtils.intercept(kanban, 'execute_action', function (event) { + testUtils.mock.intercept(kanban, 'execute_action', function (event) { count++; event.data.on_closed(); }); - $('button[data-name="a1"]').first().click(); + testUtils.dom.click($('button[data-name="a1"]').first()); assert.strictEqual(count, 1, "should have triggered a execute action"); - $('button[data-name="a1"]').first().click(); + testUtils.dom.click($('button[data-name="a1"]').first()); assert.strictEqual(count, 1, "double-click on kanban actions should be debounced"); assert.verifySteps([ @@ -3751,13 +3687,13 @@ QUnit.module('Views', { '', }); - testUtils.intercept(kanban, 'execute_action', function (event) { + testUtils.mock.intercept(kanban, 'execute_action', function (event) { data.partner.records[0].active = false; event.data.on_closed(); }); assert.strictEqual(kanban.$('.o_kanban_record:contains(yop)').length, 1, "should display 'yop' record"); - kanban.$('.o_kanban_record:contains(yop) button[data-name="toggle_active"]').click(); + testUtils.dom.click(kanban.$('.o_kanban_record:contains(yop) button[data-name="toggle_active"]')); assert.strictEqual(kanban.$('.o_kanban_record:contains(yop)').length, 0, "should remove 'yop' record from the view"); kanban.destroy(); @@ -3781,12 +3717,12 @@ QUnit.module('Views', { '', }); - testUtils.intercept(kanban, 'execute_action', function (event) { + testUtils.mock.intercept(kanban, 'execute_action', function (event) { event.data.on_closed(); }); try { - kanban.$('.o_kanban_record:contains(yop) button[data-name="toggle_action"]').click(); + testUtils.dom.click(kanban.$('.o_kanban_record:contains(yop) button[data-name="toggle_action"]')); assert.strictEqual(true, true, 'Everything went fine'); } catch (e) { assert.strictEqual(true, false, 'Error triggered at action execution'); @@ -3879,9 +3815,9 @@ QUnit.module('Views', { }, }); - assert.ok(kanban.$('.o_kanban_view').hasClass('ui-sortable'), + assert.hasClass(kanban.$('.o_kanban_view'),'ui-sortable', "columns should be sortable"); - assert.strictEqual(kanban.$('.o_kanban_group').length, 2, + assert.containsN(kanban, '.o_kanban_group', 2, "should have two columns"); assert.strictEqual(kanban.$('.o_kanban_group:first').data('id'), 3, "first column should be id 3 before resequencing"); @@ -3921,7 +3857,7 @@ QUnit.module('Views', { '', }); - assert.strictEqual(kanban.$('button.oe_kanban_action_button').length, 1, + assert.containsOnce(kanban, 'button.oe_kanban_action_button', "only one button should be visible"); kanban.destroy(); }); @@ -3967,7 +3903,7 @@ QUnit.module('Views', { var $firstRecord = kanban.$('.o_kanban_record:first()'); - assert.strictEqual(kanban.$('.o_kanban_record.oe_kanban_color_12').length, 0, + assert.containsNone(kanban, '.o_kanban_record.oe_kanban_color_12', "no record should have the color 12"); assert.strictEqual($firstRecord.find('.oe_kanban_colorpicker').length, 1, "there should be a color picker"); @@ -3975,7 +3911,8 @@ QUnit.module('Views', { "the color picker should have 12 children (the colors)"); // Set a color - $firstRecord.find('.oe_kanban_colorpicker a.oe_kanban_color_9').click(); + testUtils.kanban.toggleRecordDropdown($firstRecord); + testUtils.dom.click($firstRecord.find('.oe_kanban_colorpicker a.oe_kanban_color_9')); assert.ok(writeOnColor, "should write on the color field"); $firstRecord = kanban.$('.o_kanban_record:first()'); // First record is reloaded here assert.ok($firstRecord.is('.oe_kanban_color_9'), @@ -4020,7 +3957,7 @@ QUnit.module('Views', { // load more envIDs = [1, 2, 3, 4]; // id 3 will be loaded - kanban.$('.o_kanban_group:eq(1)').find('.o_kanban_load_more').click(); + testUtils.dom.click(kanban.$('.o_kanban_group:eq(1)').find('.o_kanban_load_more')); assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_record').length, 3, "there should now be 3 records in the column"); @@ -4086,7 +4023,7 @@ QUnit.module('Views', { assert.verifySteps([[7]], "only the appearing category should be fetched"); // load more - kanban.$('.o_kanban_group:eq(1)').find('.o_kanban_load_more').click(); + testUtils.dom.click(kanban.$('.o_kanban_group:eq(1)').find('.o_kanban_load_more')); assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_record').length, 3, "there should now be 3 records in the column"); @@ -4112,14 +4049,14 @@ QUnit.module('Views', { groupBy: ['product_id'], }); - assert.ok(kanban.$buttons.find('.o-kanban-button-new').hasClass('o_hidden'), + assert.isNotVisible(kanban.$buttons.find('.o-kanban-button-new'), "Create button should be hidden"); - kanban.$('.o_column_quick_create').click(); + testUtils.dom.click(kanban.$('.o_column_quick_create')); kanban.$('.o_column_quick_create input').val('new column'); - kanban.$('.o_column_quick_create button.o_kanban_add').click(); + testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); - assert.notOk(kanban.$buttons.find('.o-kanban-button-new').hasClass('o_hidden'), + assert.isVisible(kanban.$buttons.find('.o-kanban-button-new'), "Create button should now be visible"); kanban.destroy(); }); @@ -4149,13 +4086,13 @@ QUnit.module('Views', { }, }); - assert.ok(kanban.$('.o_kanban_view').hasClass('o_kanban_grouped'), + assert.hasClass(kanban.$('.o_kanban_view'),'o_kanban_grouped', "should have classname 'o_kanban_grouped'"); - assert.strictEqual(kanban.$('.o_kanban_group').length, 2, "should have " + 2 + " columns"); + assert.containsN(kanban, '.o_kanban_group', 2, "should have " + 2 + " columns"); // simulate an update coming from the searchview, with another groupby given kanban.update({groupBy: ['product_id']}); - assert.strictEqual(kanban.$('.o_kanban_group').length, 3, "should have " + 3 + " columns"); + assert.containsN(kanban, '.o_kanban_group', 3, "should have " + 3 + " columns"); assert.strictEqual(kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record').length, 1, "column should contain 1 record(s)"); assert.strictEqual(kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record').length, 2, @@ -4194,18 +4131,18 @@ QUnit.module('Views', { groupBy: ['product_id'], }); - kanban.$('.o_column_quick_create').click(); + testUtils.dom.click(kanban.$('.o_column_quick_create')); kanban.$('.o_column_quick_create input').val('column1'); - kanban.$('.o_column_quick_create button.o_kanban_add').click(); + testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); - kanban.$('.o_column_quick_create').click(); + testUtils.dom.click(kanban.$('.o_column_quick_create')); kanban.$('.o_column_quick_create input').val('column2'); - kanban.$('.o_column_quick_create button.o_kanban_add').click(); + testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); - kanban.$('.o_kanban_group:eq(1) .o_kanban_quick_add i').click(); + testUtils.dom.click(kanban.$('.o_kanban_group:eq(1) .o_kanban_quick_add i')); var $quickCreate = kanban.$('.o_kanban_group:eq(1) .o_kanban_quick_create'); - $quickCreate.find('input').val('new partner').trigger('input'); - $quickCreate.find('button.o_kanban_add').click(); + testUtils.fields.editInput($quickCreate.find('input'), 'new partner'); + testUtils.dom.click($quickCreate.find('button.o_kanban_add')); assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record').length, 0, "column should contain 0 record"); @@ -4214,7 +4151,7 @@ QUnit.module('Views', { var $record = kanban.$('.o_kanban_group:eq(1) .o_kanban_record:eq(0)'); var $group = kanban.$('.o_kanban_group:eq(0)'); - testUtils.dragAndDrop($record, $group); + testUtils.dom.dragAndDrop($record, $group); assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record').length, 1, "column should contain 1 records"); @@ -4224,7 +4161,7 @@ QUnit.module('Views', { $record = kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(0)'); $group = kanban.$('.o_kanban_group:eq(1)'); - testUtils.dragAndDrop($record, $group); + testUtils.dom.dragAndDrop($record, $group); assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record').length, 0, "column should contain 0 records"); @@ -4258,19 +4195,19 @@ QUnit.module('Views', { }, }); - kanban.$('.o_column_quick_create').click(); + testUtils.dom.click(kanban.$('.o_column_quick_create')); kanban.$('.o_column_quick_create input').val('column1'); - kanban.$('.o_column_quick_create button.o_kanban_add').click(); + testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); - kanban.$('.o_kanban_group:eq(0) .o_kanban_quick_add i').click(); + testUtils.dom.click(kanban.$('.o_kanban_group:eq(0) .o_kanban_quick_add i')); var $quickCreate = kanban.$('.o_kanban_group:eq(0) .o_kanban_quick_create'); - $quickCreate.find('input').val('record1').trigger('input'); - $quickCreate.find('button.o_kanban_add').click(); + testUtils.fields.editInput($quickCreate.find('input'), 'record1'); + testUtils.dom.click($quickCreate.find('button.o_kanban_add')); - kanban.$('.o_kanban_group:eq(0) .o_kanban_quick_add i').click(); + testUtils.dom.click(kanban.$('.o_kanban_group:eq(0) .o_kanban_quick_add i')); $quickCreate = kanban.$('.o_kanban_group:eq(0) .o_kanban_quick_create'); - $quickCreate.find('input').val('record2').trigger('input'); - $quickCreate.find('button.o_kanban_add').click(); + testUtils.fields.editInput($quickCreate.find('input'), 'record2'); + testUtils.dom.click($quickCreate.find('button.o_kanban_add')); assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record').length, 2, "column should contain 2 records"); @@ -4281,7 +4218,7 @@ QUnit.module('Views', { var $record1 = kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(1)'); var $record2 = kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(0)'); - testUtils.dragAndDrop($record1, $record2, {position: 'top'}); + testUtils.dom.dragAndDrop($record1, $record2, {position: 'top'}); assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record').length, 2, "column should contain 2 records"); @@ -4290,7 +4227,7 @@ QUnit.module('Views', { assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(1)').text(), "record2", "records should be correctly ordered"); - testUtils.dragAndDrop($record2, $record1, {position: 'top'}); + testUtils.dom.dragAndDrop($record2, $record1, {position: 'top'}); assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record').length, 2, "column should contain 2 records"); @@ -4356,7 +4293,7 @@ QUnit.module('Views', { groupBy: ['bar'], }); - assert.strictEqual(kanban.$('.o_kanban_counter').length, this.data.product.records.length, + assert.containsN(kanban, '.o_kanban_counter', this.data.product.records.length, "kanban counters should have been created"); assert.strictEqual(parseInt(kanban.$('.o_kanban_counter_side').last().text()), 36, @@ -4419,11 +4356,11 @@ QUnit.module('Views', { // Create a new column: this should create an empty progressbar var $columnQuickCreate = kanban.$('.o_column_quick_create'); - $columnQuickCreate.click(); + testUtils.dom.click($columnQuickCreate.find('.o_quick_create_folded')); $columnQuickCreate.find('input').val('test'); - $columnQuickCreate.find('.btn-primary').click(); + testUtils.dom.click($columnQuickCreate.find('.btn-primary')); - assert.strictEqual(kanban.$('.o_kanban_counter').length, nbProgressBars + 1, + assert.containsN(kanban, '.o_kanban_counter', nbProgressBars + 1, "a new column with a new column progressbar should have been created"); kanban.destroy(); @@ -4449,9 +4386,9 @@ QUnit.module('Views', { }); var initialCount = parseInt(kanban.$('.o_kanban_counter_side:first').text()); - kanban.$('.o_kanban_quick_add:first').click(); - kanban.$('.o_kanban_quick_create input').val('Test').trigger('input'); - kanban.$('.o_kanban_add').click(); + testUtils.dom.click(kanban.$('.o_kanban_quick_add:first')); + testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'Test'); + testUtils.dom.click(kanban.$('.o_kanban_add')); var lastCount = parseInt(kanban.$('.o_kanban_counter_side:first').text()); assert.strictEqual(lastCount, initialCount + 1, "kanban counters should have updated on quick create"); @@ -4480,8 +4417,8 @@ QUnit.module('Views', { }); // we have 1 record shown, load 2 more and check it worked - kanban.$('.o_kanban_group').find('.o_kanban_load_more').click(); - kanban.$('.o_kanban_group').find('.o_kanban_load_more').click(); + testUtils.dom.click(kanban.$('.o_kanban_group').find('.o_kanban_load_more')); + testUtils.dom.click(kanban.$('.o_kanban_group').find('.o_kanban_load_more')); var shownIDs = _.map(kanban.$('.o_kanban_record'), function(record) { return parseInt(record.innerText); }); @@ -4521,9 +4458,9 @@ QUnit.module('Views', { "the counter progressbars should be correctly displayed"); // archive all records of the second columns - kanban.$('.o_kanban_group:eq(1) .o_kanban_config a.o-no-caret').click(); - kanban.$('.o_column_archive_records:visible').click(); - $('.modal-footer button:first').click(); + testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:eq(1)')); + testUtils.dom.click(kanban.$('.o_column_archive_records:visible')); + testUtils.dom.click($('.modal-footer button:first')); assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_counter_side').text(), "0", "counter should contain the correct value"); @@ -4564,9 +4501,9 @@ QUnit.module('Views', { }); // archive all records of the first column - kanban.$('.o_kanban_group:first .o_kanban_config a.o-no-caret').click(); - kanban.$('.o_column_archive_records:visible').click(); - $('.modal-footer button:first').click(); + testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:first')); + testUtils.dom.click(kanban.$('.o_column_archive_records:visible')); + testUtils.dom.click($('.modal-footer button:first')); assert.verifySteps([ [1, 2, 3, 4], @@ -4649,15 +4586,15 @@ QUnit.module('Views', { assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_counter_side').text(), "1", "counter should contain the correct value"); - testUtils.dragAndDrop(kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(0)'), kanban.$('.o_kanban_group:eq(1)')); + testUtils.dom.dragAndDrop(kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(0)'), kanban.$('.o_kanban_group:eq(1)')); assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_counter_side').text(), "0", "counter should contain the correct value"); - testUtils.dragAndDrop(kanban.$('.o_kanban_group:eq(1) .o_kanban_record:eq(2)'), kanban.$('.o_kanban_group:eq(0)')); + testUtils.dom.dragAndDrop(kanban.$('.o_kanban_group:eq(1) .o_kanban_record:eq(2)'), kanban.$('.o_kanban_group:eq(0)')); assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_counter_side').text(), "1", "counter should contain the correct value"); - testUtils.dragAndDrop(kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(0)'), kanban.$('.o_kanban_group:eq(1)')); + testUtils.dom.dragAndDrop(kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(0)'), kanban.$('.o_kanban_group:eq(1)')); assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_counter_side').text(), "0", "counter should contain the correct value"); @@ -4683,11 +4620,12 @@ QUnit.module('Views', { groupBy: ['bar'], }); - var initialCount = parseInt(kanban.$('.o_kanban_counter_side').eq(1).text()); + var $secondGroup = kanban.$('.o_kanban_group:eq(1)'); + var initialCount = parseInt($secondGroup.find('.o_kanban_counter_side').text()); assert.strictEqual(initialCount, 3, "Initial count should be Three"); - kanban.$('.bg-success-full').click(); - var lastCount = parseInt(kanban.$('.o_kanban_counter_side').eq(1).text()); + testUtils.dom.click($secondGroup.find('.bg-success-full')); + var lastCount = parseInt($secondGroup.find('.o_kanban_counter_side').text()); assert.strictEqual(lastCount, 1, "kanban counters should vary according to what subgroup is selected"); @@ -4720,11 +4658,11 @@ QUnit.module('Views', { var initialCount = parseInt(kanban.$('.o_kanban_counter_side:first').text()); - // click on 'Create', fill the quick create and validate - kanban.$buttons.find('.o-kanban-button-new').click(); + testUtils.kanban.clickCreate(kanban); + // fill the quick create and validate var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create'); - $quickCreate.find('.o_field_widget[name=int_field]').val('44').trigger('input'); - $quickCreate.find('button.o_kanban_add').click(); + testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=int_field]'), '44'); + testUtils.dom.click($quickCreate.find('button.o_kanban_add')); var lastCount = parseInt(kanban.$('.o_kanban_counter_side:first').text()); assert.strictEqual(lastCount, initialCount + 44, @@ -4759,12 +4697,12 @@ QUnit.module('Views', { var $quickCreateGroup; var $groups; _quickCreateAndTest(); - testUtils.dragAndDrop($groups.first().find('.o_kanban_record:first'), $groups.eq(1)); + testUtils.dom.dragAndDrop($groups.first().find('.o_kanban_record:first'), $groups.eq(1)); _quickCreateAndTest(); kanban.destroy(); function _quickCreateAndTest() { - kanban.$buttons.find('.o-kanban-button-new').click(); + testUtils.kanban.clickCreate(kanban); $quickCreateGroup = kanban.$('.o_kanban_quick_create').closest('.o_kanban_group'); $groups = kanban.$('.o_kanban_group'); assert.strictEqual($quickCreateGroup[0], $groups[0], @@ -4802,7 +4740,7 @@ QUnit.module('Views', { assert.expect(1); var instanceNumber = 0; - testUtils.patch(mixins.ParentedMixin, { + testUtils.mock.patch(mixins.ParentedMixin, { init: function () { instanceNumber++; return this._super.apply(this, arguments); @@ -4850,7 +4788,7 @@ QUnit.module('Views', { kanban.destroy(); - testUtils.unpatch(mixins.ParentedMixin); + testUtils.mock.unpatch(mixins.ParentedMixin); }); QUnit.test('grouped kanban becomes ungrouped when clearing domain then clearing groupby', function (assert) { @@ -4886,18 +4824,18 @@ QUnit.module('Views', { }, }); - assert.ok(kanban.$('.o_kanban_view').hasClass('o_kanban_grouped'), + assert.hasClass(kanban.$('.o_kanban_view'),'o_kanban_grouped', "the kanban view should be grouped"); - assert.notOk(kanban.$('.o_kanban_view').hasClass('o_kanban_ungrouped'), + assert.doesNotHaveClass(kanban.$('.o_kanban_view'), 'o_kanban_ungrouped', "the kanban view should not be ungrouped"); kanban.update({domain: []}); // 1st update on kanban view kanban.update({groupBy: false}); // 2n update on kanban view def.resolve(); // simulate slow 1st update of kanban view - assert.notOk(kanban.$('.o_kanban_view').hasClass('o_kanban_grouped'), + assert.doesNotHaveClass(kanban.$('.o_kanban_view'), 'o_kanban_grouped', "the kanban view should not longer be grouped"); - assert.ok(kanban.$('.o_kanban_view').hasClass('o_kanban_ungrouped'), + assert.hasClass(kanban.$('.o_kanban_view'),'o_kanban_ungrouped', "the kanban view should have become ungrouped"); kanban.destroy(); @@ -4910,7 +4848,8 @@ QUnit.module('Views', { View: KanbanView, model: 'partner', data: this.data, - arch: '' + + // force group_create to false, otherwise the CREATE button in control panel is hidden + arch: '' + '
' + '' + '
' + @@ -4923,7 +4862,7 @@ QUnit.module('Views', { }, }, }); - kanban.$buttons.find('.o-kanban-button-new').click(); + testUtils.kanban.clickCreate(kanban); kanban.destroy(); }); @@ -5071,7 +5010,7 @@ QUnit.module('Views', { '
', }); - testUtils.intercept(kanban, 'switch_view', function (event) { + testUtils.mock.intercept(kanban, 'switch_view', function (event) { assert.deepEqual(event.data, { view_type: 'form', res_id: 1, diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index 030a676f4eb..51a4d424d10 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -105,15 +105,15 @@ QUnit.module('Views', { arch: '', }); - assert.notOk(list.$el.hasClass('o_cannot_create'), + assert.doesNotHaveClass(list.$el, 'o_cannot_create', "should not have className 'o_cannot_create'"); // 3 th (1 for checkbox, 2 for columns) - assert.strictEqual(list.$('th').length, 3, "should have 3 columns"); + assert.containsN(list, 'th', 3, "should have 3 columns"); assert.strictEqual(list.$('td:contains(gnap)').length, 1, "should contain gnap"); - assert.strictEqual(list.$('tbody tr').length, 4, "should have 4 rows"); - assert.strictEqual(list.$('th.o_column_sortable').length, 1, "should have 1 sortable column"); + assert.containsN(list, 'tbody tr', 4, "should have 4 rows"); + assert.containsOnce(list, 'th.o_column_sortable', "should have 1 sortable column"); assert.strictEqual(list.$('thead th:nth(2)').css('text-align'), 'right', "header cells of integer fields should be right aligned"); @@ -139,7 +139,7 @@ QUnit.module('Views', { arch: '', }); - assert.ok(list.$el.hasClass('o_cannot_create'), + assert.hasClass(list.$el,'o_cannot_create', "should have className 'o_cannot_create'"); assert.strictEqual(list.$buttons.find('.o_list_button_add').length, 0, "should not have the 'Create' button"); @@ -158,11 +158,11 @@ QUnit.module('Views', { arch: '', }); - assert.ok(list.sidebar.$el.hasClass('o_hidden'), 'sidebar should be invisible'); + assert.isNotVisible(list.sidebar.$el, 'sidebar should be invisible'); assert.ok(list.$('tbody td.o_list_record_selector').length, 'should have at least one record'); - list.$('tbody td.o_list_record_selector:first input').click(); - assert.ok(!list.sidebar.$el.hasClass('o_hidden'), 'sidebar should be visible'); + testUtils.dom.click(list.$('tbody td.o_list_record_selector:first input')); + assert.isVisible(list.sidebar.$el, 'sidebar should be visible'); assert.notOk(list.sidebar.$('a:contains(Delete)').length, 'sidebar should not have Delete button'); list.destroy(); @@ -178,8 +178,8 @@ QUnit.module('Views', { arch: '', }); - assert.strictEqual(list.$('th').length, 3, "should have 2 th"); - assert.strictEqual(list.$('th').length, 3, "should have 3 th"); + assert.containsN(list, 'th', 3, "should have 2 th"); + assert.containsN(list, 'th', 3, "should have 3 th"); assert.strictEqual(list.$('td:contains(yop)').length, 1, "should contain yop"); assert.ok(list.$buttons.find('.o_list_button_add').is(':visible'), @@ -189,7 +189,7 @@ QUnit.module('Views', { assert.ok(!list.$buttons.find('.o_list_button_discard').is(':visible'), "should not have a visible discard button"); - list.$('td:not(.o_list_record_selector)').first().click(); + testUtils.dom.click(list.$('td:not(.o_list_record_selector)').first()); assert.ok(!list.$buttons.find('.o_list_button_add').is(':visible'), "should not have a visible Create button"); @@ -198,7 +198,7 @@ QUnit.module('Views', { assert.ok(list.$buttons.find('.o_list_button_discard').is(':visible'), "should have a visible discard button"); - list.$buttons.find('.o_list_button_save').click(); + testUtils.dom.click(list.$buttons.find('.o_list_button_save')); assert.ok(list.$buttons.find('.o_list_button_add').is(':visible'), "should have a visible Create button"); @@ -223,7 +223,7 @@ QUnit.module('Views', { }); // 1 th for checkbox, 1 for 1 visible column - assert.strictEqual(list.$('th').length, 2, "should have 2 th"); + assert.containsN(list, 'th', 2, "should have 2 th"); list.destroy(); }); @@ -241,11 +241,11 @@ QUnit.module('Views', { '', }); - assert.strictEqual(list.$('tbody tr:first td').length, 4, + assert.containsN(list, 'tbody tr:first td', 4, "there should be 4 cells in the first row"); - assert.strictEqual(list.$('tbody td.o_invisible_modifier').length, 1, + assert.containsOnce(list, 'tbody td.o_invisible_modifier', "there should be 1 invisible bar cell"); - assert.ok(list.$('tbody tr:first td:eq(2)').hasClass('o_invisible_modifier'), + assert.hasClass(list.$('tbody tr:first td:eq(2)'),'o_invisible_modifier', "the 3rd cell should be invisible"); assert.strictEqual(list.$('tbody tr:eq(0) td:visible').length, list.$('tbody tr:eq(1) td:visible').length, "there should be the same number of visible cells in different rows"); @@ -271,7 +271,7 @@ QUnit.module('Views', { }, }); - list.$buttons.find('.o_list_button_add').click(); + testUtils.dom.click(list.$buttons.find('.o_list_button_add')); assert.verifySteps(['search_read', 'default_get'], "no nameget should be done"); list.destroy(); @@ -288,7 +288,7 @@ QUnit.module('Views', { domain: [['bar', '=', true]], }); - assert.strictEqual(list.$('tbody tr').length, 4, "should have 4 rows"); + assert.containsN(list, 'tbody tr', 4, "should have 4 rows"); list.destroy(); }); @@ -305,8 +305,8 @@ QUnit.module('Views', { assert.strictEqual(list.$('th:contains(Foo)').length, 1, "should contain Foo"); assert.strictEqual(list.$('th:contains(Bar)').length, 1, "should contain Bar"); - assert.strictEqual(list.$('tr.o_group_header').length, 2, "should have 2 .o_group_header"); - assert.strictEqual(list.$('th.o_group_name').length, 2, "should have 2 .o_group_name"); + assert.containsN(list, 'tr.o_group_header', 2, "should have 2 .o_group_header"); + assert.containsN(list, 'th.o_group_name', 2, "should have 2 .o_group_name"); list.destroy(); }); @@ -404,9 +404,9 @@ QUnit.module('Views', { groupBy: ['foo'], }); - list.$('th.o_group_name').get(1).click(); + testUtils.dom.click(list.$('th.o_group_name:nth(1)')); assert.strictEqual(list.$('tbody:eq(1) tr').length, 2, "open group should contain 2 records"); - assert.strictEqual(list.$('tbody').length, 3, "should contain 3 tbody"); + assert.containsN(list, 'tbody', 3, "should contain 3 tbody"); assert.strictEqual(list.$('td:contains(9)').length, 1, "should contain 9"); assert.strictEqual(list.$('td:contains(-4)').length, 1, "should contain -4"); assert.strictEqual(list.$('td:contains(10)').length, 1, "should contain 10"); @@ -424,16 +424,16 @@ QUnit.module('Views', { arch: '', }); - testUtils.intercept(list, "open_record", function () { + testUtils.mock.intercept(list, "open_record", function () { assert.ok("list view should trigger 'open_record' event"); }); - list.$('tr td:not(.o_list_record_selector)').first().click(); + testUtils.dom.click(list.$('tr td:not(.o_list_record_selector)').first()); list.update({groupBy: ['foo']}); - assert.strictEqual(list.$('tr.o_group_header').length, 3, "list should be grouped"); - list.$('th.o_group_name').first().click(); + assert.containsN(list, 'tr.o_group_header', 3, "list should be grouped"); + testUtils.dom.click(list.$('th.o_group_name').first()); - list.$('tr:not(.o_group_header) td:not(.o_list_record_selector)').first().click(); + testUtils.dom.click(list.$('tr:not(.o_group_header) td:not(.o_list_record_selector)').first()); list.destroy(); }); @@ -455,31 +455,33 @@ QUnit.module('Views', { var $td = list.$('td:not(.o_list_record_selector)').first(); var $second_td = list.$('td:not(.o_list_record_selector)').eq(1); var $third_td = list.$('td:not(.o_list_record_selector)').eq(2); - $td.click(); - assert.ok($td.parent().hasClass('o_selected_row'), + testUtils.dom.click($td); + assert.hasClass($td.parent(),'o_selected_row', "row should be in edit mode"); - assert.ok($td.hasClass('o_readonly_modifier'), + assert.hasClass($td,'o_readonly_modifier', "foo cell should be readonly in edit mode"); - assert.ok(!$second_td.hasClass('o_readonly_modifier'), + assert.doesNotHaveClass($second_td, 'o_readonly_modifier', "bar cell should be editable"); - assert.ok($third_td.hasClass('o_readonly_modifier'), + assert.hasClass($third_td,'o_readonly_modifier', "int_field cell should be readonly in edit mode"); list.destroy(); }); - QUnit.test('editable list view: no active element', function (assert) { + QUnit.test('editable list view: line with no active element', function (assert) { assert.expect(3); - this.data.bar= { + + this.data.bar = { fields: { titi: {string: "Char", type: "char"}, grosminet: {string: "Bool", type: "boolean"}, }, records: [ - {titi: 'cui', grosminet: true}, - {titi: 'cuicui', grosminet: false}, - ] + {id: 1, titi: 'cui', grosminet: true}, + {id: 2, titi: 'cuicui', grosminet: false}, + ], }; this.data.foo.records[0].o2m = [1, 2]; + var form = createView({ View: FormView, model: 'foo', @@ -494,20 +496,25 @@ QUnit.module('Views', { ''+ '
'+ '', + mockRPC: function (route, args) { + if (args.method === 'write') { + assert.deepEqual(args.args[1], { + o2m: [[1, 1, {grosminet: false}], [4, 2, false]], + }); + } + return this._super.apply(this, arguments); + }, }); + var $td = form.$('.o_data_cell').first(); var $td2 = form.$('.o_data_cell').eq(1); - assert.ok($td.hasClass("o_readonly_modifier"), "first field must be readonly"); - assert.ok($td2.hasClass("o_boolean_toggle_cell"), "second field must be not activable but updatable on click (boolean toggle in this case)"); - $td.click(); //select row first - var $slider = $td2.find('.slider').first(); - try { - $slider.click(); //toggle boolean - assert.ok(true); - } - catch(e) { - assert.ok(false, "should not crash when clicking on the slider"); - } + assert.hasClass($td, 'o_readonly_modifier'); + assert.hasClass($td2, 'o_boolean_toggle_cell'); + testUtils.dom.click($td); + testUtils.dom.click($td2.find('.o_boolean_toggle input')); + + testUtils.form.clickSave(form); + form.destroy(); }); @@ -522,16 +529,16 @@ QUnit.module('Views', { }); var $td = list.$('td:not(.o_list_record_selector)').first(); - assert.ok(!$td.parent().hasClass('o_selected_row'), "td should not be in edit mode"); - $td.click(); - assert.ok($td.parent().hasClass('o_selected_row'), "td should be in edit mode"); + assert.doesNotHaveClass($td.parent(), 'o_selected_row', "td should not be in edit mode"); + testUtils.dom.click($td); + assert.hasClass($td.parent(),'o_selected_row', "td should be in edit mode"); list.destroy(); }); QUnit.test('editable list: add a line and discard', function (assert) { assert.expect(11); - testUtils.patch(basicFields.FieldChar, { + testUtils.mock.patch(basicFields.FieldChar, { destroy: function () { assert.step('destroy'); this._super.apply(this, arguments); @@ -546,34 +553,34 @@ QUnit.module('Views', { domain: [['foo', '=', 'yop']], }); - assert.strictEqual(list.$('tbody tr').length, 4, + assert.containsN(list, 'tbody tr', 4, "list should contain 4 rows"); - assert.strictEqual(list.$('.o_data_row').length, 1, + assert.containsOnce(list, '.o_data_row', "list should contain one record (and thus 3 empty rows)"); assert.strictEqual(list.pager.$('.o_pager_value').text(), '1-1', "pager should be correct"); - list.$buttons.find('.o_list_button_add').click(); + testUtils.dom.click(list.$buttons.find('.o_list_button_add')); - assert.strictEqual(list.$('tbody tr').length, 4, + assert.containsN(list, 'tbody tr', 4, "list should still contain 4 rows"); - assert.strictEqual(list.$('.o_data_row').length, 2, + assert.containsN(list, '.o_data_row', 2, "list should contain two record (and thus 2 empty rows)"); assert.strictEqual(list.pager.$('.o_pager_value').text(), '1-2', "pager should be correct"); - list.$buttons.find('.o_list_button_discard').click(); + testUtils.dom.click(list.$buttons.find('.o_list_button_discard')); - assert.strictEqual(list.$('tbody tr').length, 4, + assert.containsN(list, 'tbody tr', 4, "list should still contain 4 rows"); - assert.strictEqual(list.$('.o_data_row').length, 1, + assert.containsOnce(list, '.o_data_row', "list should contain one record (and thus 3 empty rows)"); assert.strictEqual(list.pager.$('.o_pager_value').text(), '1-1', "pager should be correct"); assert.verifySteps(['destroy'], "should have destroyed the widget of the removed line"); - testUtils.unpatch(basicFields.FieldChar); + testUtils.mock.unpatch(basicFields.FieldChar); list.destroy(); }); @@ -589,13 +596,13 @@ QUnit.module('Views', { var $td = list.$('td:not(.o_list_record_selector)').first(); var n = 0; - testUtils.intercept(list, "field_changed", function () { + testUtils.mock.intercept(list, "field_changed", function () { n += 1; }); - $td.click(); - $td.find('input').val('abc').trigger('input'); + testUtils.dom.click($td); + testUtils.fields.editInput($td.find('input'), 'abc'); assert.strictEqual(n, 1, "field_changed should have been triggered"); - list.$('td:not(.o_list_record_selector)').eq(2).click(); + testUtils.dom.click(list.$('td:not(.o_list_record_selector)').eq(2)); assert.strictEqual(n, 1, "field_changed should not have been triggered"); list.destroy(); }); @@ -610,15 +617,15 @@ QUnit.module('Views', { arch: '', }); var $td = list.$('td:not(.o_list_record_selector)').first(); - $td.click(); - $td.find('input').val('abc').trigger('input'); + testUtils.dom.click($td); + testUtils.fields.editInput($td.find('input'), 'abc'); assert.strictEqual($td.find('input').val(), 'abc', "char field has been edited correctly"); var $next_row_td = list.$('tbody tr:eq(1) td:not(.o_list_record_selector)').first(); - $next_row_td.click(); // should trigger the save of the previous row + testUtils.dom.click($next_row_td); assert.strictEqual(list.$('td:not(.o_list_record_selector)').first().text(), 'abc', 'changes should be saved correctly'); - assert.ok(!list.$('tbody tr').first().hasClass('o_selected_row'), + assert.doesNotHaveClass(list.$('tbody tr').first(), 'o_selected_row', 'saved row should be in readonly mode'); assert.strictEqual(this.data.foo.records[0].foo, 'abc', "the edition should have been properly saved"); @@ -644,15 +651,15 @@ QUnit.module('Views', { } }); - list.$('.o_data_cell:first').click(); - list.$('input[name="foo"]').val('xyz').trigger('input'); - list.$('.o_column_sortable').click(); + testUtils.dom.click(list.$('.o_data_cell:first')); + testUtils.fields.editInput(list.$('input[name="foo"]'), 'xyz'); + testUtils.dom.click(list.$('.o_column_sortable')); - assert.ok(list.$('.o_data_row:first').hasClass('o_selected_row'), + assert.hasClass(list.$('.o_data_row:first'),'o_selected_row', "first row should still be in edition"); - list.$buttons.find('.o_list_button_save').click(); - assert.ok(!list.$buttons.hasClass('o-editing'), + testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + assert.doesNotHaveClass(list.$buttons, 'o-editing', "list buttons should be back to their readonly mode"); list.destroy(); @@ -671,28 +678,28 @@ QUnit.module('Views', { var $thead_selector = list.$('thead .o_list_record_selector input'); var n = 0; - testUtils.intercept(list, "selection_changed", function () { + testUtils.mock.intercept(list, "selection_changed", function () { n += 1; }); // tbody checkbox click - $tbody_selector.click(); + testUtils.dom.click($tbody_selector); assert.strictEqual(n, 1, "selection_changed should have been triggered"); assert.ok($tbody_selector.is(':checked'), "selection checkbox should be checked"); - $tbody_selector.click(); + testUtils.dom.click($tbody_selector); assert.strictEqual(n, 2, "selection_changed should have been triggered"); assert.ok(!$tbody_selector.is(':checked'), "selection checkbox shouldn't be checked"); // head checkbox click - $thead_selector.click(); + testUtils.dom.click($thead_selector); assert.strictEqual(n, 3, "selection_changed should have been triggered"); - assert.strictEqual(list.$('tbody .o_list_record_selector input:checked').length, + assert.containsN(list, 'tbody .o_list_record_selector input:checked', list.$('tbody tr').length, "all selection checkboxes should be checked"); - $thead_selector.click(); + testUtils.dom.click($thead_selector); assert.strictEqual(n, 4, "selection_changed should have been triggered"); - assert.strictEqual(list.$('tbody .o_list_record_selector input:checked').length, 0, + assert.containsNone(list, 'tbody .o_list_record_selector input:checked', "no selection checkbox should be checked"); list.destroy(); }); @@ -715,7 +722,7 @@ QUnit.module('Views', { // select first record var $firstRowSelector = list.$('tbody .o_list_record_selector input').first(); - $firstRowSelector.click(); + testUtils.dom.click($firstRowSelector); assert.ok($firstRowSelector.is(':checked'), "first row should be selected"); assert.strictEqual(list.$('tfoot td:nth(2)').text(), '10', "total should be 10 (first record selected)"); @@ -732,7 +739,7 @@ QUnit.module('Views', { }); QUnit.test('selection is kept on render without reload', function (assert) { - assert.expect(5); + assert.expect(6); var list = createView({ View: ListView, @@ -746,26 +753,25 @@ QUnit.module('Views', { '', }); + assert.isNotVisible(list.sidebar); + // open blip grouping and check all lines - list.$('.o_group_header:contains("blip (2)")').click(); - list.$('.o_data_row input').click(); - assert.strictEqual(true, list.sidebar.$el.is(':visible'), - "element checked so sidebar") + testUtils.dom.click(list.$('.o_group_header:contains("blip (2)")')); + testUtils.dom.click(list.$('.o_data_row:first input')); + assert.isVisible(list.sidebar); // open yop grouping and verify blip are still checked - list.$('.o_group_header:contains("yop (1)")').click() - assert.strictEqual(2, list.$('.o_data_row input:checked').length, + testUtils.dom.click(list.$('.o_group_header:contains("yop (1)")')); + assert.containsOnce(list, '.o_data_row input:checked', "opening a grouping does not uncheck others"); - assert.strictEqual(true, list.sidebar.$el.is(':visible'), - "element checked so sidebar") + assert.isVisible(list.sidebar); // close and open blip grouping and verify blip are unchecked - list.$('.o_group_header:contains("blip (2)")').click(); - list.$('.o_group_header:contains("blip (2)")').click(); - assert.strictEqual(0, list.$('.o_data_row input:checked').length, + testUtils.dom.click(list.$('.o_group_header:contains("blip (2)")')); + testUtils.dom.click(list.$('.o_group_header:contains("blip (2)")')); + assert.containsNone(list, '.o_data_row input:checked', "opening and closing a grouping uncheck its elements"); - assert.strictEqual(false, list.sidebar.$el.is(':visible'), - "no element checked so no sidebar") + assert.isNotVisible(list.sidebar); list.destroy(); }); @@ -784,12 +790,12 @@ QUnit.module('Views', { assert.strictEqual(list.$('tfoot td:nth(2)').text(), "32", "total should be 32"); - $tbody_selectors.first().click(); - $tbody_selectors.last().click(); + testUtils.dom.click($tbody_selectors.first()); + testUtils.dom.click($tbody_selectors.last()); assert.strictEqual(list.$('tfoot td:nth(2)').text(), "6", "total should be 6 as first and last records are selected"); - $thead_selector.click(); + testUtils.dom.click($thead_selector); assert.strictEqual(list.$('tfoot td:nth(2)').text(), "32", "total should be 32 as all records are selected"); @@ -821,8 +827,8 @@ QUnit.module('Views', { assert.strictEqual($groupHeader2.find('td:last()').text(), "9", "second group total should be 9"); assert.strictEqual(list.$('tfoot td:last()').text(), "32", "total should be 32"); - $groupHeader1.click(); - list.$('tbody .o_list_record_selector input').first().click(); + testUtils.dom.click($groupHeader1); + testUtils.dom.click(list.$('tbody .o_list_record_selector input').first()); assert.strictEqual(list.$('tfoot td:last()').text(), "10", "total should be 10 as first record of first group is selected"); list.destroy(); @@ -840,8 +846,8 @@ QUnit.module('Views', { assert.strictEqual(list.$('td[title="Sum"]').text(), "32", "current total should be 32"); - list.$('tr.o_data_row td.o_data_cell').first().click(); - list.$('td.o_data_cell input').val("15").trigger("input"); + testUtils.dom.click(list.$('tr.o_data_row td.o_data_cell').first()); + testUtils.fields.editInput(list.$('td.o_data_cell input'), "15"); assert.strictEqual(list.$('td[title="Sum"]').text(), "37", "current total should now be 37"); @@ -887,12 +893,12 @@ QUnit.module('Views', { "initial order should be 10, 5, 17"); assert.strictEqual(list.$('tfoot td:last()').text(), '32', "total should be 32"); - list.$('.o_column_sortable').click(); // sort (int_field ASC) + testUtils.dom.click(list.$('.o_column_sortable')); assert.strictEqual(list.$('tfoot td:last()').text(), '32', "total should still be 32"); assert.strictEqual(list.$('tbody .o_list_number').text(), '51017', "order should be 5, 10, 17"); - list.$('.o_column_sortable').click(); // sort (int_field DESC) + testUtils.dom.click(list.$('.o_column_sortable')); assert.strictEqual(list.$('tbody .o_list_number').text(), '17105', "initial order should be 17, 10, 5"); assert.strictEqual(list.$('tfoot td:last()').text(), '32', "total should still be 32"); @@ -923,13 +929,13 @@ QUnit.module('Views', { }, }); //we cannot sort by sort_field since it doesn't have a group_operator - list.$('.o_column_sortable:eq(2)').click(); // sort (sort_field ASC(invalid) -> default order) + testUtils.dom.click(list.$('.o_column_sortable:eq(2)')); //we can sort by int_field since it has a group_operator - list.$('.o_column_sortable:eq(1)').click(); // sort (int_field ASC) + testUtils.dom.click(list.$('.o_column_sortable:eq(1)')); //we keep previous order - list.$('.o_column_sortable:eq(2)').click(); // sort (sort_field ASC(invalid) -> int_field ASC) + testUtils.dom.click(list.$('.o_column_sortable:eq(2)')); //we can sort on foo since we are groupped by foo + previous order - list.$('.o_column_sortable:eq(0)').click(); // sort (foo ASC, int_field ASK") + testUtils.dom.click(list.$('.o_column_sortable:eq(0)')); assert.verifySteps([ 'default order', @@ -963,8 +969,8 @@ QUnit.module('Views', { assert.strictEqual($int_field_td.text(), '10', "should contain initial value"); - $foo_td.click(); - $foo_td.find('input').val('tralala').trigger('input'); + testUtils.dom.click($foo_td); + testUtils.fields.editInput($foo_td.find('input'), 'tralala'); assert.strictEqual($int_field_td.find('input').val(), "1007", "should contain input with onchange applied"); @@ -991,13 +997,13 @@ QUnit.module('Views', { var startWidth = list.$('table').addBack('table').width(); // start edition of first row - list.$('td:not(.o_list_record_selector)').first().click(); + testUtils.dom.click(list.$('td:not(.o_list_record_selector)').first()); var editionWidths = _.pluck(list.$('thead th'), 'offsetWidth'); var editionWidth = list.$('table').addBack('table').width(); // leave edition - list.$buttons.find('.o_list_button_save').click(); + testUtils.dom.click(list.$buttons.find('.o_list_button_save')); var readonlyWidths = _.pluck(list.$('thead th'), 'offsetWidth'); var readonlyWidth = list.$('table').addBack('table').width(); @@ -1025,19 +1031,20 @@ QUnit.module('Views', { arch: '', }); - assert.ok(list.sidebar.$el.hasClass('o_hidden'), 'sidebar should be invisible'); - assert.strictEqual(list.$('tbody td.o_list_record_selector').length, 4, "should have 4 records"); + assert.isNotVisible(list.sidebar.$el, 'sidebar should be invisible'); + assert.containsN(list, 'tbody td.o_list_record_selector', 4, "should have 4 records"); - list.$('tbody td.o_list_record_selector:first input').click(); + testUtils.dom.click(list.$('tbody td.o_list_record_selector:first input')); - assert.ok(!list.sidebar.$el.hasClass('o_hidden'), 'sidebar should be visible'); + assert.isVisible(list.sidebar.$el, 'sidebar should be visible'); - list.sidebar.$('a:contains(Delete)').click(); - assert.ok($('body').hasClass('modal-open'), 'body should have modal-open clsss'); + testUtils.dom.click(list.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + testUtils.dom.click(list.sidebar.$('a:contains(Delete)')); + assert.hasClass($('body'),'modal-open', 'body should have modal-open clsss'); - $('body .modal button span:contains(Ok)').click(); + testUtils.dom.click($('body .modal button span:contains(Ok)')); - assert.strictEqual(list.$('tbody td.o_list_record_selector').length, 3, "should have 3 records"); + assert.containsN(list, 'tbody td.o_list_record_selector', 3, "should have 3 records"); list.destroy(); }); @@ -1059,23 +1066,25 @@ QUnit.module('Views', { }, }); - assert.ok(list.sidebar.$el.hasClass('o_hidden'), 'sidebar should be invisible'); - assert.strictEqual(list.$('tbody td.o_list_record_selector').length, 4, "should have 4 records"); + assert.isNotVisible(list.sidebar.$el, 'sidebar should be invisible'); + assert.containsN(list, 'tbody td.o_list_record_selector', 4, "should have 4 records"); - list.$('tbody td.o_list_record_selector:first input').click(); + testUtils.dom.click(list.$('tbody td.o_list_record_selector:first input')); - assert.ok(!list.sidebar.$el.hasClass('o_hidden'), 'sidebar should be visible'); + assert.isVisible(list.sidebar.$el, 'sidebar should be visible'); assert.verifySteps(['/web/dataset/search_read']); - list.sidebar.$('a:contains(Archive)').click(); + testUtils.dom.click(list.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + testUtils.dom.click(list.sidebar.$('a:contains(Archive)')); assert.strictEqual($('.modal').length, 1, 'a confirm modal should be displayed'); - $('.modal-footer .btn-secondary').click(); // Click on 'Cancel' - assert.strictEqual(list.$('tbody td.o_list_record_selector').length, 4, "still should have 4 records"); + testUtils.dom.click($('.modal-footer .btn-secondary')); + assert.containsN(list, 'tbody td.o_list_record_selector', 4, "still should have 4 records"); - list.sidebar.$('a:contains(Archive)').click(); + testUtils.dom.click(list.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + testUtils.dom.click(list.sidebar.$('a:contains(Archive)')); assert.strictEqual($('.modal').length, 1, 'a confirm modal should be displayed'); - $('.modal-footer .btn-primary').click(); // Click on 'Ok' - assert.strictEqual(list.$('tbody td.o_list_record_selector').length, 3, "should have 3 records"); + testUtils.dom.click($('.modal-footer .btn-primary')); + assert.containsN(list, 'tbody td.o_list_record_selector', 3, "should have 3 records"); assert.verifySteps(['/web/dataset/search_read', '/web/dataset/call_kw/foo/write', '/web/dataset/search_read']); list.destroy(); }); @@ -1096,10 +1105,10 @@ QUnit.module('Views', { }, }); - assert.ok(!list.pager.$el.hasClass('o_hidden'), "pager should be visible"); + assert.isVisible(list.pager.$el, "pager should be visible"); assert.strictEqual(list.pager.state.size, 4, "pager's size should be 4"); list.update({ groupBy: ['bar']}); - assert.ok(list.pager.$el.hasClass('o_hidden'), "pager should be invisible"); + assert.isNotVisible(list.pager.$el, "pager should be invisible"); list.destroy(); }); @@ -1129,7 +1138,7 @@ QUnit.module('Views', { "record 3 should be first"); nbSearchRead = 0; - list.$('thead th:contains(Foo)').click(); + testUtils.dom.click(list.$('thead th:contains(Foo)')); assert.strictEqual(nbSearchRead, 1, "should have done one search_read"); assert.ok(list.$('tbody tr:first td:contains(blip)').length, "record 3 should be first"); @@ -1137,7 +1146,7 @@ QUnit.module('Views', { "record 1 should be first"); nbSearchRead = 0; - list.$('thead th:contains(Foo)').click(); + testUtils.dom.click(list.$('thead th:contains(Foo)')); assert.strictEqual(nbSearchRead, 1, "should have done one search_read"); assert.ok(list.$('tbody tr:first td:contains(yop)').length, "record 3 should be first"); @@ -1220,15 +1229,15 @@ QUnit.module('Views', { res_id: 1, }); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); assert.ok(form.$('tbody tr:first td:contains(Value 1)').length, "Value 1 should be first"); assert.ok(form.$('tbody tr:eq(1) td:contains(Value 3)').length, "Value 3 should be second"); var $o2m = form.$('.o_field_widget[name=o2m]'); - form.$('.o_field_x2many_list_row_add a').click(); - $o2m.find('.o_field_widget').val("Value 2").trigger('input'); + testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + testUtils.fields.editInput($o2m.find('.o_field_widget'), "Value 2"); assert.ok(form.$('tbody tr:first td:contains(Value 1)').length, "Value 1 should be first"); assert.ok(form.$('tbody tr:eq(1) td:contains(Value 3)').length, @@ -1236,7 +1245,7 @@ QUnit.module('Views', { assert.ok(form.$('tbody tr:eq(2) td input').val(), "Value 2 should be third (shouldn't be sorted)"); - form.$buttons.find('.o_form_button_save').click(); + testUtils.form.clickSave(form); assert.ok(form.$('tbody tr:first td:contains(Value 1)').length, "Value 1 should be first"); assert.ok(form.$('tbody tr:eq(1) td:contains(Value 2)').length, @@ -1271,15 +1280,15 @@ QUnit.module('Views', { res_id: 1, }); - form.$buttons.find('.o_form_button_edit').click(); + testUtils.form.clickEdit(form); assert.ok(form.$('tbody tr:first td:contains(Value 1)').length, "Value 1 should be first"); assert.ok(form.$('tbody tr:eq(1) td:contains(Value 3)').length, "Value 3 should be second"); var $o2m = form.$('.o_field_widget[name=o2m]'); - form.$('.o_field_x2many_list_row_add a').click(); - $o2m.find('.o_field_widget').val("Value 2").trigger('input'); + testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + testUtils.fields.editInput($o2m.find('.o_field_widget'), "Value 2"); assert.ok(form.$('tbody tr:first td:contains(Value 1)').length, "Value 1 should be first"); assert.ok(form.$('tbody tr:eq(1) td:contains(Value 3)').length, @@ -1287,9 +1296,9 @@ QUnit.module('Views', { assert.ok(form.$('tbody tr:eq(2) td input').val(), "Value 2 should be third (shouldn't be sorted)"); - form.$('.o_form_sheet_bg').click(); // validate the row before sorting + testUtils.dom.click(form.$('.o_form_sheet_bg')); - $o2m.find('.o_column_sortable').click(); // resort list after edition + testUtils.dom.click($o2m.find('.o_column_sortable')); assert.strictEqual(form.$('tbody tr:first').text(), 'Value 1', "Value 1 should be first"); assert.strictEqual(form.$('tbody tr:eq(1)').text(), 'Value 2', @@ -1297,7 +1306,7 @@ QUnit.module('Views', { assert.strictEqual(form.$('tbody tr:eq(2)').text(), 'Value 3', "Value 3 should be third"); - $o2m.find('.o_column_sortable').click(); + testUtils.dom.click($o2m.find('.o_column_sortable')); assert.strictEqual(form.$('tbody tr:first').text(), 'Value 3', "Value 3 should be first"); assert.strictEqual(form.$('tbody tr:eq(1)').text(), 'Value 2', @@ -1341,13 +1350,13 @@ QUnit.module('Views', { }); // Change page - form.$('.o_pager_next').click(); + testUtils.dom.click(form.$('.o_pager_next')); assert.strictEqual(form.$('tbody tr:first').text(), 'Value 44', "record 44 should be first"); assert.strictEqual(form.$('tbody tr:eq(4)').text(), 'Value 48', "record 48 should be last"); - form.$('.o_column_sortable').click(); + testUtils.dom.click(form.$('.o_column_sortable')); assert.strictEqual(form.$('tbody tr:first').text(), 'Value 08', "record 48 should be first"); assert.strictEqual(form.$('tbody tr:eq(4)').text(), 'Value 04', @@ -1368,8 +1377,8 @@ QUnit.module('Views', { '