',
+ '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: '
',
+ 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: '
',
+ 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: '
',
+ },
+ 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: '
',
+ },
+ 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: '
',
+ 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) {
+ 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, { 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: '
',
+ '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: '
'},
+ 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,
+ });
+
+ // 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:'
',
+ 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,
+ });
+
+ 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: '
',
+ 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.$('.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: '
',
+ 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(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: '
',
+ 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,
+ });
+
+ 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: 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: 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 (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: '
',
+ 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: '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ // field without Widget in the list
+ '' +
+ '' +
+ '
' +
+ '' +
+ // field with Widget requiring specialData in the form
+ '' +
+ '
' +
+ '' +
+ '' +
+ '
',
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ mockRPC: function (route) {
+ if (route === '/web/dataset/call_kw/partner/search_count') {
+ nbFetchSpecialDomain++;
+ }
+ return this._super.apply(this, arguments);
+ }
+ });
+
+ form.$('.o_field_one2many .o_field_x2many_list_row_add a').click();
+ assert.strictEqual($('.modal').length, 1, "form view dialog should be opened");
+ $('.modal-body input[name="model_name"]').val('partner').trigger('input');
+ $('.modal-footer button:first').click();
+
+ assert.strictEqual(form.$('.o_field_one2many tbody tr:first').text(), "coucouhas changed[]",
+ "the onchange should create one new record and remove the existing");
+
+ form.$('.o_field_one2many .o_list_view tbody tr:eq(0) td:first').click();
+
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(nbFetchSpecialDomain, 1,
+ "should only fetch special domain once");
+ form.destroy();
+ });
+
+ QUnit.test('one2many without inline tree arch', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.records[0].turtles = [2, 3];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' + // check if the view don not call load view (widget without useSubview)
+ '' +
+ '' + // check if the view don not call load view in invisible
+ '' +
+ '
',
+ res_id: 1,
+ archs: {
+ "turtle,false,list": '',
+ }
+ });
+
+ assert.strictEqual(form.$('.o_field_widget[name="turtles"] .o_list_view').length, 1,
+ 'should display one2many list view in the modal');
+
+ assert.strictEqual(form.$('.o_data_row').length, 2,
+ 'should display the 2 turtles');
+
+ form.destroy();
+ });
+
+ QUnit.test('many2one and many2many in one2many', function (assert) {
+ assert.expect(11);
+
+ this.data.turtle.records[1].product_id = 37;
+ this.data.partner.records[0].turtles = [2, 3];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
',
+ });
+
+ // add new line (first, xpad)
+ form.$('.o_field_x2many_list_row_add a').click();
+ form.$('input[name="display_name"]').val('first').trigger('input');
+ form.$('div[name="product_id"] input').click();
+ // the onchange won't be generated
+ $('li.ui-menu-item a:contains(xpad)').trigger('mouseenter').click();
+
+ assert.strictEqual(form.$('.o_field_many2manytags.o_input').length, 1,
+ 'should display the line in editable mode');
+ assert.strictEqual(form.$('.o_field_many2one input').val(), "xpad",
+ 'should display the product xpad');
+ assert.strictEqual(form.$('.o_field_many2manytags.o_input .o_badge_text').text(), "first record",
+ 'should display the tag from the onchange');
+
+ form.$('input.o_field_integer[name="int_field"]').click();
+
+ assert.strictEqual(form.$('.o_data_cell.o_required_modifier').text(), "xpad",
+ 'should display the product xpad');
+ assert.strictEqual(form.$('.o_field_many2manytags:not(.o_input) .o_badge_text').text(), "first record",
+ 'should display the tag in readonly');
+
+ // enable the many2many onchange and generate it
+ form.$('input.o_field_integer[name="int_field"]').val('10').trigger('input');
+
+ assert.strictEqual(form.$('.o_data_cell.o_required_modifier').text(), "xenomorphexphone",
+ 'should display the product xphone and xenomorphe');
+ assert.strictEqual(form.$('.o_data_row').text().replace(/\s+/g, ' '), "firstxenomorphe second record new linexphone first record ",
+ 'should display the name, one2many and many2many value');
+
+ // disable the many2many onchange
+ form.$('input.o_field_integer[name="int_field"]').val('0').trigger('input');
+
+ // remove and start over
+ form.$('.o_list_record_remove:first button').click();
+ form.$('.o_list_record_remove:first button').click();
+
+ // enable the many2many onchange
+ form.$('input.o_field_integer[name="int_field"]').val('10').trigger('input');
+
+ // add new line (first, xenomorphe)
+ form.$('.o_field_x2many_list_row_add a').click();
+ form.$('input[name="display_name"]').val('first').trigger('input');
+ form.$('div[name="product_id"] input').click();
+ // generate the onchange
+ $('li.ui-menu-item a:contains(xenomorphe)').trigger('mouseenter').click();
+
+ assert.strictEqual(form.$('.o_field_many2manytags.o_input').length, 1,
+ 'should display the line in editable mode');
+ assert.strictEqual(form.$('.o_field_many2one input').val(), "xenomorphe",
+ 'should display the product xenomorphe');
+ assert.strictEqual(form.$('.o_field_many2manytags.o_input .o_badge_text').text(), "second record",
+ 'should display the tag from the onchange');
+
+ // put list in readonly mode
+ form.$('input.o_field_integer[name="int_field"]').click();
+
+ assert.strictEqual(form.$('.o_data_cell.o_required_modifier').text(), "xenomorphexphone",
+ 'should display the product xphone and xenomorphe');
+ assert.strictEqual(form.$('.o_field_many2manytags:not(.o_input) .o_badge_text').text(), "second recordfirst record",
+ 'should display the tag in readonly (first record and second record)');
+
+ form.$('input.o_field_integer[name="int_field"]').val('10').trigger('input');
+
+ assert.strictEqual(form.$('.o_data_row').text().replace(/\s+/g, ' '), "firstxenomorphe second record new linexphone first record ",
+ 'should display the name, one2many and many2many value');
+
+ form.$buttons.find('.o_form_button_save').click();
+
+ assert.strictEqual(form.$('.o_data_row').text().replace(/\s+/g, ' '), "firstxenomorphe second record new linexphone first record ",
+ 'should display the name, one2many and many2many value after save');
+
+ form.destroy();
+ });
+
+ QUnit.test('load view for x2many in one2many', function (assert) {
+ assert.expect(2);
+
+ this.data.turtle.records[1].product_id = 37;
+ this.data.partner.records[0].turtles = [2, 3];
+ this.data.partner.records[2].turtles = [1, 3];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
',
+ res_id: 1,
+ archs: {
+ "partner,false,list": '',
+ },
+ });
+
+ assert.strictEqual(form.$('.o_data_row').length, 2,
+ 'should display the 2 turtles');
+
+ form.$('.o_data_row:first').click();
+
+ assert.strictEqual($('.modal .o_field_widget[name="partner_ids"] .o_list_view').length, 1,
+ 'should display many2many list view in the modal');
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many (who contains a one2many) with tree view and without form view', function (assert) {
+ assert.expect(1);
+
+ // avoid error in _postprocess
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
',
+ },
+ });
+
+ form.$('.o_data_row:first').click();
+
+ assert.strictEqual($('.modal .o_field_widget[name="turtle_foo"]').text(), 'blip',
+ 'should open the modal and display the form field');
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many with x2many in form view (but not in list view)', function (assert) {
+ assert.expect(1);
+
+ // avoid error when saving the edited related record (because the
+ // related x2m field is unknown in the inline list view)
+ // also ensure that the changes are correctly saved
+
+ this.data.turtle.fields.o2m = { string: "o2m", type: "one2many", relation: 'user' };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
',
+ },
+ res_id: 4,
+ });
+
+ assert.strictEqual(form.$('.o_field_widget .o_kanban_view').length, 1,
+ "should have one inner kanban view for the one2many field");
+ assert.strictEqual(form.$('.o_field_widget .o_kanban_view .o_kanban_record:not(.o_kanban_ghost)').length, 0,
+ "should not have kanban records yet");
+
+ // // switch to edit mode and create a new kanban record
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_field_widget .o-kanban-button-new').click();
+
+ // save & close the modal
+ assert.strictEqual($('.modal-content input.o_field_widget').val(), 'My little Foo Value',
+ "should already have the default value for field foo");
+ $('.modal-content .btn-primary').first().click();
+
+ assert.strictEqual(form.$('.o_field_widget .o_kanban_view').length, 1,
+ "should have one inner kanban view for the one2many field");
+ assert.strictEqual(form.$('.o_field_widget .o_kanban_view .o_kanban_record:not(.o_kanban_ghost)').length, 1,
+ "should now have one kanban record");
+ assert.strictEqual(form.$('.o_field_widget .o_kanban_view .o_kanban_record:not(.o_kanban_ghost) .o_test_id').text(),
+ '', "should not have a value for the id field");
+ assert.strictEqual(form.$('.o_field_widget .o_kanban_view .o_kanban_record:not(.o_kanban_ghost) .o_test_foo').text(),
+ 'My little Foo Value', "should have a value for the foo field");
+
+ // save the view to force a create of the new record in the one2many
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('.o_field_widget .o_kanban_view').length, 1,
+ "should have one inner kanban view for the one2many field");
+ assert.strictEqual(form.$('.o_field_widget .o_kanban_view .o_kanban_record:not(.o_kanban_ghost)').length, 1,
+ "should now have one kanban record");
+ assert.notEqual(form.$('.o_field_widget .o_kanban_view .o_kanban_record:not(.o_kanban_ghost) .o_test_id').text(),
+ '', "should now have a value for the id field");
+ assert.strictEqual(form.$('.o_field_widget .o_kanban_view .o_kanban_record:not(.o_kanban_ghost) .o_test_foo').text(),
+ 'My little Foo Value', "should still have a value for the foo field");
+
+ form.destroy();
+ });
+
+ QUnit.test('focusing fields in one2many list', function (assert) {
+ assert.expect(2);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
',
+ mockRPC: function (route, args) {
+ assert.step(args.method);
+ return this._super.apply(this, arguments);
+ },
+ res_id: 1,
+ });
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "0",
+ "int_field should start with value 0");
+ form.$('.o_field_x2many_list_row_add a').click();
+ assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "0",
+ "int_field should still be 0 (no onchange should have been done yet");
+
+ assert.verifySteps(['read', 'default_get'], "no onchange should have been applied");
+
+ form.$('.o_field_widget[name="turtle_foo"]').val("some text").trigger('input');
+ assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "1",
+ "int_field should now be 1 (the onchange should have been done");
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many list editable: trigger onchange when row is valid', function (assert) {
+ // should omit require fields that aren't in the view as they (obviously)
+ // have no value, when checking the validity of required fields
+ // shouldn't consider numerical fields with value 0 as unset
+ assert.expect(12);
+
+ this.data.turtle.fields.turtle_foo.required = true;
+ this.data.turtle.fields.turtle_qux.required = true; // required field not in the view
+ this.data.turtle.fields.turtle_bar.required = true; // required boolean field with no default
+ delete this.data.turtle.fields.turtle_bar.default;
+ this.data.turtle.fields.turtle_int.required = true; // required int field (default 0)
+ this.data.turtle.fields.turtle_int.default = 0;
+ this.data.turtle.fields.partner_ids.required = true; // required many2many
+ this.data.partner.onchanges = {
+ turtles: function (obj) {
+ obj.int_field = obj.turtles.length;
+ },
+ };
+ this.data.partner.records[0].int_field = 0;
+ this.data.partner.records[0].turtles = [];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' +
+ '
',
+ mockRPC: function (route, args) {
+ assert.step(args.method);
+ return this._super.apply(this, arguments);
+ },
+ archs: {
+ 'turtle,false,list': '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '',
+ },
+ res_id: 1,
+ });
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "0",
+ "int_field should start with value 0");
+
+ // add a new row (which is invalid at first)
+ form.$('.o_field_x2many_list_row_add a').click();
+ assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "0",
+ "int_field should still be 0 (no onchange should have been done yet)");
+ assert.verifySteps(['load_views', 'read', 'default_get'], "no onchange should have been applied");
+
+ // fill turtle_foo field
+ form.$('.o_field_widget[name="turtle_foo"]').val("some text").trigger('input');
+ assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "0",
+ "int_field should still be 0 (no onchange should have been done yet)");
+ assert.verifySteps(['load_views', 'read', 'default_get'], "no onchange should have been applied");
+
+ // fill partner_ids field with a tag (all required fields will then be set)
+ var $m2mInput = form.$('.o_field_widget[name=partner_ids] input');
+ $m2mInput.click();
+ $m2mInput.autocomplete('widget').find('li:first()').click();
+ assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "1",
+ "int_field should now be 1 (the onchange should have been done");
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many list editable: \'required\' modifiers is properly working', function (assert) {
+ assert.expect(3);
+
+ this.data.partner.onchanges = {
+ turtles: function (obj) {
+ obj.int_field = obj.turtles.length;
+ },
+ };
+
+ this.data.partner.records[0].turtles = [];
+
+ 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_widget[name="int_field"]').val(), "10",
+ "int_field should start with value 10");
+
+ form.$('.o_field_x2many_list_row_add a').click();
+
+ assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "10",
+ "int_field should still be 10 (no onchange, because line is not valid)");
+
+ // fill turtle_foo field
+ form.$('.o_field_widget[name="turtle_foo"]').val("some text").trigger('input');
+
+ assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "1",
+ "int_field should be 1 (onchange triggered, because line is now valid)");
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many list editable: \'required\' modifiers is properly working, part 2', function (assert) {
+ assert.expect(3);
+
+ this.data.partner.onchanges = {
+ turtles: function (obj) {
+ obj.int_field = obj.turtles.length;
+ },
+ };
+
+ this.data.partner.records[0].turtles = [];
+
+ 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_widget[name="int_field"]').val(), "10",
+ "int_field should start with value 10");
+
+ form.$('.o_field_x2many_list_row_add a').click();
+
+ assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "10",
+ "int_field should still be 10 (no onchange, because line is not valid)");
+
+ // fill turtle_int field
+ form.$('.o_field_widget[name="turtle_int"]').val("1").trigger('input');
+
+ assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "1",
+ "int_field should be 1 (onchange triggered, because line is now valid)");
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many list editable: add new line before onchange returns', function (assert) {
+ // If the user adds a new row (with a required field with onchange), selects
+ // a value for that field, then adds another row before the onchange returns,
+ // the editable list must wait for the onchange to return before trying to
+ // unselect the first row, otherwise it will be detected as invalid.
+ assert.expect(7);
+
+ this.data.turtle.onchanges = {
+ turtle_trululu: function () { },
+ };
+
+ var def;
+ 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;
+ },
+ });
+
+ // add a first line but hold the onchange back
+ form.$('.o_field_x2many_list_row_add a').click();
+ assert.strictEqual(form.$('.o_data_row').length, 1,
+ "should have created the first row immediately");
+ def = $.Deferred();
+ form.$('.o_field_many2one input').click();
+ form.$('.o_field_many2one input').autocomplete('widget').find('a').first().click();
+
+ // try to add a second line and check that it is correctly waiting
+ // for the onchange to return
+ form.$('.o_field_x2many_list_row_add a').click();
+ assert.strictEqual($('.modal').length, 0, "no modal should be displayed");
+ assert.strictEqual($('.o_field_invalid').length, 0,
+ "no field should be marked as invalid");
+ assert.strictEqual(form.$('.o_data_row').length, 1,
+ "should wait for the onchange to create the second row");
+ assert.ok(form.$('.o_data_row').hasClass('o_selected_row'),
+ "first row should still be in edition");
+
+ // resolve the onchange def
+ def.resolve();
+ assert.strictEqual(form.$('.o_data_row').length, 2,
+ "second row should now have been created");
+ assert.notOk(form.$('.o_data_row:first').hasClass('o_selected_row'),
+ "first row should no more be in edition");
+
+ form.destroy();
+ });
+
+ QUnit.test('editable list: multiple clicks on Add an item do not create invalid rows', function (assert) {
+ assert.expect(3);
+
+ this.data.turtle.onchanges = {
+ turtle_trululu: function () { },
+ };
+
+ var def;
+ 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;
+ },
+ });
+
+ // click twice to add a new line
+ def = $.Deferred();
+ form.$('.o_field_x2many_list_row_add a').click();
+ form.$('.o_field_x2many_list_row_add a').click();
+ assert.strictEqual(form.$('.o_data_row').length, 0,
+ "no row should have been created yet (waiting for the onchange)");
+
+ // resolve the onchange def
+ def.resolve();
+ assert.strictEqual(form.$('.o_data_row').length, 1,
+ "only one row should have been created");
+ assert.ok(form.$('.o_data_row:first').hasClass('o_selected_row'),
+ "the created row should be in edition");
+
+ form.destroy();
+ });
+
+ QUnit.test('editable list: value reset by an onchange', function (assert) {
+ // this test reproduces a subtle behavior that may occur in a form view:
+ // the user adds a record in a one2many field, and directly clicks on a
+ // datetime field of the form view which has an onchange, which totally
+ // overrides the value of the one2many (commands 5 and 0). The handler
+ // that switches the edited row to readonly is then called after the
+ // new value of the one2many field is applied (the one returned by the
+ // onchange), so the row that must go to readonly doesn't exist anymore.
+ assert.expect(2);
+
+ this.data.partner.onchanges = {
+ datetime: function (obj) {
+ obj.turtles = [[5], [0, 0, { display_name: 'new' }]];
+ },
+ };
+
+ var def;
+ 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;
+ },
+ });
+
+ // trigger the two onchanges
+ form.$('.o_field_x2many_list_row_add a').click();
+ form.$('.o_data_row .o_field_widget').val('a name').trigger('input');
+ def = $.Deferred();
+ form.$('.o_datepicker_input').click(); // focusout o2m and set value to today
+ var dateTimeVal = fieldUtils.format.datetime(moment(), { timezone: false });
+ form.$('.o_datepicker_input').val(dateTimeVal).trigger('change');
+
+ // resolve the onchange def
+ def.resolve();
+
+ assert.strictEqual(form.$('.o_data_row').length, 1,
+ "should have one record in the o2m");
+ assert.strictEqual(form.$('.o_data_row .o_data_cell').text(), 'new',
+ "should be the record created by the onchange");
+
+ form.destroy();
+ });
+
+ QUnit.test('editable list: onchange that returns a warning', function (assert) {
+ assert.expect(5);
+
+ this.data.turtle.onchanges = {
+ display_name: function () { },
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (args.method === 'onchange') {
+ assert.step(args.method);
+ return $.when({
+ value: {},
+ warning: {
+ title: "Warning",
+ message: "You must first select a partner"
+ },
+ });
+ }
+ return this._super.apply(this, arguments);
+ },
+ viewOptions: {
+ mode: 'edit',
+ },
+ intercepts: {
+ warning: function () {
+ assert.step('warning');
+ },
+ },
+ });
+
+ // add a line (this should trigger an onchange and a warning)
+ form.$('.o_field_x2many_list_row_add a').click();
+
+ // check if 'Add an item' still works (this should trigger an onchange
+ // and a warning again)
+ form.$('.o_field_x2many_list_row_add a').click();
+
+ assert.verifySteps(['onchange', 'warning', 'onchange', 'warning']);
+
+ form.destroy();
+ });
+
+ QUnit.test('editable list: contexts are correctly sent', function (assert) {
+ assert.expect(5);
+
+ this.data.partner.records[0].timmy = [12];
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
',
+ viewOptions: {
+ mode: 'edit',
+ },
+ res_id: 1,
+ mockRPC: function (route, args) {
+ assert.step(args.method);
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ form.$('input[name="foo"]').val('abcd').trigger('input');
+ assert.verifySteps(['read', 'onchange']);
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many: onchange that returns unknow field in list, but not in form', function (assert) {
+ assert.expect(5);
+
+ this.data.partner.onchanges = {
+ name: function () { },
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ '' +
+ '' +
+ '
' +
+ '' +
+ '
',
+ mockRPC: function (route, args) {
+ if (args.method === 'onchange') {
+ return $.when({
+ value: {
+ p: [[5], [0, 0, { display_name: 'new', timmy: [[5], [4, 12]] }]],
+ },
+ });
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ assert.strictEqual(form.$('.o_data_row').length, 1,
+ "the one2many should contain one row");
+ assert.strictEqual(form.$('.o_field_widget[name="timmy"]').length, 0,
+ "timmy should not be displayed in the list view");
+
+ form.$('.o_data_row td:first').click(); // open the record
+
+ assert.strictEqual($('.modal .o_field_many2manytags[name="timmy"]').length, 1,
+ "timmy should be displayed in the form view");
+ assert.strictEqual($('.modal .o_field_many2manytags[name="timmy"] .badge').length, 1,
+ "m2mtags should contain one tag");
+ assert.strictEqual($('.modal .o_field_many2manytags[name="timmy"] .o_badge_text').text(),
+ 'gold', "tag name should have been correctly loaded");
+
+ form.destroy();
+ });
+
+ QUnit.test('onchange and required fields with override in arch', function (assert) {
+ assert.expect(4);
+
+ this.data.partner.onchanges = {
+ turtles: function (obj) { }
+ };
+ this.data.turtle.fields.turtle_foo.required = true;
+ this.data.partner.records[0].turtles = [];
+
+ 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();
+
+ // triggers an onchange on partner, because the new record is valid
+ form.$('.o_field_x2many_list_row_add a').click();
+
+ assert.verifySteps(['read', 'default_get', 'onchange']);
+ form.destroy();
+ });
+
+ QUnit.test('onchange on a one2many containing a one2many', function (assert) {
+ // the purpose of this test is to ensure that the onchange specs are
+ // correctly and recursively computed
+ assert.expect(1);
+
+ this.data.partner.onchanges = {
+ p: function () { }
+ };
+ var checkOnchange = false;
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' +
+ '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ '' +
+ '
',
+ mockRPC: function (route, args) {
+ if (args.method === 'onchange' && checkOnchange) {
+ assert.strictEqual(args.args[3]['p.p.display_name'], '',
+ "onchange specs should be computed recursively");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ form.$('.o_field_x2many_list_row_add a').click();
+ $('.modal .o_field_x2many_list_row_add a').click();
+ $('.modal .o_data_cell input').val('new record').trigger('input');
+ checkOnchange = true;
+ $('.modal .modal-footer .btn-primary').click(); // save (should trigger the onchange)
+
+ form.destroy();
+ });
+
+ QUnit.test('editing tabbed one2many (editable=bottom)', function (assert) {
+ assert.expect(12);
+
+ this.data.partner.records[0].turtles = [];
+ for (var i = 0; i < 42; i++) {
+ var id = 100 + i;
+ this.data.turtle.records.push({ id: id, turtle_foo: 'turtle' + (id - 99) });
+ this.data.partner.records[0].turtles.push(id);
+ }
+
+ 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') {
+ // delay the onchange RPC
+ return $.when(def).then(_.constant(result));
+ }
+ return result;
+ },
+ });
+
+ form.$('.o_field_x2many_list_row_add a').click();
+ def = $.Deferred();
+ form.$('.o_field_widget[name=int_field]')
+ .val('44')
+ .trigger('input')
+ .trigger({ type: 'keydown', which: $.ui.keyCode.TAB });
+ def.resolve();
+
+ assert.strictEqual(document.activeElement, form.$('.o_field_widget[name=qux]')[0],
+ "qux field should have the focus");
+
+ form.$('.o_field_many2one input').click();
+ form.$('.o_field_many2one input').autocomplete('widget').find('a').first().click();
+ assert.strictEqual(form.$('.o_field_many2one input').val(), 'first record',
+ "the one2many field should have the expected value");
+
+ form.destroy();
+ });
+
+ QUnit.test('checkbox in an x2many that triggers an onchange', function (assert) {
+ assert.expect(1);
+
+ this.data.partner.onchanges = {
+ bar: function () { }
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
',
+ });
+
+ form.$('.o_field_x2many_list_row_add a').click();
+
+ form.$('.o_field_widget[name=bar] input').click();
+ assert.notOk(form.$('.o_field_widget[name=bar] input').prop('checked'),
+ "the checkbox should be unticked");
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many with default value: edit line to make it invalid', function (assert) {
+ assert.expect(3);
+
+ this.data.partner.fields.p.default = [
+ [0, false, { foo: "coucou", int_field: 5 }],
+ ];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
',
+ });
+
+ // edit the line and enter an invalid value for int_field
+ form.$('.o_data_row .o_data_cell:nth(1)').click();
+ form.$('.o_field_widget[name=int_field]').val('e').trigger('input');
+ form.$el.click(); // try to validate the line
+
+ assert.strictEqual(form.$('.o_data_row.o_selected_row').length, 1,
+ "line should not have been removed and should still be in edition");
+ assert.strictEqual($('.modal').length, 1,
+ "a confirmation dialog should be opened");
+ assert.ok(form.$('.o_field_widget[name=int_field]').hasClass('o_field_invalid'),
+ "should indicate that int_field is invalid");
+
+ form.destroy();
+ });
+
+ QUnit.test('default value for nested one2manys (coming from onchange)', function (assert) {
+ assert.expect(3);
+
+ this.data.partner.onchanges.p = function (obj) {
+ obj.p = [
+ [5],
+ [0, 0, { turtles: [[5], [4, 1]] }], // link record 1 by default
+ ];
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
',
+ mockRPC: function (route, args) {
+ if (args.method === 'create') {
+ assert.strictEqual(args.args[0].p[0][0], 0,
+ "should send a command 0 (CREATE) for p");
+ assert.deepEqual(args.args[0].p[0][2], { turtles: [[4, 1, false]] },
+ "should send the correct values");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ assert.strictEqual(form.$('.o_data_cell').text(), '1 record',
+ "should correctly display the value of the inner o2m");
+
+ form.$buttons.find('.o_form_button_save').click();
+
+ form.destroy();
+ });
+
+ QUnit.test('display correct value after validation error', function (assert) {
+ assert.expect(4);
+
+ this.data.partner.onchanges.turtles = function () { };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
',
+ mockRPC: function (route, args) {
+ if (args.method === 'onchange') {
+ if (args.args[1].turtles[0][2].turtle_foo === 'pinky') {
+ // we simulate a validation error. In the 'real' web client,
+ // the server error will be used by the session to display
+ // an error dialog. From the point of view of the basic
+ // model, the deferred is just rejected.
+ return $.Deferred().reject();
+ }
+ }
+ if (args.method === 'write') {
+ assert.deepEqual(args.args[1].turtles[0], [1, 2, { turtle_foo: 'foo' }],
+ 'should send the "good" value');
+ }
+ return this._super.apply(this, arguments);
+ },
+ viewOptions: { mode: 'edit' },
+ res_id: 1,
+ });
+
+ assert.strictEqual(form.$('.o_data_row .o_data_cell:nth(0)').text(), 'blip',
+ "initial text should be correct");
+
+ // click and edit value to 'foo', which will trigger onchange
+ form.$('.o_data_row .o_data_cell:nth(0)').click();
+ form.$('.o_field_widget[name=turtle_foo]').val('foo').trigger('input');
+ form.$el.click();
+ assert.strictEqual(form.$('.o_data_row .o_data_cell:nth(0)').text(), 'foo',
+ "field should have been changed to foo");
+
+ // click and edit value to 'pinky', which trigger a failed onchange
+ form.$('.o_data_row .o_data_cell:nth(0)').click();
+ form.$('.o_field_widget[name=turtle_foo]').val('pinky').trigger('input');
+ form.$el.click();
+
+ assert.strictEqual(form.$('.o_data_row .o_data_cell:nth(0)').text(), 'foo',
+ "turtle_foo text should now be set back to foo");
+
+ // we make sure here that when we save, the values are the current
+ // values displayed in the field.
+ form.$buttons.find('.o_form_button_save').click();
+
+ form.destroy();
+ });
+
+ QUnit.test('propagate context to sub views', function (assert) {
+ assert.expect(5);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
',
+ mockRPC: function (route, args) {
+ if (args.method === 'create') {
+ assert.strictEqual(args.args[0].p[0][0], 0,
+ "should send a command 0 (CREATE) for p");
+ assert.strictEqual(args.args[0].p[0][2].display_name, 'test',
+ "should send the correct values");
+ assert.strictEqual(args.args[0].p[0][2].turtles[0][0], 0,
+ "should send a command 0 (CREATE) for turtles");
+ assert.deepEqual(args.args[0].p[0][2].turtles[0][2], { display_name: 'test nested' },
+ "should send the correct values");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ form.$('.o_field_widget[name=display_name]').val('trigger onchange').trigger('input');
+
+ assert.strictEqual(form.$('.o_data_cell').text(), 'test',
+ "should have added the new row to the one2many");
+
+ // open the new subrecord to check the value of the nested o2m, and to
+ // ensure that it will be saved
+ form.$('.o_data_cell:first').click();
+ assert.strictEqual($('.modal .o_data_cell').text(), 'test nested',
+ "should have added the new row to the nested one2many");
+ $('.modal .modal-footer .btn-primary').click();
+
+ form.$buttons.find('.o_form_button_save').click();
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many with multiple pages and sequence field', function (assert) {
+ assert.expect(1);
+
+ this.data.partner.records[0].turtles = [3, 2, 1];
+ this.data.partner.onchanges.turtles = function () { };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
',
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ // starting condition
+ assert.strictEqual($('.o_data_cell').text(), "blipyopkawa");
+
+ // click add a new line
+ // save the record
+ // check line is at the correct place
+
+ var inputText = 'ninja';
+ form.$('.o_field_x2many_list_row_add a').click();
+ form.$('.o_input[name="turtle_foo"]').val(inputText).trigger('input');
+ form.$buttons.find('.o_form_button_save').click();
+
+ assert.strictEqual($('.o_data_cell').text(), "blipyopkawa" + inputText);
+ form.destroy();
+ });
+
+ QUnit.test('one2many with sequence field, override default_get, top when inline', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.records[0].turtles = [3, 2, 1];
+
+ this.data.turtle.fields.turtle_int.default = 10;
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
',
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ // starting condition
+ assert.strictEqual($('.o_data_cell').text(), "blipyopkawa");
+
+ // click add a new line
+ // save the record
+ // check line is at the correct place
+
+ var inputText = 'ninja';
+ form.$('.o_field_x2many_list_row_add a').click();
+ form.$('.o_input[name="turtle_foo"]').val(inputText).trigger('input');
+ form.$buttons.find('.o_form_button_save').click();
+
+ assert.strictEqual($('.o_data_cell').text(), inputText + "blipyopkawa");
+ form.destroy();
+ });
+
+ QUnit.test('one2many with sequence field, override default_get, bottom when popup', function (assert) {
+ assert.expect(3);
+
+ this.data.partner.records[0].turtles = [3, 2, 1];
+
+ this.data.turtle.fields.turtle_int.default = 10;
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ // NOTE: at some point we want to fix this in the framework so that an invisible field is not required.
+ '' +
+ '' +
+ '
' +
+ '' +
+ '
',
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ // starting condition
+ assert.strictEqual($('.o_data_cell').text(), "blipyopkawa");
+
+ // click add a new line
+ // save the record
+ // check line is at the correct place
+
+ var inputText = 'ninja';
+ $('.o_field_x2many_list_row_add a').click();
+ $('.o_input[name="turtle_foo"]').val(inputText).trigger('input');
+ $('.modal .modal-footer .btn-primary:first').click();
+
+ assert.strictEqual($('.o_data_cell').text(), "blipyopkawa" + inputText);
+
+ $('.o_form_button_save').click();
+ assert.strictEqual($('.o_data_cell').text(), "blipyopkawa" + inputText);
+ form.destroy();
+ });
+
+ QUnit.test('one2many with sequence field, override default_get, not last page', function (assert) {
+ assert.expect(1);
+
+ this.data.partner.records[0].turtles = [3, 2, 1];
+
+ this.data.turtle.fields.turtle_int.default = 10;
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ '' +
+ '
' +
+ '' +
+ '
',
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ // click add a new line
+ // check turtle_int for new is the current max of the page
+ $('.o_field_x2many_list_row_add a').click();
+ assert.strictEqual($('.modal .o_input[name="turtle_int"]').val(), '10');
+ form.destroy();
+ });
+
+ QUnit.test('one2many with sequence field, override default_get, last page', function (assert) {
+ assert.expect(1);
+
+ this.data.partner.records[0].turtles = [3, 2, 1];
+
+ this.data.turtle.fields.turtle_int.default = 10;
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ '' +
+ '
' +
+ '' +
+ '
',
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ // click add a new line
+ // check turtle_int for new is the current max of the page +1
+ $('.o_field_x2many_list_row_add a').click();
+ assert.strictEqual($('.modal .o_input[name="turtle_int"]').val(), '22');
+ form.destroy();
+ });
+
+ QUnit.test('one2many with sequence field, fetch name_get from empty list, field text', function (assert) {
+ // There was a bug where a RPC would fail because no route was set.
+ // The scenario is:
+ // - create a new parent model, which has a one2many
+ // - add at least 2 one2many lines which have:
+ // - a handle field
+ // - a many2one, which is not required, and we will leave it empty
+ // - reorder the lines with the handle
+ // -> This will call a resequence, which calls a name_get.
+ // -> With the bug that would fail, if it's ok the test will pass.
+
+ // This test will also make sure lists with
+ // FieldText (turtle_description) can be reordered with a handle.
+ // More specifically this will trigger a reset on a FieldText
+ // while the field is not in editable mode.
+ assert.expect(4);
+
+ this.data.turtle.fields.turtle_int.default = 10;
+ this.data.turtle.fields.product_id.default = 37;
+ this.data.turtle.fields.not_required_product_id = {
+ string: "Product",
+ type: "many2one",
+ relation: 'product'
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
',
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ // starting condition
+ assert.strictEqual($('.o_data_cell:nth-child(2)').text(), "");
+
+ var inputText1 = 'relax';
+ var inputText2 = 'max';
+ form.$('.o_field_x2many_list_row_add a').click();
+ form.$('.o_input[name="turtle_foo"]').val(inputText1).trigger('input');
+ form.$('.o_field_x2many_list_row_add a').click();
+ form.$('.o_input[name="turtle_foo"]').val(inputText2).trigger('input');
+ form.$('.o_field_x2many_list_row_add a').click();
+
+ assert.strictEqual($('.o_data_cell:nth-child(2)').text(), inputText1 + inputText2);
+
+ var $handles = form.$('.ui-sortable-handle');
+
+ assert.equal($handles.length, 3, 'There should be 3 sequence handlers');
+
+ testUtils.dragAndDrop($handles.eq(1),
+ form.$('tbody tr').first(),
+ { position: 'top' }
+ );
+
+ assert.strictEqual($('.o_data_cell:nth-child(2)').text(), inputText2 + inputText1);
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many with several pages, onchange and default order', function (assert) {
+ // This test reproduces a specific scenario where a one2many is displayed
+ // over several pages, and has a default order such that a record that
+ // would normally be on page 1 is actually on another page. Moreover,
+ // there is an onchange on that one2many which converts all commands 4
+ // (LINK_TO) into commands 1 (UPDATE), which is standard in the ORM.
+ // This test ensures that the record displayed on page 2 is never fully
+ // read.
+ assert.expect(8);
+
+ var data = this.data;
+ data.partner.records[0].turtles = [1, 2, 3];
+ data.turtle.records[0].partner_ids = [1];
+ data.partner.onchanges = {
+ turtles: function (obj) {
+ var res = _.map(obj.turtles, function (command) {
+ if (command[0] === 1) { // already an UPDATE command: do nothing
+ return command;
+ }
+ // convert LINK_TO commands to UPDATE commands
+ var id = command[1];
+ var record = _.findWhere(data.turtle.records, { id: id });
+ return [1, id, _.pick(record, ['turtle_int', 'turtle_foo', 'partner_ids'])];
+ });
+ obj.turtles = [[5]].concat(res);
+ },
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: data,
+ arch: '
',
+ mockRPC: function (route, args) {
+ var ids = args.method === 'read' ? ' [' + args.args[0] + ']' : '';
+ assert.step(args.method + ids);
+ return this._super.apply(this, arguments);
+ },
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ assert.strictEqual(form.$('.o_data_cell.foo').text(), 'blipkawa',
+ "should display two records out of three, in the correct order");
+
+ // edit turtle_int field of first row
+ form.$('.o_data_cell:first').click();
+ form.$('.o_field_widget[name=turtle_int]').val(3).trigger('input');
+ form.$el.click(); // leave edition
+
+ assert.strictEqual(form.$('.o_data_cell.foo').text(), 'blipkawa',
+ "should still display the same two records");
+
+ assert.verifySteps([
+ 'read [1]', // main record
+ 'read [1,2,3]', // one2many (turtle_foo, all records)
+ 'read [2,3]', // one2many (all fields in view, records of first page)
+ 'read [2,4]', // many2many inside one2many (partner_ids), first page only
+ 'onchange',
+ ]);
+
+ form.destroy();
+ });
+
+ QUnit.test('new record, with one2many with more default values than limit', function (assert) {
+ assert.expect(2);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
',
+ context: { default_turtles: [1, 2, 3] },
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+ assert.strictEqual(form.$('.o_data_row').text(), 'yopblip',
+ 'data has been properly loaded');
+ form.$buttons.find('.o_form_button_save').click();
+
+ assert.strictEqual(form.$('.o_data_row').text(), 'yopblip',
+ 'data has been properly saved');
+ form.destroy();
+ });
+
+ QUnit.test('add a new line after limit is reached should behave nicely', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.records[0].turtles = [1, 2, 3];
+
+ this.data.partner.onchanges = {
+ turtles: function (obj) {
+ obj.turtles = [
+ [5],
+ [1, 1, { turtle_foo: "yop" }],
+ [1, 2, { turtle_foo: "blip" }],
+ [1, 3, { turtle_foo: "kawa" }],
+ [0, obj.turtles[3][2], { turtle_foo: "abc" }],
+ ];
+ },
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
',
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ form.$('.o_field_x2many_list_row_add a').click();
+ assert.strictEqual(form.$('.o_data_row').length, 4, 'should have 4 data rows');
+ form.$('.o_input[name="turtle_foo"]').val('a').trigger('input');
+ assert.strictEqual(form.$('.o_data_row').length, 4,
+ 'should still have 4 data rows (the limit is increased to 4)');
+
+ form.destroy();
+ });
+
+ QUnit.test('onchange in a one2many with non inline view on an existing record', function (assert) {
+ assert.expect(6);
+
+ 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 () { } };
+
+ this.data.partner_type.fields.partner_ids = { string: "Partner", type: "one2many", relation: 'partner' };
+ this.data.partner_type.records[0].partner_ids = [1, 2];
+
+ var form = testUtils.createView({
+ View: FormView,
+ model: 'partner_type',
+ data: this.data,
+ arch: '
',
+ archs: {
+ 'partner,false,list': '' +
+ '' +
+ '' +
+ '',
+ },
+ res_id: 12,
+ mockRPC: function (route, args) {
+ assert.step(args.method);
+ return this._super.apply(this, arguments);
+ },
+ viewOptions: { mode: 'edit' },
+ });
+
+ // swap 2 lines in the one2many
+ testUtils.dragAndDrop(form.$('.ui-sortable-handle:eq(1)'), form.$('tbody tr').first(),
+ { position: 'top' });
+ assert.verifySteps(['load_views', 'read', 'read', 'onchange', 'onchange']);
+ form.destroy();
+ });
+
+ QUnit.test('onchange in a one2many with non inline view on a new record', function (assert) {
+ assert.expect(8);
+
+ this.data.turtle.onchanges = {
+ display_name: function (obj) {
+ if (obj.display_name) {
+ obj.turtle_int = 44;
+ }
+ },
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
',
+ archs: {
+ 'turtle,false,list': '' +
+ '' +
+ '' +
+ '',
+ },
+ mockRPC: function (route, args) {
+ assert.step(args.method || route);
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ // add a row and trigger the onchange
+ form.$('.o_field_x2many_list_row_add a').click();
+ form.$('.o_data_row .o_field_widget[name=display_name]').val('a name').trigger('input');
+
+ assert.strictEqual(form.$('.o_data_row .o_field_widget[name=turtle_int]').val(), "44",
+ "should have triggered the onchange");
+
+ assert.verifySteps([
+ 'load_views', // load sub list
+ 'default_get', // main record
+ 'onchange', // main record
+ 'default_get', // sub record
+ 'onchange', // sub record
+ 'onchange', // edition of display_name of sub record
+ ]);
+
+ form.destroy();
+ });
+
+ QUnit.test('add a line, edit it and "Save & New"', function (assert) {
+ assert.expect(5);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' +
+ '
' +
+ '' +
+ '
',
+ });
+
+ assert.strictEqual(form.$('.o_data_row').length, 0,
+ "there should be no record in the relation");
+
+ // add a new record
+ form.$('.o_field_x2many_list_row_add a').click();
+ $('.modal .o_field_widget').val('new record').trigger('input');
+ $('.modal .modal-footer .btn-primary:first').click(); // Save & Close
+
+ assert.strictEqual(form.$('.o_data_row .o_data_cell').text(), 'new record',
+ "should display the new record");
+
+ // reopen freshly added record and edit it
+ form.$('.o_data_row .o_data_cell').click();
+ $('.modal .o_field_widget').val('new record edited').trigger('input');
+
+ // save it, and choose to directly create another record
+ $('.modal .modal-footer .btn-primary:nth(1)').click(); // Save & New
+
+ assert.strictEqual($('.modal').length, 1,
+ "the model should still be open");
+ assert.strictEqual($('.modal .o_field_widget').text(), '',
+ "should have cleared the input");
+
+ $('.modal .o_field_widget').val('another new record').trigger('input');
+ $('.modal .modal-footer .btn-primary:first').click(); // Save & Close
+
+ assert.strictEqual(form.$('.o_data_row .o_data_cell').text(),
+ 'new record editedanother new record', "should display the two records");
+
+ form.destroy();
+ });
+
+ QUnit.test('o2m add a line custom control create editable', function (assert) {
+ assert.expect(5);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:
+ '
',
+ });
+
+ // new controls correctly added
+ var $td = form.$('.o_field_x2many_list_row_add');
+ assert.strictEqual($td.length, 1);
+ assert.strictEqual($td.closest('tr').find('td').length, 1);
+ assert.strictEqual($td.text(), "Add foodAdd pizzaAdd pasta");
+
+ // click add food
+ // check it's empty
+ form.$('.o_field_x2many_list_row_add a:eq(0)').click();
+ $('.modal .modal-footer .btn-primary:first').click();
+ assert.strictEqual($('.o_data_cell').text(), "");
+
+ // click add pizza
+ // save the modal
+ // check it's pizza
+ form.$('.o_field_x2many_list_row_add a:eq(1)').click();
+ $('.modal .modal-footer .btn-primary:first').click();
+ assert.strictEqual($('.o_data_cell').text(), "pizza");
+
+ // click add pasta
+ // save the whole record
+ // check it's pizzapasta
+ form.$('.o_field_x2many_list_row_add a:eq(2)').click();
+ $('.modal .modal-footer .btn-primary:first').click();
+ assert.strictEqual($('.o_data_cell').text(), "pizzapasta");
+
+ form.destroy();
+ });
+
+ QUnit.test('o2m add a line custom control create align with handle', function (assert) {
+ assert.expect(3);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:
+ '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
',
+ });
+
+ // controls correctly added, at one column offset when handle is present
+ var $tr = form.$('.o_field_x2many_list_row_add').closest('tr');
+ assert.strictEqual($tr.find('td').length, 2);
+ assert.strictEqual($tr.find('td:eq(0)').text(), "");
+ assert.strictEqual($tr.find('td:eq(1)').text(), "Add a line");
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many form view with action button', function (assert) {
+ // once the action button is clicked, the record is reloaded (via the
+ // on_close handler, executed because the python method does not return
+ // any action, or an ir.action.act_window_close) ; this test ensures that
+ // it reloads the fields of the opened view (i.e. the form in this case).
+ // See https://github.com/odoo/odoo/issues/24189
+ assert.expect(7);
+
+ var data = this.data;
+ data.partner.records[0].p = [2];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: data,
+ res_id: 1,
+ arch: '
' +
+ '' +
+ '' +
+ '
' +
+ '' +
+ '' +
+ '
' +
+ '' +
+ '
',
+ archs: {
+ 'partner_type,false,list': '',
+ },
+ intercepts: {
+ execute_action: function (ev) {
+ data.partner.records[1].display_name = 'new name';
+ data.partner.records[1].timmy = [12];
+ ev.data.on_closed();
+ },
+ },
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ assert.strictEqual(form.$('.o_data_row').length, 1,
+ "there should be one record in the one2many");
+ assert.strictEqual(form.$('.o_data_cell').text(), 'second record',
+ "initial display_name of o2m record should be correct");
+
+ // open one2many record in form view
+ form.$('.o_data_cell:first').click();
+ assert.strictEqual($('.modal .o_form_view').length, 1,
+ "should have opened the form view in a dialog");
+ assert.strictEqual($('.modal .o_form_view .o_data_row').length, 0,
+ "there should be no record in the many2many");
+
+ // click on the action button
+ $('.modal .o_form_view button').click();
+ assert.strictEqual($('.modal .o_data_row').length, 1,
+ "fields in the o2m form view should have been read");
+ assert.strictEqual($('.modal .o_data_cell').text(), 'gold',
+ "many2many subrecord should have been fetched");
+
+ // save the dialog
+ $('.modal .modal-footer .btn-primary').click();
+
+ assert.strictEqual(form.$('.o_data_cell').text(), 'new name',
+ "fields in the o2m list view should have been read as well");
+
+ form.destroy();
+ });
+
+ QUnit.test('onchange affecting inline unopened list view', function (assert) {
+ // when we got onchange result for fields of record that were not
+ // already available because they were in a inline view not already
+ // opened, in a given configuration the change were applied ignoring
+ // existing data, thus a line of a one2many field inside a one2many
+ // field could be duplicated unexplectedly
+ assert.expect(5);
+
+ var numUserOnchange = 0;
+
+ this.data.user.onchanges = {
+ partner_ids: function (obj) {
+ if (numUserOnchange === 0) {
+ // simulate proper server onchange after save of modal with new record
+ obj.partner_ids = [
+ [5],
+ [1, 1, {
+ display_name: 'first record',
+ turtles: [
+ [5],
+ [1, 2, { 'display_name': 'donatello' }],
+ ],
+ }],
+ [1, 2, {
+ display_name: 'second record',
+ turtles: [
+ [5],
+ obj.partner_ids[1][2].turtles[0],
+ ],
+ }],
+ ];
+ }
+ numUserOnchange++;
+ },
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'user',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
',
+ res_id: 17,
+ });
+
+ // add a turtle on second partner
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_data_row:eq(1)').click();
+ $('.modal .o_field_x2many_list_row_add a').click();
+ $('.modal input[name="display_name"]').val('michelangelo').change();
+ $('.modal .btn-primary').click();
+ // open first partner so changes from previous action are applied
+ form.$('.o_data_row:eq(0)').click();
+ $('.modal .btn-primary').click();
+ form.$buttons.find('.o_form_button_save').click();
+
+ assert.strictEqual(numUserOnchange, 2,
+ 'there should 2 and only 2 onchange from closing the partner modal');
+
+ form.$('.o_data_row:eq(0)').click();
+ assert.strictEqual($('.modal .o_data_row').length, 1,
+ 'only 1 turtle for first partner');
+ assert.strictEqual($('.modal .o_data_row').text(), 'donatello',
+ 'first partner turtle is donatello');
+ $('.modal .o_form_button_cancel').click();
+
+ form.$('.o_data_row:eq(1)').click();
+ assert.strictEqual($('.modal .o_data_row').length, 1,
+ 'only 1 turtle for second partner');
+ assert.strictEqual($('.modal .o_data_row').text(), 'michelangelo',
+ 'second partner turtle is michelangelo');
+ $('.modal .o_form_button_cancel').click();
+
+ form.destroy();
+ });
+
+ QUnit.test('click on URL should not open the record', 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,
+ });
+
+ form.$('.o_email_cell a').click();
+ assert.strictEqual($('.modal .o_form_view').length, 0,
+ 'click should not open the modal');
+
+ form.$('.o_url_cell a').click();
+ assert.strictEqual($('.modal .o_form_view').length, 0,
+ 'click should not open the modal');
+ form.destroy();
+ });
+
+ QUnit.test('create and edit on m2o in o2m, and press ESCAPE', function (assert) {
+ assert.expect(4);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
',
+ archs: {
+ 'partner,false,form': '
',
+ },
+ });
+
+ form.$('.o_field_x2many_list_row_add a').click();
+
+ assert.strictEqual(form.$('.o_selected_row').length, 1,
+ "should have create a new row in edition");
+
+ var $input = form.$('.o_field_widget[name="turtle_trululu"] input');
+ $input.click();
+ $input.autocomplete('widget').find('.o_m2o_dropdown_option').focus().click();
+
+ assert.strictEqual($('.modal .o_form_view').length, 1,
+ "should have opened a form view in a dialog");
+
+ $('.modal .o_form_view .o_field_widget[name=display_name]').trigger($.Event('keydown', {
+ which: $.ui.keyCode.ESCAPE,
+ keyCode: $.ui.keyCode.ESCAPE,
+ }));
+
+ assert.strictEqual($('.modal .o_form_view').length, 0,
+ "should have closed the dialog");
+ assert.strictEqual(form.$('.o_selected_row').length, 1,
+ "new row should still be present");
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many add a line should not crash if orderedResIDs is not set', function (assert) {
+ // There is no assertion, the code will just crash before the bugfix.
+ assert.expect(0);
+
+ this.data.partner.records[0].turtles = [];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
',
+ viewOptions: {
+ mode: 'edit',
+ },
+ intercepts: {
+ execute_action: function (event) {
+ event.data.on_fail();
+ },
+ },
+ });
+
+ $('button[name="action_invoice_open"]').click();
+ form.$('.o_field_x2many_list_row_add a').click();
+ form.destroy();
+ });
+
+ QUnit.test('one2many shortcut tab should not crash when there is no input widget', function (assert) {
+ assert.expect(2);
+
+ // create a one2many view which has no input (only 1 textarea in this case)
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
',
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ // add a row, fill it, then trigger the tab shortcut
+ form.$('.o_field_x2many_list_row_add a').click();
+ form.$('.o_input[name="turtle_foo"]')
+ .val('ninja')
+ .trigger('input')
+ .trigger($.Event('keydown', {
+ which: $.ui.keyCode.TAB,
+ keyCode: $.ui.keyCode.TAB,
+ }));
+
+ assert.strictEqual(form.$('.o_field_text').text(), 'blipninja',
+ 'current line should be saved');
+ assert.strictEqual(form.$('textarea.o_field_text').length, 1,
+ 'new line should be created');
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many with onchange, required field, shortcut enter', function (assert) {
+ assert.expect(5);
+
+ this.data.turtle.onchanges = {
+ turtle_foo: function () { },
+ };
+
+ var def;
+ 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;
+ },
+ // simulate what happens in the client:
+ // the new value isn't notified directly to the model
+ fieldDebounce: 5000,
+ });
+
+ var value = "hello";
+
+ // add a new line
+ form.$('.o_field_x2many_list_row_add a').click();
+
+ // we want to add a delay to simulate an onchange
+ def = $.Deferred();
+
+ // write something in the field
+ var $input = form.$('input[name="turtle_foo"]');
+ $input.val(value).trigger('input');
+
+ // trigger keydown ENTER
+ $input.trigger($.Event('keydown', {
+ keyCode: $.ui.keyCode.ENTER,
+ which: $.ui.keyCode.ENTER,
+ }));
+
+ // check that nothing changed before the onchange finished
+ assert.strictEqual($input.val(), value, "input content shouldn't change");
+ assert.strictEqual(form.$('.o_data_row').length, 1,
+ "should still contain only one row");
+
+ // unlock onchange
+ def.resolve();
+
+ // check the current line is added with the correct content and a new line is editable
+ assert.strictEqual(form.$('td.o_data_cell').text(), value);
+ assert.strictEqual(form.$('input[name="turtle_foo"]').val(), '');
+ assert.strictEqual(form.$('.o_data_row').length, 2,
+ "should now contain two rows");
+
+ form.destroy();
+ });
+
+ QUnit.test('no deadlock when leaving a one2many line with uncommitted changes', function (assert) {
+ // Before unselecting a o2m line, field widgets are asked to commit their changes (new values
+ // that they wouldn't have sent to the model yet). This test is added alongside a bug fix
+ // ensuring that we don't end up in a deadlock when a widget actually has some changes to
+ // commit at that moment.
+ assert.expect(9);
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
',
+ mockRPC: function (route, args) {
+ assert.step(args.method);
+ return this._super.apply(this, arguments);
+ },
+ // we set a fieldDebounce to precisely mock the behavior of the webclient: changes are
+ // not sent to the model at keystrokes, but when the input is left
+ fieldDebounce: 5000,
+ });
+
+ form.$('.o_field_x2many_list_row_add a').click();
+
+ form.$('input').val('some foo value').trigger('input');
+
+ // click to add a second row to unselect the current one, then save
+ form.$('.o_field_x2many_list_row_add a').click();
+ form.$buttons.find('.o_form_button_save').click();
+
+ assert.strictEqual(form.$('.o_form_readonly').length, 1,
+ "form view should be in readonly");
+ assert.strictEqual(form.$('.o_data_row').text(), 'some foo value',
+ "foo field should have correct value");
+ assert.verifySteps([
+ 'default_get', // main record
+ 'default_get', // line 1
+ 'default_get', // line 2
+ 'create',
+ 'read', // main record
+ 'read', // line 1
+ ]);
+
+ 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: '
'
+ },
+ 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 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,
+ });
+
+ // 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_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index 9c7c6363a40..ed34b56574b 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -1,17 +1,11 @@
odoo.define('web.relational_fields_tests', function (require) {
"use strict";
-var AbstractField = require('web.AbstractField');
-var BasicModel = require('web.BasicModel');
var concurrency = require('web.concurrency');
var FormView = require('web.FormView');
-var ListRenderer = require('web.ListRenderer');
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 fieldUtils = require('web.field_utils');
var createView = testUtils.createView;
@@ -106,7 +100,6 @@ QUnit.module('relational_fields', {
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: [
@@ -134,7 +127,6 @@ QUnit.module('relational_fields', {
turtle_bar: false,
turtle_foo: "kawa",
turtle_int: 21,
- turtle_qux: 9.8,
partner_ids: [],
turtle_ref: 'product,37',
}],
@@ -158,11416 +150,6 @@ QUnit.module('relational_fields', {
}
}, 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: '
',
- 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) {
- 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, { 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: '
',
- '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: '
',
- },
- 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('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();
- });
-
- 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,
- });
-
- 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:'
',
- 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.$('.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:'
',
- 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(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: '
',
- 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,
- });
-
- 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: 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: '
',
- },
- 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 (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: '
',
- 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: '
' +
- '' +
- '' +
- '' +
- '' +
- '' +
- // field without Widget in the list
- '' +
- '' +
- '
' +
- '' +
- // field with Widget requiring specialData in the form
- '' +
- '
' +
- '' +
- '' +
- '
',
- res_id: 1,
- viewOptions: {
- mode: 'edit',
- },
- mockRPC: function (route) {
- if (route === '/web/dataset/call_kw/partner/search_count') {
- nbFetchSpecialDomain++;
- }
- return this._super.apply(this, arguments);
- }
- });
-
- form.$('.o_field_one2many .o_field_x2many_list_row_add a').click();
- assert.strictEqual($('.modal').length, 1, "form view dialog should be opened");
- $('.modal-body input[name="model_name"]').val('partner').trigger('input');
- $('.modal-footer button:first').click();
-
- assert.strictEqual(form.$('.o_field_one2many tbody tr:first').text(), "coucouhas changed[]",
- "the onchange should create one new record and remove the existing");
-
- form.$('.o_field_one2many .o_list_view tbody tr:eq(0) td:first').click();
-
- form.$buttons.find('.o_form_button_save').click();
- assert.strictEqual(nbFetchSpecialDomain, 1,
- "should only fetch special domain once");
- form.destroy();
- });
-
- QUnit.test('one2many without inline tree arch', function (assert) {
- assert.expect(2);
-
- this.data.partner.records[0].turtles = [2,3];
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch: '
' +
- '' +
- '' + // check if the view don not call load view (widget without useSubview)
- '' +
- '' + // check if the view don not call load view in invisible
- '' +
- '
',
- res_id: 1,
- archs: {
- "turtle,false,list": '',
- }
- });
-
- assert.strictEqual(form.$('.o_field_widget[name="turtles"] .o_list_view').length, 1,
- 'should display one2many list view in the modal');
-
- assert.strictEqual(form.$('.o_data_row').length, 2,
- 'should display the 2 turtles');
-
- form.destroy();
- });
-
- QUnit.test('many2one and many2many in one2many', function (assert) {
- assert.expect(11);
-
- this.data.turtle.records[1].product_id = 37;
- this.data.partner.records[0].turtles = [2, 3];
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch: '
',
- });
-
- // add new line (first, xpad)
- form.$('.o_field_x2many_list_row_add a').click();
- form.$('input[name="display_name"]').val('first').trigger('input');
- form.$('div[name="product_id"] input').click();
- // the onchange won't be generated
- $('li.ui-menu-item a:contains(xpad)').trigger('mouseenter').click();
-
- assert.strictEqual(form.$('.o_field_many2manytags.o_input').length, 1,
- 'should display the line in editable mode');
- assert.strictEqual(form.$('.o_field_many2one input').val(), "xpad",
- 'should display the product xpad');
- assert.strictEqual(form.$('.o_field_many2manytags.o_input .o_badge_text').text(), "first record",
- 'should display the tag from the onchange');
-
- form.$('input.o_field_integer[name="int_field"]').click();
-
- assert.strictEqual(form.$('.o_data_cell.o_required_modifier').text(), "xpad",
- 'should display the product xpad');
- assert.strictEqual(form.$('.o_field_many2manytags:not(.o_input) .o_badge_text').text(), "first record",
- 'should display the tag in readonly');
-
- // enable the many2many onchange and generate it
- form.$('input.o_field_integer[name="int_field"]').val('10').trigger('input');
-
- assert.strictEqual(form.$('.o_data_cell.o_required_modifier').text(), "xenomorphexphone",
- 'should display the product xphone and xenomorphe');
- assert.strictEqual(form.$('.o_data_row').text().replace(/\s+/g, ' '), "firstxenomorphe second record new linexphone first record ",
- 'should display the name, one2many and many2many value');
-
- // disable the many2many onchange
- form.$('input.o_field_integer[name="int_field"]').val('0').trigger('input');
-
- // remove and start over
- form.$('.o_list_record_remove:first button').click();
- form.$('.o_list_record_remove:first button').click();
-
- // enable the many2many onchange
- form.$('input.o_field_integer[name="int_field"]').val('10').trigger('input');
-
- // add new line (first, xenomorphe)
- form.$('.o_field_x2many_list_row_add a').click();
- form.$('input[name="display_name"]').val('first').trigger('input');
- form.$('div[name="product_id"] input').click();
- // generate the onchange
- $('li.ui-menu-item a:contains(xenomorphe)').trigger('mouseenter').click();
-
- assert.strictEqual(form.$('.o_field_many2manytags.o_input').length, 1,
- 'should display the line in editable mode');
- assert.strictEqual(form.$('.o_field_many2one input').val(), "xenomorphe",
- 'should display the product xenomorphe');
- assert.strictEqual(form.$('.o_field_many2manytags.o_input .o_badge_text').text(), "second record",
- 'should display the tag from the onchange');
-
- // put list in readonly mode
- form.$('input.o_field_integer[name="int_field"]').click();
-
- assert.strictEqual(form.$('.o_data_cell.o_required_modifier').text(), "xenomorphexphone",
- 'should display the product xphone and xenomorphe');
- assert.strictEqual(form.$('.o_field_many2manytags:not(.o_input) .o_badge_text').text(), "second recordfirst record",
- 'should display the tag in readonly (first record and second record)');
-
- form.$('input.o_field_integer[name="int_field"]').val('10').trigger('input');
-
- assert.strictEqual(form.$('.o_data_row').text().replace(/\s+/g, ' '), "firstxenomorphe second record new linexphone first record ",
- 'should display the name, one2many and many2many value');
-
- form.$buttons.find('.o_form_button_save').click();
-
- assert.strictEqual(form.$('.o_data_row').text().replace(/\s+/g, ' '), "firstxenomorphe second record new linexphone first record ",
- 'should display the name, one2many and many2many value after save');
-
- form.destroy();
- });
-
- QUnit.test('load view for x2many in one2many', function (assert) {
- assert.expect(2);
-
- this.data.turtle.records[1].product_id = 37;
- this.data.partner.records[0].turtles = [2,3];
- this.data.partner.records[2].turtles = [1,3];
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch: '
' +
- '' +
- '' +
- '' +
- '
' +
- '' +
- '' +
- '' +
- '' +
- '
' +
- '' +
- '' +
- '' +
- '' +
- '' +
- '
',
- res_id: 1,
- archs: {
- "partner,false,list": '',
- },
- });
-
- assert.strictEqual(form.$('.o_data_row').length, 2,
- 'should display the 2 turtles');
-
- form.$('.o_data_row:first').click();
-
- assert.strictEqual($('.modal .o_field_widget[name="partner_ids"] .o_list_view').length, 1,
- 'should display many2many list view in the modal');
-
- form.destroy();
- });
-
- QUnit.test('one2many (who contains a one2many) with tree view and without form view', function (assert) {
- assert.expect(1);
-
- // avoid error in _postprocess
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch: '
',
- },
- });
-
- form.$('.o_data_row:first').click();
-
- assert.strictEqual($('.modal .o_field_widget[name="turtle_foo"]').text(), 'blip',
- 'should open the modal and display the form field');
-
- form.destroy();
- });
-
- QUnit.test('one2many with x2many in form view (but not in list view)', function (assert) {
- assert.expect(1);
-
- // avoid error when saving the edited related record (because the
- // related x2m field is unknown in the inline list view)
- // also ensure that the changes are correctly saved
-
- this.data.turtle.fields.o2m = {string: "o2m", type: "one2many", relation: 'user'};
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch: '
',
- },
- res_id: 4,
- });
-
- assert.strictEqual(form.$('.o_field_widget .o_kanban_view').length, 1,
- "should have one inner kanban view for the one2many field");
- assert.strictEqual(form.$('.o_field_widget .o_kanban_view .o_kanban_record:not(.o_kanban_ghost)').length, 0,
- "should not have kanban records yet");
-
- // // switch to edit mode and create a new kanban record
- form.$buttons.find('.o_form_button_edit').click();
- form.$('.o_field_widget .o-kanban-button-new').click();
-
- // save & close the modal
- assert.strictEqual($('.modal-content input.o_field_widget').val(), 'My little Foo Value',
- "should already have the default value for field foo");
- $('.modal-content .btn-primary').first().click();
-
- assert.strictEqual(form.$('.o_field_widget .o_kanban_view').length, 1,
- "should have one inner kanban view for the one2many field");
- assert.strictEqual(form.$('.o_field_widget .o_kanban_view .o_kanban_record:not(.o_kanban_ghost)').length, 1,
- "should now have one kanban record");
- assert.strictEqual(form.$('.o_field_widget .o_kanban_view .o_kanban_record:not(.o_kanban_ghost) .o_test_id').text(),
- '', "should not have a value for the id field");
- assert.strictEqual(form.$('.o_field_widget .o_kanban_view .o_kanban_record:not(.o_kanban_ghost) .o_test_foo').text(),
- 'My little Foo Value', "should have a value for the foo field");
-
- // save the view to force a create of the new record in the one2many
- form.$buttons.find('.o_form_button_save').click();
- assert.strictEqual(form.$('.o_field_widget .o_kanban_view').length, 1,
- "should have one inner kanban view for the one2many field");
- assert.strictEqual(form.$('.o_field_widget .o_kanban_view .o_kanban_record:not(.o_kanban_ghost)').length, 1,
- "should now have one kanban record");
- assert.notEqual(form.$('.o_field_widget .o_kanban_view .o_kanban_record:not(.o_kanban_ghost) .o_test_id').text(),
- '', "should now have a value for the id field");
- assert.strictEqual(form.$('.o_field_widget .o_kanban_view .o_kanban_record:not(.o_kanban_ghost) .o_test_foo').text(),
- 'My little Foo Value', "should still have a value for the foo field");
-
- form.destroy();
- });
-
- QUnit.test('focusing fields in one2many list', function (assert) {
- assert.expect(2);
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch: '
',
- mockRPC: function (route, args) {
- assert.step(args.method);
- return this._super.apply(this, arguments);
- },
- res_id: 1,
- });
- form.$buttons.find('.o_form_button_edit').click();
-
- assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "0",
- "int_field should start with value 0");
- form.$('.o_field_x2many_list_row_add a').click();
- assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "0",
- "int_field should still be 0 (no onchange should have been done yet");
-
- assert.verifySteps(['read', 'default_get'], "no onchange should have been applied");
-
- form.$('.o_field_widget[name="turtle_foo"]').val("some text").trigger('input');
- assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "1",
- "int_field should now be 1 (the onchange should have been done");
-
- form.destroy();
- });
-
- QUnit.test('one2many list editable: trigger onchange when row is valid', function (assert) {
- // should omit require fields that aren't in the view as they (obviously)
- // have no value, when checking the validity of required fields
- // shouldn't consider numerical fields with value 0 as unset
- assert.expect(12);
-
- this.data.turtle.fields.turtle_foo.required = true;
- this.data.turtle.fields.turtle_qux.required = true; // required field not in the view
- this.data.turtle.fields.turtle_bar.required = true; // required boolean field with no default
- delete this.data.turtle.fields.turtle_bar.default;
- this.data.turtle.fields.turtle_int.required = true; // required int field (default 0)
- this.data.turtle.fields.turtle_int.default = 0;
- this.data.turtle.fields.partner_ids.required = true; // required many2many
- this.data.partner.onchanges = {
- turtles: function (obj) {
- obj.int_field = obj.turtles.length;
- },
- };
- this.data.partner.records[0].int_field = 0;
- this.data.partner.records[0].turtles = [];
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch: '
' +
- '' +
- '' +
- '
',
- mockRPC: function (route, args) {
- assert.step(args.method);
- return this._super.apply(this, arguments);
- },
- archs: {
- 'turtle,false,list' : '' +
- '' +
- '' +
- '' +
- '' +
- '' +
- '',
- },
- res_id: 1,
- });
- form.$buttons.find('.o_form_button_edit').click();
-
- assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "0",
- "int_field should start with value 0");
-
- // add a new row (which is invalid at first)
- form.$('.o_field_x2many_list_row_add a').click();
- assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "0",
- "int_field should still be 0 (no onchange should have been done yet)");
- assert.verifySteps(['load_views', 'read', 'default_get'], "no onchange should have been applied");
-
- // fill turtle_foo field
- form.$('.o_field_widget[name="turtle_foo"]').val("some text").trigger('input');
- assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "0",
- "int_field should still be 0 (no onchange should have been done yet)");
- assert.verifySteps(['load_views', 'read', 'default_get'], "no onchange should have been applied");
-
- // fill partner_ids field with a tag (all required fields will then be set)
- var $m2mInput = form.$('.o_field_widget[name=partner_ids] input');
- $m2mInput.click();
- $m2mInput.autocomplete('widget').find('li:first()').click();
- assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "1",
- "int_field should now be 1 (the onchange should have been done");
-
- form.destroy();
- });
-
- QUnit.test('one2many list editable: \'required\' modifiers is properly working', function (assert) {
- assert.expect(3);
-
- this.data.partner.onchanges = {
- turtles: function (obj) {
- obj.int_field = obj.turtles.length;
- },
- };
-
- this.data.partner.records[0].turtles = [];
-
- 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_widget[name="int_field"]').val(), "10",
- "int_field should start with value 10");
-
- form.$('.o_field_x2many_list_row_add a').click();
-
- assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "10",
- "int_field should still be 10 (no onchange, because line is not valid)");
-
- // fill turtle_foo field
- form.$('.o_field_widget[name="turtle_foo"]').val("some text").trigger('input');
-
- assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "1",
- "int_field should be 1 (onchange triggered, because line is now valid)");
-
- form.destroy();
- });
-
- QUnit.test('one2many list editable: \'required\' modifiers is properly working, part 2', function (assert) {
- assert.expect(3);
-
- this.data.partner.onchanges = {
- turtles: function (obj) {
- obj.int_field = obj.turtles.length;
- },
- };
-
- this.data.partner.records[0].turtles = [];
-
- 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_widget[name="int_field"]').val(), "10",
- "int_field should start with value 10");
-
- form.$('.o_field_x2many_list_row_add a').click();
-
- assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "10",
- "int_field should still be 10 (no onchange, because line is not valid)");
-
- // fill turtle_int field
- form.$('.o_field_widget[name="turtle_int"]').val("1").trigger('input');
-
- assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "1",
- "int_field should be 1 (onchange triggered, because line is now valid)");
-
- form.destroy();
- });
-
- QUnit.test('one2many list editable: add new line before onchange returns', function (assert) {
- // If the user adds a new row (with a required field with onchange), selects
- // a value for that field, then adds another row before the onchange returns,
- // the editable list must wait for the onchange to return before trying to
- // unselect the first row, otherwise it will be detected as invalid.
- assert.expect(7);
-
- this.data.turtle.onchanges = {
- turtle_trululu: function () {},
- };
-
- var def;
- 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;
- },
- });
-
- // add a first line but hold the onchange back
- form.$('.o_field_x2many_list_row_add a').click();
- assert.strictEqual(form.$('.o_data_row').length, 1,
- "should have created the first row immediately");
- def = $.Deferred();
- form.$('.o_field_many2one input').click();
- form.$('.o_field_many2one input').autocomplete('widget').find('a').first().click();
-
- // try to add a second line and check that it is correctly waiting
- // for the onchange to return
- form.$('.o_field_x2many_list_row_add a').click();
- assert.strictEqual($('.modal').length, 0, "no modal should be displayed");
- assert.strictEqual($('.o_field_invalid').length, 0,
- "no field should be marked as invalid");
- assert.strictEqual(form.$('.o_data_row').length, 1,
- "should wait for the onchange to create the second row");
- assert.ok(form.$('.o_data_row').hasClass('o_selected_row'),
- "first row should still be in edition");
-
- // resolve the onchange def
- def.resolve();
- assert.strictEqual(form.$('.o_data_row').length, 2,
- "second row should now have been created");
- assert.notOk(form.$('.o_data_row:first').hasClass('o_selected_row'),
- "first row should no more be in edition");
-
- form.destroy();
- });
-
- QUnit.test('editable list: multiple clicks on Add an item do not create invalid rows', function (assert) {
- assert.expect(3);
-
- this.data.turtle.onchanges = {
- turtle_trululu: function () {},
- };
-
- var def;
- 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;
- },
- });
-
- // click twice to add a new line
- def = $.Deferred();
- form.$('.o_field_x2many_list_row_add a').click();
- form.$('.o_field_x2many_list_row_add a').click();
- assert.strictEqual(form.$('.o_data_row').length, 0,
- "no row should have been created yet (waiting for the onchange)");
-
- // resolve the onchange def
- def.resolve();
- assert.strictEqual(form.$('.o_data_row').length, 1,
- "only one row should have been created");
- assert.ok(form.$('.o_data_row:first').hasClass('o_selected_row'),
- "the created row should be in edition");
-
- form.destroy();
- });
-
- QUnit.test('editable list: value reset by an onchange', function (assert) {
- // this test reproduces a subtle behavior that may occur in a form view:
- // the user adds a record in a one2many field, and directly clicks on a
- // datetime field of the form view which has an onchange, which totally
- // overrides the value of the one2many (commands 5 and 0). The handler
- // that switches the edited row to readonly is then called after the
- // new value of the one2many field is applied (the one returned by the
- // onchange), so the row that must go to readonly doesn't exist anymore.
- assert.expect(2);
-
- this.data.partner.onchanges = {
- datetime: function (obj) {
- obj.turtles = [[5], [0, 0, {display_name: 'new'}]];
- },
- };
-
- var def;
- 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;
- },
- });
-
- // trigger the two onchanges
- form.$('.o_field_x2many_list_row_add a').click();
- form.$('.o_data_row .o_field_widget').val('a name').trigger('input');
- def = $.Deferred();
- form.$('.o_datepicker_input').click(); // focusout o2m and set value to today
- var dateTimeVal = fieldUtils.format.datetime(moment(), {timezone: false});
- form.$('.o_datepicker_input').val(dateTimeVal).trigger('change');
-
- // resolve the onchange def
- def.resolve();
-
- assert.strictEqual(form.$('.o_data_row').length, 1,
- "should have one record in the o2m");
- assert.strictEqual(form.$('.o_data_row .o_data_cell').text(), 'new',
- "should be the record created by the onchange");
-
- form.destroy();
- });
-
- QUnit.test('editable list: onchange that returns a warning', function (assert) {
- assert.expect(5);
-
- this.data.turtle.onchanges = {
- display_name: function () {},
- };
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch: '
' +
- '' +
- '' +
- '' +
- '' +
- '' +
- '
',
- res_id: 1,
- mockRPC: function (route, args) {
- if (args.method === 'onchange') {
- assert.step(args.method);
- return $.when({
- value: {},
- warning: {
- title: "Warning",
- message: "You must first select a partner"
- },
- });
- }
- return this._super.apply(this, arguments);
- },
- viewOptions: {
- mode: 'edit',
- },
- intercepts: {
- warning: function () {
- assert.step('warning');
- },
- },
- });
-
- // add a line (this should trigger an onchange and a warning)
- form.$('.o_field_x2many_list_row_add a').click();
-
- // check if 'Add an item' still works (this should trigger an onchange
- // and a warning again)
- form.$('.o_field_x2many_list_row_add a').click();
-
- assert.verifySteps(['onchange', 'warning', 'onchange', 'warning']);
-
- form.destroy();
- });
-
- QUnit.test('editable list: contexts are correctly sent', function (assert) {
- assert.expect(5);
-
- this.data.partner.records[0].timmy = [12];
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch: '
',
- viewOptions: {
- mode: 'edit',
- },
- res_id: 1,
- mockRPC: function (route, args) {
- assert.step(args.method);
- return this._super.apply(this, arguments);
- },
- });
-
- form.$('input[name="foo"]').val('abcd').trigger('input');
- assert.verifySteps(['read', 'onchange']);
-
- form.destroy();
- });
-
- QUnit.test('one2many: onchange that returns unknow field in list, but not in form', function (assert) {
- assert.expect(5);
-
- this.data.partner.onchanges = {
- name: function () {},
- };
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch: '
' +
- '' +
- '' +
- '' +
- '' +
- '' +
- '
' +
- '' +
- '' +
- '
' +
- '' +
- '
',
- mockRPC: function (route, args) {
- if (args.method === 'onchange') {
- return $.when({
- value: {
- p: [[5], [0, 0, {display_name: 'new', timmy: [[5], [4, 12]]}]],
- },
- });
- }
- return this._super.apply(this, arguments);
- },
- });
-
- assert.strictEqual(form.$('.o_data_row').length, 1,
- "the one2many should contain one row");
- assert.strictEqual(form.$('.o_field_widget[name="timmy"]').length, 0,
- "timmy should not be displayed in the list view");
-
- form.$('.o_data_row td:first').click(); // open the record
-
- assert.strictEqual($('.modal .o_field_many2manytags[name="timmy"]').length, 1,
- "timmy should be displayed in the form view");
- assert.strictEqual($('.modal .o_field_many2manytags[name="timmy"] .badge').length, 1,
- "m2mtags should contain one tag");
- assert.strictEqual($('.modal .o_field_many2manytags[name="timmy"] .o_badge_text').text(),
- 'gold', "tag name should have been correctly loaded");
-
- form.destroy();
- });
-
- QUnit.test('onchange and required fields with override in arch', function (assert) {
- assert.expect(4);
-
- this.data.partner.onchanges = {
- turtles: function (obj) {}
- };
- this.data.turtle.fields.turtle_foo.required = true;
- this.data.partner.records[0].turtles = [];
-
- 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();
-
- // triggers an onchange on partner, because the new record is valid
- form.$('.o_field_x2many_list_row_add a').click();
-
- assert.verifySteps(['read', 'default_get', 'onchange']);
- form.destroy();
- });
-
- QUnit.test('onchange on a one2many containing a one2many', function (assert) {
- // the purpose of this test is to ensure that the onchange specs are
- // correctly and recursively computed
- assert.expect(1);
-
- this.data.partner.onchanges = {
- p: function () {}
- };
- var checkOnchange = false;
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch:'
' +
- '' +
- '' +
- '
' +
- '' +
- '' +
- '' +
- '' +
- '
' +
- '' +
- '
',
- mockRPC: function (route, args) {
- if (args.method === 'onchange' && checkOnchange) {
- assert.strictEqual(args.args[3]['p.p.display_name'], '',
- "onchange specs should be computed recursively");
- }
- return this._super.apply(this, arguments);
- },
- });
-
- form.$('.o_field_x2many_list_row_add a').click();
- $('.modal .o_field_x2many_list_row_add a').click();
- $('.modal .o_data_cell input').val('new record').trigger('input');
- checkOnchange = true;
- $('.modal .modal-footer .btn-primary').click(); // save (should trigger the onchange)
-
- form.destroy();
- });
-
- QUnit.test('editing tabbed one2many (editable=bottom)', function (assert) {
- assert.expect(12);
-
- this.data.partner.records[0].turtles = [];
- for (var i = 0; i < 42; i++) {
- var id = 100 + i;
- this.data.turtle.records.push({id: id, turtle_foo: 'turtle' + (id-99)});
- this.data.partner.records[0].turtles.push(id);
- }
-
- 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') {
- // delay the onchange RPC
- return $.when(def).then(_.constant(result));
- }
- return result;
- },
- });
-
- form.$('.o_field_x2many_list_row_add a').click();
- def = $.Deferred();
- form.$('.o_field_widget[name=int_field]')
- .val('44')
- .trigger('input')
- .trigger({type: 'keydown', which: $.ui.keyCode.TAB});
- def.resolve();
-
- assert.strictEqual(document.activeElement, form.$('.o_field_widget[name=qux]')[0],
- "qux field should have the focus");
-
- form.$('.o_field_many2one input').click();
- form.$('.o_field_many2one input').autocomplete('widget').find('a').first().click();
- assert.strictEqual(form.$('.o_field_many2one input').val(), 'first record',
- "the one2many field should have the expected value");
-
- form.destroy();
- });
-
- QUnit.test('checkbox in an x2many that triggers an onchange', function (assert) {
- assert.expect(1);
-
- this.data.partner.onchanges = {
- bar: function () {}
- };
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch: '
' +
- '' +
- '' +
- '' +
- '' +
- '' +
- '
',
- });
-
- form.$('.o_field_x2many_list_row_add a').click();
-
- form.$('.o_field_widget[name=bar] input').click();
- assert.notOk(form.$('.o_field_widget[name=bar] input').prop('checked'),
- "the checkbox should be unticked");
-
- form.destroy();
- });
-
- QUnit.test('one2many with default value: edit line to make it invalid', function (assert) {
- assert.expect(3);
-
- this.data.partner.fields.p.default = [
- [0, false, {foo: "coucou", int_field: 5}],
- ];
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch:'
' +
- '' +
- '' +
- '' +
- '' +
- '' +
- '' +
- '
',
- });
-
- // edit the line and enter an invalid value for int_field
- form.$('.o_data_row .o_data_cell:nth(1)').click();
- form.$('.o_field_widget[name=int_field]').val('e').trigger('input');
- form.$el.click(); // try to validate the line
-
- assert.strictEqual(form.$('.o_data_row.o_selected_row').length, 1,
- "line should not have been removed and should still be in edition");
- assert.strictEqual($('.modal').length, 1,
- "a confirmation dialog should be opened");
- assert.ok(form.$('.o_field_widget[name=int_field]').hasClass('o_field_invalid'),
- "should indicate that int_field is invalid");
-
- form.destroy();
- });
-
- QUnit.test('default value for nested one2manys (coming from onchange)', function (assert) {
- assert.expect(3);
-
- this.data.partner.onchanges.p = function (obj) {
- obj.p = [
- [5],
- [0, 0, {turtles: [[5], [4, 1]]}], // link record 1 by default
- ];
- };
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch: '
' +
- '' +
- '' +
- '' +
- '' +
- '' +
- '
',
- mockRPC: function (route, args) {
- if (args.method === 'create') {
- assert.strictEqual(args.args[0].p[0][0], 0,
- "should send a command 0 (CREATE) for p");
- assert.deepEqual(args.args[0].p[0][2], {turtles: [[4, 1, false]]},
- "should send the correct values");
- }
- return this._super.apply(this, arguments);
- },
- });
-
- assert.strictEqual(form.$('.o_data_cell').text(), '1 record',
- "should correctly display the value of the inner o2m");
-
- form.$buttons.find('.o_form_button_save').click();
-
- form.destroy();
- });
-
- QUnit.test('display correct value after validation error', function (assert) {
- assert.expect(4);
-
- this.data.partner.onchanges.turtles = function () { };
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch: '
',
- mockRPC: function (route, args) {
- if (args.method === 'onchange') {
- if (args.args[1].turtles[0][2].turtle_foo === 'pinky') {
- // we simulate a validation error. In the 'real' web client,
- // the server error will be used by the session to display
- // an error dialog. From the point of view of the basic
- // model, the deferred is just rejected.
- return $.Deferred().reject();
- }
- }
- if (args.method === 'write') {
- assert.deepEqual(args.args[1].turtles[0], [1,2,{turtle_foo: 'foo'}],
- 'should send the "good" value');
- }
- return this._super.apply(this, arguments);
- },
- viewOptions: {mode: 'edit'},
- res_id: 1,
- });
-
- assert.strictEqual(form.$('.o_data_row .o_data_cell:nth(0)').text(), 'blip',
- "initial text should be correct");
-
- // click and edit value to 'foo', which will trigger onchange
- form.$('.o_data_row .o_data_cell:nth(0)').click();
- form.$('.o_field_widget[name=turtle_foo]').val('foo').trigger('input');
- form.$el.click();
- assert.strictEqual(form.$('.o_data_row .o_data_cell:nth(0)').text(), 'foo',
- "field should have been changed to foo");
-
- // click and edit value to 'pinky', which trigger a failed onchange
- form.$('.o_data_row .o_data_cell:nth(0)').click();
- form.$('.o_field_widget[name=turtle_foo]').val('pinky').trigger('input');
- form.$el.click();
-
- assert.strictEqual(form.$('.o_data_row .o_data_cell:nth(0)').text(), 'foo',
- "turtle_foo text should now be set back to foo");
-
- // we make sure here that when we save, the values are the current
- // values displayed in the field.
- form.$buttons.find('.o_form_button_save').click();
-
- form.destroy();
- });
-
- QUnit.test('propagate context to sub views', function (assert) {
- assert.expect(5);
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch: '
',
- mockRPC: function (route, args) {
- assert.strictEqual(args.kwargs.context.flutter, 'shy',
- 'view context key should be used for every rpcs');
- return this._super.apply(this, arguments);
- },
- viewOptions: {context: {flutter: 'shy'}},
- });
- form.$('.o_field_x2many_list_row_add a').click();
- form.$('input[name="turtle_foo"]').val('pinky pie').trigger('input');
- form.$buttons.find('.o_form_button_save').click();
-
- form.destroy();
- });
-
- QUnit.test('onchange on nested one2manys', function (assert) {
- assert.expect(6);
-
- this.data.partner.onchanges.display_name = function (obj) {
- if (obj.display_name) {
- obj.p = [
- [5],
- [0, 0, {
- display_name: 'test',
- turtles: [[5], [0, 0, {display_name: 'test nested'}]],
- }],
- ];
- }
- };
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch: '
' +
- '' +
- '' +
- '' +
- '' +
- '' +
- '' +
- '
' +
- '' +
- '' +
- '' +
- '
' +
- '' +
- '' +
- '
',
- mockRPC: function (route, args) {
- if (args.method === 'create') {
- assert.strictEqual(args.args[0].p[0][0], 0,
- "should send a command 0 (CREATE) for p");
- assert.strictEqual(args.args[0].p[0][2].display_name, 'test',
- "should send the correct values");
- assert.strictEqual(args.args[0].p[0][2].turtles[0][0], 0,
- "should send a command 0 (CREATE) for turtles");
- assert.deepEqual(args.args[0].p[0][2].turtles[0][2], {display_name: 'test nested'},
- "should send the correct values");
- }
- return this._super.apply(this, arguments);
- },
- });
-
- form.$('.o_field_widget[name=display_name]').val('trigger onchange').trigger('input');
-
- assert.strictEqual(form.$('.o_data_cell').text(), 'test',
- "should have added the new row to the one2many");
-
- // open the new subrecord to check the value of the nested o2m, and to
- // ensure that it will be saved
- form.$('.o_data_cell:first').click();
- assert.strictEqual($('.modal .o_data_cell').text(), 'test nested',
- "should have added the new row to the nested one2many");
- $('.modal .modal-footer .btn-primary').click();
-
- form.$buttons.find('.o_form_button_save').click();
-
- form.destroy();
- });
-
- QUnit.test('one2many with multiple pages and sequence field', function (assert) {
- assert.expect(1);
-
- this.data.partner.records[0].turtles = [3, 2, 1];
- this.data.partner.onchanges.turtles = function () {};
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch:'
',
- res_id: 1,
- viewOptions: {
- mode: 'edit',
- },
- });
-
- // starting condition
- assert.strictEqual($('.o_data_cell').text(), "blipyopkawa");
-
- // click add a new line
- // save the record
- // check line is at the correct place
-
- var inputText = 'ninja';
- form.$('.o_field_x2many_list_row_add a').click();
- form.$('.o_input[name="turtle_foo"]').val(inputText).trigger('input');
- form.$buttons.find('.o_form_button_save').click();
-
- assert.strictEqual($('.o_data_cell').text(), "blipyopkawa" + inputText);
- form.destroy();
- });
-
- QUnit.test('one2many with sequence field, override default_get, top when inline', function (assert) {
- assert.expect(2);
-
- this.data.partner.records[0].turtles = [3, 2, 1];
-
- this.data.turtle.fields.turtle_int.default = 10;
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch:'
' +
- '' +
- '' +
- '' +
- '' +
- '' +
- '' +
- '
',
- res_id: 1,
- viewOptions: {
- mode: 'edit',
- },
- });
-
- // starting condition
- assert.strictEqual($('.o_data_cell').text(), "blipyopkawa");
-
- // click add a new line
- // save the record
- // check line is at the correct place
-
- var inputText = 'ninja';
- form.$('.o_field_x2many_list_row_add a').click();
- form.$('.o_input[name="turtle_foo"]').val(inputText).trigger('input');
- form.$buttons.find('.o_form_button_save').click();
-
- assert.strictEqual($('.o_data_cell').text(), inputText + "blipyopkawa");
- form.destroy();
- });
-
- QUnit.test('one2many with sequence field, override default_get, bottom when popup', function (assert) {
- assert.expect(3);
-
- this.data.partner.records[0].turtles = [3, 2, 1];
-
- this.data.turtle.fields.turtle_int.default = 10;
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch:'
' +
- '' +
- '' +
- '' +
- '' +
- '' +
- '
' +
- // NOTE: at some point we want to fix this in the framework so that an invisible field is not required.
- '' +
- '' +
- '
' +
- '' +
- '
',
- res_id: 1,
- viewOptions: {
- mode: 'edit',
- },
- });
-
- // starting condition
- assert.strictEqual($('.o_data_cell').text(), "blipyopkawa");
-
- // click add a new line
- // save the record
- // check line is at the correct place
-
- var inputText = 'ninja';
- $('.o_field_x2many_list_row_add a').click();
- $('.o_input[name="turtle_foo"]').val(inputText).trigger('input');
- $('.modal .modal-footer .btn-primary:first').click();
-
- assert.strictEqual($('.o_data_cell').text(), "blipyopkawa" + inputText);
-
- $('.o_form_button_save').click();
- assert.strictEqual($('.o_data_cell').text(), "blipyopkawa" + inputText);
- form.destroy();
- });
-
- QUnit.test('one2many with sequence field, override default_get, not last page', function (assert) {
- assert.expect(1);
-
- this.data.partner.records[0].turtles = [3, 2, 1];
-
- this.data.turtle.fields.turtle_int.default = 10;
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch:'
' +
- '' +
- '' +
- '' +
- '' +
- '
' +
- '' +
- '
' +
- '' +
- '
',
- res_id: 1,
- viewOptions: {
- mode: 'edit',
- },
- });
-
- // click add a new line
- // check turtle_int for new is the current max of the page
- $('.o_field_x2many_list_row_add a').click();
- assert.strictEqual($('.modal .o_input[name="turtle_int"]').val(), '10');
- form.destroy();
- });
-
- QUnit.test('one2many with sequence field, override default_get, last page', function (assert) {
- assert.expect(1);
-
- this.data.partner.records[0].turtles = [3, 2, 1];
-
- this.data.turtle.fields.turtle_int.default = 10;
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch:'
' +
- '' +
- '' +
- '' +
- '' +
- '
' +
- '' +
- '
' +
- '' +
- '
',
- res_id: 1,
- viewOptions: {
- mode: 'edit',
- },
- });
-
- // click add a new line
- // check turtle_int for new is the current max of the page +1
- $('.o_field_x2many_list_row_add a').click();
- assert.strictEqual($('.modal .o_input[name="turtle_int"]').val(), '22');
- form.destroy();
- });
-
- QUnit.test('one2many with sequence field, fetch name_get from empty list, field text', function (assert) {
- // There was a bug where a RPC would fail because no route was set.
- // The scenario is:
- // - create a new parent model, which has a one2many
- // - add at least 2 one2many lines which have:
- // - a handle field
- // - a many2one, which is not required, and we will leave it empty
- // - reorder the lines with the handle
- // -> This will call a resequence, which calls a name_get.
- // -> With the bug that would fail, if it's ok the test will pass.
-
- // This test will also make sure lists with
- // FieldText (turtle_description) can be reordered with a handle.
- // More specifically this will trigger a reset on a FieldText
- // while the field is not in editable mode.
- assert.expect(4);
-
- this.data.turtle.fields.turtle_int.default = 10;
- this.data.turtle.fields.product_id.default = 37;
- this.data.turtle.fields.not_required_product_id = {
- string: "Product",
- type: "many2one",
- relation: 'product'
- };
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch:'
',
- viewOptions: {
- mode: 'edit',
- },
- });
-
- // starting condition
- assert.strictEqual($('.o_data_cell:nth-child(2)').text(), "");
-
- var inputText1 = 'relax';
- var inputText2 = 'max';
- form.$('.o_field_x2many_list_row_add a').click();
- form.$('.o_input[name="turtle_foo"]').val(inputText1).trigger('input');
- form.$('.o_field_x2many_list_row_add a').click();
- form.$('.o_input[name="turtle_foo"]').val(inputText2).trigger('input');
- form.$('.o_field_x2many_list_row_add a').click();
-
- assert.strictEqual($('.o_data_cell:nth-child(2)').text(), inputText1 + inputText2);
-
- var $handles = form.$('.ui-sortable-handle');
-
- assert.equal($handles.length, 3, 'There should be 3 sequence handlers');
-
- testUtils.dragAndDrop($handles.eq(1),
- form.$('tbody tr').first(),
- {position: 'top'}
- );
-
- assert.strictEqual($('.o_data_cell:nth-child(2)').text(), inputText2 + inputText1);
-
- form.destroy();
- });
-
- QUnit.test('one2many with several pages, onchange and default order', function (assert) {
- // This test reproduces a specific scenario where a one2many is displayed
- // over several pages, and has a default order such that a record that
- // would normally be on page 1 is actually on another page. Moreover,
- // there is an onchange on that one2many which converts all commands 4
- // (LINK_TO) into commands 1 (UPDATE), which is standard in the ORM.
- // This test ensures that the record displayed on page 2 is never fully
- // read.
- assert.expect(8);
-
- var data = this.data;
- data.partner.records[0].turtles = [1, 2, 3];
- data.turtle.records[0].partner_ids = [1];
- data.partner.onchanges = {
- turtles: function (obj) {
- var res = _.map(obj.turtles, function (command) {
- if (command[0] === 1) { // already an UPDATE command: do nothing
- return command;
- }
- // convert LINK_TO commands to UPDATE commands
- var id = command[1];
- var record = _.findWhere(data.turtle.records, {id: id});
- return [1, id, _.pick(record, ['turtle_int', 'turtle_foo', 'partner_ids'])];
- });
- obj.turtles = [[5]].concat(res);
- },
- };
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: data,
- arch: '
',
- mockRPC: function (route, args) {
- var ids = args.method === 'read' ? ' [' + args.args[0] + ']' : '';
- assert.step(args.method + ids);
- return this._super.apply(this, arguments);
- },
- res_id: 1,
- viewOptions: {
- mode: 'edit',
- },
- });
-
- assert.strictEqual(form.$('.o_data_cell.foo').text(), 'blipkawa',
- "should display two records out of three, in the correct order");
-
- // edit turtle_int field of first row
- form.$('.o_data_cell:first').click();
- form.$('.o_field_widget[name=turtle_int]').val(3).trigger('input');
- form.$el.click(); // leave edition
-
- assert.strictEqual(form.$('.o_data_cell.foo').text(), 'blipkawa',
- "should still display the same two records");
-
- assert.verifySteps([
- 'read [1]', // main record
- 'read [1,2,3]', // one2many (turtle_foo, all records)
- 'read [2,3]', // one2many (all fields in view, records of first page)
- 'read [2,4]', // many2many inside one2many (partner_ids), first page only
- 'onchange',
- ]);
-
- form.destroy();
- });
-
- QUnit.test('new record, with one2many with more default values than limit', function (assert) {
- assert.expect(2);
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch:'
' +
- '' +
- '' +
- '' +
- '' +
- '' +
- '
',
- context: { default_turtles: [1,2,3]},
- viewOptions: {
- mode: 'edit',
- },
- });
- assert.strictEqual(form.$('.o_data_row').text(), 'yopblip',
- 'data has been properly loaded');
- form.$buttons.find('.o_form_button_save').click();
-
- assert.strictEqual(form.$('.o_data_row').text(), 'yopblip',
- 'data has been properly saved');
- form.destroy();
- });
-
- QUnit.test('add a new line after limit is reached should behave nicely', function (assert) {
- assert.expect(2);
-
- this.data.partner.records[0].turtles = [1,2,3];
-
- this.data.partner.onchanges = {
- turtles: function (obj) {
- obj.turtles = [
- [5],
- [1, 1, {turtle_foo: "yop"}],
- [1, 2, {turtle_foo: "blip"}],
- [1, 3, {turtle_foo: "kawa"}],
- [0, obj.turtles[3][2], {turtle_foo: "abc"}],
- ];
- },
- };
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch:'
' +
- '' +
- '' +
- '' +
- '' +
- '' +
- '
',
- res_id: 1,
- viewOptions: {
- mode: 'edit',
- },
- });
-
- form.$('.o_field_x2many_list_row_add a').click();
- assert.strictEqual(form.$('.o_data_row').length, 4, 'should have 4 data rows');
- form.$('.o_input[name="turtle_foo"]').val('a').trigger('input');
- assert.strictEqual(form.$('.o_data_row').length, 4,
- 'should still have 4 data rows (the limit is increased to 4)');
-
- form.destroy();
- });
-
- QUnit.test('onchange in a one2many with non inline view on an existing record', function (assert) {
- assert.expect(6);
-
- 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 () {}};
-
- this.data.partner_type.fields.partner_ids = {string: "Partner", type: "one2many", relation: 'partner'};
- this.data.partner_type.records[0].partner_ids = [1,2];
-
- var form = testUtils.createView({
- View: FormView,
- model: 'partner_type',
- data: this.data,
- arch: '
',
- archs: {
- 'partner,false,list': '' +
- '' +
- '' +
- '',
- },
- res_id: 12,
- mockRPC: function (route, args) {
- assert.step(args.method);
- return this._super.apply(this, arguments);
- },
- viewOptions: {mode: 'edit'},
- });
-
- // swap 2 lines in the one2many
- testUtils.dragAndDrop(form.$('.ui-sortable-handle:eq(1)'), form.$('tbody tr').first(),
- {position: 'top'});
- assert.verifySteps(['load_views', 'read', 'read', 'onchange', 'onchange']);
- form.destroy();
- });
-
- QUnit.test('onchange in a one2many with non inline view on a new record', function (assert) {
- assert.expect(8);
-
- this.data.turtle.onchanges = {
- display_name: function (obj) {
- if (obj.display_name) {
- obj.turtle_int = 44;
- }
- },
- };
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch: '
',
- archs: {
- 'turtle,false,list': '' +
- '' +
- '' +
- '',
- },
- mockRPC: function (route, args) {
- assert.step(args.method || route);
- return this._super.apply(this, arguments);
- },
- });
-
- // add a row and trigger the onchange
- form.$('.o_field_x2many_list_row_add a').click();
- form.$('.o_data_row .o_field_widget[name=display_name]').val('a name').trigger('input');
-
- assert.strictEqual(form.$('.o_data_row .o_field_widget[name=turtle_int]').val(), "44",
- "should have triggered the onchange");
-
- assert.verifySteps([
- 'load_views', // load sub list
- 'default_get', // main record
- 'onchange', // main record
- 'default_get', // sub record
- 'onchange', // sub record
- 'onchange', // edition of display_name of sub record
- ]);
-
- form.destroy();
- });
-
- QUnit.test('add a line, edit it and "Save & New"', function (assert) {
- assert.expect(5);
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch: '
' +
- '' +
- '' +
- '
' +
- '' +
- '
',
- });
-
- assert.strictEqual(form.$('.o_data_row').length, 0,
- "there should be no record in the relation");
-
- // add a new record
- form.$('.o_field_x2many_list_row_add a').click();
- $('.modal .o_field_widget').val('new record').trigger('input');
- $('.modal .modal-footer .btn-primary:first').click(); // Save & Close
-
- assert.strictEqual(form.$('.o_data_row .o_data_cell').text(), 'new record',
- "should display the new record");
-
- // reopen freshly added record and edit it
- form.$('.o_data_row .o_data_cell').click();
- $('.modal .o_field_widget').val('new record edited').trigger('input');
-
- // save it, and choose to directly create another record
- $('.modal .modal-footer .btn-primary:nth(1)').click(); // Save & New
-
- assert.strictEqual($('.modal').length, 1,
- "the model should still be open");
- assert.strictEqual($('.modal .o_field_widget').text(), '',
- "should have cleared the input");
-
- $('.modal .o_field_widget').val('another new record').trigger('input');
- $('.modal .modal-footer .btn-primary:first').click(); // Save & Close
-
- assert.strictEqual(form.$('.o_data_row .o_data_cell').text(),
- 'new record editedanother new record', "should display the two records");
-
- form.destroy();
- });
-
- QUnit.test('o2m add a line custom control create editable', function (assert) {
- assert.expect(5);
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch:
- '
',
- });
-
- // new controls correctly added
- var $td = form.$('.o_field_x2many_list_row_add');
- assert.strictEqual($td.length, 1);
- assert.strictEqual($td.closest('tr').find('td').length, 1);
- assert.strictEqual($td.text(), "Add foodAdd pizzaAdd pasta");
-
- // click add food
- // check it's empty
- form.$('.o_field_x2many_list_row_add a:eq(0)').click();
- $('.modal .modal-footer .btn-primary:first').click();
- assert.strictEqual($('.o_data_cell').text(), "");
-
- // click add pizza
- // save the modal
- // check it's pizza
- form.$('.o_field_x2many_list_row_add a:eq(1)').click();
- $('.modal .modal-footer .btn-primary:first').click();
- assert.strictEqual($('.o_data_cell').text(), "pizza");
-
- // click add pasta
- // save the whole record
- // check it's pizzapasta
- form.$('.o_field_x2many_list_row_add a:eq(2)').click();
- $('.modal .modal-footer .btn-primary:first').click();
- assert.strictEqual($('.o_data_cell').text(), "pizzapasta");
-
- form.destroy();
- });
-
- QUnit.test('o2m add a line custom control create align with handle', function (assert) {
- assert.expect(3);
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch:
- '
' +
- '' +
- '' +
- '' +
- '' +
- '' +
- '
',
- });
-
- // controls correctly added, at one column offset when handle is present
- var $tr = form.$('.o_field_x2many_list_row_add').closest('tr');
- assert.strictEqual($tr.find('td').length, 2);
- assert.strictEqual($tr.find('td:eq(0)').text(), "");
- assert.strictEqual($tr.find('td:eq(1)').text(), "Add a line");
-
- form.destroy();
- });
-
- QUnit.test('one2many form view with action button', function (assert) {
- // once the action button is clicked, the record is reloaded (via the
- // on_close handler, executed because the python method does not return
- // any action, or an ir.action.act_window_close) ; this test ensures that
- // it reloads the fields of the opened view (i.e. the form in this case).
- // See https://github.com/odoo/odoo/issues/24189
- assert.expect(7);
-
- var data = this.data;
- data.partner.records[0].p = [2];
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: data,
- res_id: 1,
- arch: '
' +
- '' +
- '' +
- '
' +
- '' +
- '' +
- '
' +
- '' +
- '
',
- archs: {
- 'partner_type,false,list': '',
- },
- intercepts: {
- execute_action: function (ev) {
- data.partner.records[1].display_name = 'new name';
- data.partner.records[1].timmy = [12];
- ev.data.on_closed();
- },
- },
- viewOptions: {
- mode: 'edit',
- },
- });
-
- assert.strictEqual(form.$('.o_data_row').length, 1,
- "there should be one record in the one2many");
- assert.strictEqual(form.$('.o_data_cell').text(), 'second record',
- "initial display_name of o2m record should be correct");
-
- // open one2many record in form view
- form.$('.o_data_cell:first').click();
- assert.strictEqual($('.modal .o_form_view').length, 1,
- "should have opened the form view in a dialog");
- assert.strictEqual($('.modal .o_form_view .o_data_row').length, 0,
- "there should be no record in the many2many");
-
- // click on the action button
- $('.modal .o_form_view button').click();
- assert.strictEqual($('.modal .o_data_row').length, 1,
- "fields in the o2m form view should have been read");
- assert.strictEqual($('.modal .o_data_cell').text(), 'gold',
- "many2many subrecord should have been fetched");
-
- // save the dialog
- $('.modal .modal-footer .btn-primary').click();
-
- assert.strictEqual(form.$('.o_data_cell').text(), 'new name',
- "fields in the o2m list view should have been read as well");
-
- form.destroy();
- });
-
- QUnit.test('onchange affecting inline unopened list view', function (assert) {
- // when we got onchange result for fields of record that were not
- // already available because they were in a inline view not already
- // opened, in a given configuration the change were applied ignoring
- // existing data, thus a line of a one2many field inside a one2many
- // field could be duplicated unexplectedly
- assert.expect(5);
-
- var numUserOnchange = 0;
-
- this.data.user.onchanges = {
- partner_ids: function (obj) {
- if (numUserOnchange === 0) {
- // simulate proper server onchange after save of modal with new record
- obj.partner_ids = [
- [5],
- [1, 1, {
- display_name: 'first record',
- turtles: [
- [5],
- [1, 2, {'display_name': 'donatello'}],
- ],
- }],
- [1, 2, {
- display_name: 'second record',
- turtles: [
- [5],
- obj.partner_ids[1][2].turtles[0],
- ],
- }],
- ];
- }
- numUserOnchange++;
- },
- };
-
- var form = createView({
- View: FormView,
- model: 'user',
- data: this.data,
- arch: '
' +
- '' +
- '
'+
- '' +
- '' +
- '' +
- '' +
- '' +
- '
' +
- '' +
- '' +
- '' +
- '' +
- '
',
- res_id: 17,
- });
-
- // add a turtle on second partner
- form.$buttons.find('.o_form_button_edit').click();
- form.$('.o_data_row:eq(1)').click();
- $('.modal .o_field_x2many_list_row_add a').click();
- $('.modal input[name="display_name"]').val('michelangelo').change();
- $('.modal .btn-primary').click();
- // open first partner so changes from previous action are applied
- form.$('.o_data_row:eq(0)').click();
- $('.modal .btn-primary').click();
- form.$buttons.find('.o_form_button_save').click();
-
- assert.strictEqual(numUserOnchange, 2,
- 'there should 2 and only 2 onchange from closing the partner modal');
-
- form.$('.o_data_row:eq(0)').click();
- assert.strictEqual($('.modal .o_data_row').length, 1,
- 'only 1 turtle for first partner');
- assert.strictEqual($('.modal .o_data_row').text(), 'donatello',
- 'first partner turtle is donatello');
- $('.modal .o_form_button_cancel').click();
-
- form.$('.o_data_row:eq(1)').click();
- assert.strictEqual($('.modal .o_data_row').length, 1,
- 'only 1 turtle for second partner');
- assert.strictEqual($('.modal .o_data_row').text(), 'michelangelo',
- 'second partner turtle is michelangelo');
- $('.modal .o_form_button_cancel').click();
-
- form.destroy();
- });
-
- QUnit.test('click on URL should not open the record', 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,
- });
-
- form.$('.o_email_cell a').click();
- assert.strictEqual($('.modal .o_form_view').length, 0,
- 'click should not open the modal');
-
- form.$('.o_url_cell a').click();
- assert.strictEqual($('.modal .o_form_view').length, 0,
- 'click should not open the modal');
- form.destroy();
- });
-
- QUnit.test('create and edit on m2o in o2m, and press ESCAPE', function (assert) {
- assert.expect(4);
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch: '
' +
- '' +
- '' +
- '' +
- '' +
- '' +
- '
',
- archs: {
- 'partner,false,form': '
',
- },
- });
-
- form.$('.o_field_x2many_list_row_add a').click();
-
- assert.strictEqual(form.$('.o_selected_row').length, 1,
- "should have create a new row in edition");
-
- var $input = form.$('.o_field_widget[name="turtle_trululu"] input');
- $input.click();
- $input.autocomplete('widget').find('.o_m2o_dropdown_option').focus().click();
-
- assert.strictEqual($('.modal .o_form_view').length, 1,
- "should have opened a form view in a dialog");
-
- $('.modal .o_form_view .o_field_widget[name=display_name]').trigger($.Event('keydown', {
- which: $.ui.keyCode.ESCAPE,
- keyCode: $.ui.keyCode.ESCAPE,
- }));
-
- assert.strictEqual($('.modal .o_form_view').length, 0,
- "should have closed the dialog");
- assert.strictEqual(form.$('.o_selected_row').length, 1,
- "new row should still be present");
-
- form.destroy();
- });
-
- QUnit.test('one2many add a line should not crash if orderedResIDs is not set', function (assert) {
- // There is no assertion, the code will just crash before the bugfix.
- assert.expect(0);
-
- this.data.partner.records[0].turtles = [];
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch:'
',
- viewOptions: {
- mode: 'edit',
- },
- intercepts: {
- execute_action: function (event) {
- event.data.on_fail();
- },
- },
- });
-
- $('button[name="action_invoice_open"]').click();
- form.$('.o_field_x2many_list_row_add a').click();
- form.destroy();
- });
-
- QUnit.test('one2many shortcut tab should not crash when there is no input widget', function (assert) {
- assert.expect(2);
-
- // create a one2many view which has no input (only 1 textarea in this case)
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch:'
' +
- '' +
- '' +
- '' +
- '' +
- '' +
- '
',
- res_id: 1,
- viewOptions: {
- mode: 'edit',
- },
- });
-
- // add a row, fill it, then trigger the tab shortcut
- form.$('.o_field_x2many_list_row_add a').click();
- form.$('.o_input[name="turtle_foo"]')
- .val('ninja')
- .trigger('input')
- .trigger($.Event('keydown', {
- which: $.ui.keyCode.TAB,
- keyCode: $.ui.keyCode.TAB,
- }));
-
- assert.strictEqual(form.$('.o_field_text').text(), 'blipninja',
- 'current line should be saved');
- assert.strictEqual(form.$('textarea.o_field_text').length, 1,
- 'new line should be created');
-
- form.destroy();
- });
-
- QUnit.test('one2many with onchange, required field, shortcut enter', function (assert) {
- assert.expect(5);
-
- this.data.turtle.onchanges = {
- turtle_foo: function () {},
- };
-
- var def;
- 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;
- },
- // simulate what happens in the client:
- // the new value isn't notified directly to the model
- fieldDebounce: 5000,
- });
-
- var value = "hello";
-
- // add a new line
- form.$('.o_field_x2many_list_row_add a').click();
-
- // we want to add a delay to simulate an onchange
- def = $.Deferred();
-
- // write something in the field
- var $input = form.$('input[name="turtle_foo"]');
- $input.val(value).trigger('input');
-
- // trigger keydown ENTER
- $input.trigger($.Event('keydown', {
- keyCode: $.ui.keyCode.ENTER,
- which: $.ui.keyCode.ENTER,
- }));
-
- // check that nothing changed before the onchange finished
- assert.strictEqual($input.val(), value, "input content shouldn't change");
- assert.strictEqual(form.$('.o_data_row').length, 1,
- "should still contain only one row");
-
- // unlock onchange
- def.resolve();
-
- // check the current line is added with the correct content and a new line is editable
- assert.strictEqual(form.$('td.o_data_cell').text(), value);
- assert.strictEqual(form.$('input[name="turtle_foo"]').val(), '');
- assert.strictEqual(form.$('.o_data_row').length, 2,
- "should now contain two rows");
-
- form.destroy();
- });
-
- QUnit.test('no deadlock when leaving a one2many line with uncommitted changes', function (assert) {
- // Before unselecting a o2m line, field widgets are asked to commit their changes (new values
- // that they wouldn't have sent to the model yet). This test is added alongside a bug fix
- // ensuring that we don't end up in a deadlock when a widget actually has some changes to
- // commit at that moment.
- assert.expect(9);
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch: '
' +
- '' +
- '' +
- '' +
- '' +
- '' +
- '
',
- mockRPC: function (route, args) {
- assert.step(args.method);
- return this._super.apply(this, arguments);
- },
- // we set a fieldDebounce to precisely mock the behavior of the webclient: changes are
- // not sent to the model at keystrokes, but when the input is left
- fieldDebounce: 5000,
- });
-
- form.$('.o_field_x2many_list_row_add a').click();
-
- form.$('input').val('some foo value').trigger('input');
-
- // click to add a second row to unselect the current one, then save
- form.$('.o_field_x2many_list_row_add a').click();
- form.$buttons.find('.o_form_button_save').click();
-
- assert.strictEqual(form.$('.o_form_readonly').length, 1,
- "form view should be in readonly");
- assert.strictEqual(form.$('.o_data_row').text(), 'some foo value',
- "foo field should have correct value");
- assert.verifySteps([
- 'default_get', // main record
- 'default_get', // line 1
- 'default_get', // line 2
- 'create',
- 'read', // main record
- 'read', // line 1
- ]);
-
- form.destroy();
- });
-
- 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: '
',
- 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:'
',
- 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:'
',
- },
- 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: '
',
- 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.module('FieldStatus');
QUnit.test('static statusbar widget on many2one field', function (assert) {
@@ -13544,90 +2126,6 @@ QUnit.module('relational_fields', {
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);
diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml
index dab795eae6f..cc5c9b30a65 100644
--- a/addons/web/views/webclient_templates.xml
+++ b/addons/web/views/webclient_templates.xml
@@ -511,6 +511,9 @@
+
+
+