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 -------------------- 6 files changed, 12361 insertions(+), 12361 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 (limited to 'addons/web/static/tests') 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', { '