odoo.define('web.relational_fields_tests', function (require) { "use strict"; var FormView = require('web.FormView'); var ListView = require('web.ListView'); var testUtils = require('web.test_utils'); var createView = testUtils.createView; QUnit.module('fields', {}, function () { QUnit.module('relational_fields', { beforeEach: function () { this.data = { partner: { fields: { display_name: { string: "Displayed name", type: "char" }, foo: {string: "Foo", type: "char", default: "My little Foo Value"}, bar: {string: "Bar", type: "boolean", default: true}, int_field: {string: "int_field", type: "integer", sortable: true}, qux: {string: "Qux", type: "float", digits: [16,1] }, p: {string: "one2many field", type: "one2many", relation: 'partner', relation_field: 'trululu'}, trululu: {string: "Trululu", type: "many2one", relation: 'partner'}, timmy: { string: "pokemon", type: "many2many", relation: 'partner_type'}, product_id: {string: "Product", type: "many2one", relation: 'product'}, color: { type: "selection", selection: [['red', "Red"], ['black', "Black"]], default: 'red', }, }, records: [{ id: 1, display_name: "first record", bar: true, foo: "yop", int_field: 10, qux: 0.44, p: [], timmy: [], trululu: 4, }, { id: 2, display_name: "second record", bar: true, foo: "blip", int_field: 9, qux: 13, p: [], timmy: [], trululu: 1, }, { id: 4, display_name: "aaa", bar: false, }], onchanges: {}, }, product: { fields: { name: {string: "Product Name", type: "char"} }, records: [{ id: 37, display_name: "xphone", }, { id: 41, display_name: "xpad", }] }, partner_type: { fields: { name: {string: "Partner Type", type: "char"}, color: {string: "Color index", type: "int"}, }, records: [ {id: 12, display_name: "gold", color: 2}, {id: 14, display_name: "silver", color: 5}, ] }, }; } }, function () { QUnit.module('FieldMany2One'); QUnit.test('many2ones in form views', function (assert) { assert.expect(4); 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"); }); testUtils.intercept(form, 'load_views', function (event) { // temporarily prevent the fields_view from being loaded (to prevent a crash) // TODO: specify a fields_view and test that we can edit the record in the dialog event.stopPropagation(); }); 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) // TODO: test that we can edit the record in the dialog, and that the value is correctly // updated on close }); QUnit.test('many2one readonly fields with option "no_open"', function (assert) { assert.expect(1); var form = createView({ View: FormView, model: 'partner', data: this.data, arch:'
' + '' + '' + '' + '' + '' + '
', res_id: 1, }); assert.strictEqual(form.$('a.o_form_uri').length, 0, "should not have an anchor"); }); 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._toEditMode(); var $dropdown = form.$('.o_form_field_many2one input').autocomplete('widget'); form.$('.o_form_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_form_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_form_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_form_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_form_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_form_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"); }); 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"); }); // 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('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(); }); 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"); }); 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_form_field_many2one input').click(); form.$('.o_form_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'); }); QUnit.module('FieldOne2Many'); 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, vel: 2, }); assert.strictEqual(form.$('td.o_list_record_selector').length, 0, "embedded one2many should not have a selector"); assert.ok(!form.$('.o_form_field_x2many_list_row_add').length, "embedded one2many should not be editable"); assert.ok(!form.$('td.o_list_record_delete').length, "embedded one2many records should not have a trash icon"); form.$buttons.find('.o_form_button_edit').click(); assert.ok(form.$('.o_form_field_x2many_list_row_add').length, "embedded one2many should now be editable"); assert.strictEqual(form.$('.o_form_field_x2many_list_row_add').attr('colspan'), "2", "should have colspan 2 (one for field foo, one for being below trash icon)"); assert.ok(form.$('td.o_list_record_delete').length, "embedded one2many records should have a trash icon"); }); QUnit.test('rendering with embedded one2many', function (assert) { assert.expect(2); this.data.partner.records[0].p = [2]; var form = createView({ View: FormView, model: 'partner', data: this.data, arch:'
' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
', res_id: 1, }); assert.strictEqual(form.$('th:contains(Foo)').length, 1, "embedded one2many should have a column titled according to foo"); assert.strictEqual(form.$('td:contains(blip)').length, 1, "embedded one2many should have a cell with relational value"); }); QUnit.test('embedded one2many with widget', function (assert) { assert.expect(1); this.data.partner.records[0].p = [2]; var form = createView({ View: FormView, model: 'partner', data: this.data, arch:'
' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
', res_id: 1, }); assert.strictEqual(form.$('span.o_row_handle').length, 1, "should have 1 handles"); }); 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, but that is already in cache so only 1 RPC (the read for the // main record) should have been done form.pager.previous(); assert.strictEqual(count, 5, 'one RPC 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, 6, '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 // (only one RPC should be done, to read the record 2) form.pager.next(); assert.strictEqual(count, 7, 'one RPC 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 o2m records should // be in cache form.pager.previous(); assert.strictEqual(count, 8, 'one RPC should have been done'); // the read of record 1 form.$('.o_x2m_control_panel .o_pager_next').click(); assert.strictEqual(count, 8, 'no more 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'); }); QUnit.test('sorting one2many fields', function (assert) { assert.expect(4); this.data.partner.fields.foo.sortable = true; this.data.partner.records.push({id: 23, foo: "abc"}); this.data.partner.records.push({id: 24, foo: "xyz"}); this.data.partner.records.push({id: 25, foo: "def"}); this.data.partner.records[0].p = [23,24,25]; var rpcCount = 0; var form = createView({ View: FormView, model: 'partner', data: this.data, arch: '
' + '' + '' + '' + '' + '' + '
', res_id: 1, mockRPC: function () { rpcCount++; return this._super.apply(this, arguments); }, }); rpcCount = 0; assert.ok(form.$('table tbody tr:eq(2) td:contains(def)').length, "the 3rd record is the one with 'def' value"); form.renderer._render = function () { throw "should not render the whole form"; }; form.$('table thead th:contains(Foo)').click(); assert.strictEqual(rpcCount, 0, 'sort should be in memory, no extra RPCs should have been done'); assert.ok(form.$('table tbody tr:eq(2) td:contains(xyz)').length, "the 3rd record is the one with 'xyz' value"); form.$('table thead th:contains(Foo)').click(); assert.ok(form.$('table tbody tr:eq(2) td:contains(abc)').length, "the 3rd record is the one with 'abc' value"); }); QUnit.test('one2many list field edition', function (assert) { assert.expect(5); 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().hasClass('o_edit_mode'), "first row of o2m should be in edition"); form.$('.o_field_one2many tbody td').first().find('input').val("new value").trigger('input'); // FIXME: this doesn"t work for now as the x2many fields are reset on field changed // assert.ok(form.$('.o_field_one2many tbody td').first().hasClass('o_edit_mode'), // "first row of o2m should still be in edition"); // // leave o2m edition // form.$el.click(); // assert.ok(!form.$('.o_field_one2many tbody td').first().hasClass('o_edit_mode'), // "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 discarding 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 actually test that the save works, because the relational // data isn't reloaded when clicking on save (it doesn't work for now actually) assert.strictEqual(form.$('.o_field_one2many tbody td').first().text(), 'new value', "display name of first record in o2m list should be 'new value'"); }); 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_form_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_form_field_x2many_list_row_add').length, '"Add an item" link should not be available in readonly'); }); QUnit.test('one2many list: deleting one record', function (assert) { assert.expect(5); 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 (route === '/web/dataset/call_kw/partner/write') { var commands = args.args[1].p; assert.strictEqual(commands.length, 2, 'should have generated two 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] === 2, 'should have generated the command 2 (DELETE) with id 2'); } return this._super.apply(this, arguments); }, }); form.$buttons.find('.o_form_button_edit').click(); assert.strictEqual(form.$('td.o_list_record_delete span').length, 2, "should have 2 delete buttons"); form.$('td.o_list_record_delete span').first().click(); assert.strictEqual(form.$('td.o_list_record_delete span').length, 1, "should have 1 delete button (a 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 }); QUnit.test('one2many kanban: edition', function (assert) { assert.expect(13); this.data.partner.records[0].p = [2]; var form = createView({ View: FormView, model: 'partner', data: this.data, arch: '
' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '', res_id: 1, }); assert.ok(!form.$('.o_kanban_view .delete_icon').length, 'delete icon should not be visible in readonly'); assert.ok(!form.$('.o_form_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').text(), 'second record', '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_form_field_one2many .o-kanban-button-new').length, '"Create" button should be visible in edit'); // edit existing subrecord form.$('.oe_kanban_global_click').click(); $('.modal .o_form_view .o_form_input').val('new name').trigger('input'); $('.modal .modal-footer .btn-primary').click(); // save assert.strictEqual(form.$('.o_kanban_record span').text(), 'new name', 'value of subrecord should have been updated'); // create a new subrecord form.$('.o-kanban-button-new').click(); $('.modal .o_form_view .o_form_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 .o_form_input').val('new subrecord 2').trigger('input'); $('.modal .modal-footer .btn-primary:nth(1)').click(); // save and new $('.modal .o_form_view .o_form_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.$('.o_kanban_view .delete_icon:first()').click(); 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').text(), 'new subrecord 3', 'the remaining subrecord should be "new subrecord 3"'); }); QUnit.test('one2many kanban: create action disabled', function (assert) { assert.expect(3); this.data.partner.records[0].p = [4]; var form = createView({ View: FormView, model: 'partner', data: this.data, arch:'
' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '', res_id: 1, }); assert.ok(!form.$('.o-kanban-button-new').length, '"Add" button should not be available in readonly'); form.$buttons.find('.o_form_button_edit').click(); assert.ok(!form.$('.o-kanban-button-new').length, '"Add" button should not be available in edit'); assert.ok(form.$('.o_kanban_view .delete_icon').length, 'delete icon should be visible in edit'); }); QUnit.test('one2many list (non editable): edition', function (assert) { assert.expect(9); 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_list_record_delete').length, 'delete icon should not be visible in readonly'); assert.ok(!form.$('.o_form_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_delete').length, 'delete icon should be visible in edit'); assert.ok(form.$('.o_form_field_x2many_list_row_add').length, '"Add an item" should not visible in edit'); // edit existing subrecord form.$('.o_list_view tbody tr:first()').click(); $('.modal .o_form_view .o_form_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'); // create new subrecords // TODO when 'Add an item' will be implemented // delete subrecords form.$('.o_list_record_delete:first()').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(), 'aaa', 'the remaining subrecord should be "aaa"'); }); 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_form_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_form_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 .o_form_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 }); 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'"); }); 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"); }); QUnit.test('one2many list (editable): readonly domain is evaluated', function (assert) { assert.expect(2); this.data.partner.records[0].p = [2, 4]; this.data.partner.records[1].product_id = false; this.data.partner.records[2].product_id = 37; var form = createView({ View: FormView, model: 'partner', data: this.data, arch: '
' + '' + '' + '' + '' + '' + '
', res_id: 1, }); form.$buttons.find('.o_form_button_edit').click(); assert.ok(form.$('.o_list_view tbody tr:eq(0) td:first').hasClass('o_readonly'), "first record should have display_name in readonly mode"); assert.notOk(form.$('.o_list_view tbody tr:eq(1) td:first').hasClass('o_readonly'), "second record should not have display_name in readonly mode"); }); QUnit.test('pager of one2many field in new record', function (assert) { assert.expect(2); this.data.partner.records[0].p = []; var form = createView({ View: FormView, model: 'partner', data: this.data, arch: '
' + '' + '' + '' + '' + '' + '
', archs: { 'partner,false,form': '
', }, }); assert.ok(!form.$('.o_x2m_control_panel .o_cp_pager div').is(':visible'), 'o2m pager should be hidden'); // click to create a subrecord form.$('tbody td.o_form_field_x2many_list_row_add a').click(); $('.modal .o_form_input').val('new record').trigger('input'); $('.modal .modal-footer button:eq(0)').click(); // save and close assert.ok(!form.$('.o_x2m_control_panel .o_cp_pager div').is(':visible'), 'o2m pager should be hidden'); }); QUnit.test('one2many list with a many2one', function (assert) { assert.expect(5); this.data.partner.records[0].p = [2]; this.data.partner.records[1].product_id = 37; this.data.partner.onchanges.p = function (obj) { obj.p = [ [5], // delete all [1, 2, {product_id: [37, "xphone"]}], // update existing record [0, 0, {product_id: [41, "xpad"]}] ]; // }; var form = createView({ View: FormView, model: 'partner', data: this.data, arch: '
' + '' + '' + '' + '' + '' + '
', res_id: 1, archs: { 'partner,false,form': '
', }, mockRPC: function (route, args) { if (args.method === 'onchange') { assert.deepEqual(args.args[1].p, [[4, 2, false], [0, false, {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_form_field_x2many_list_row_add a').click(); $('.modal .o_form_field_many2one input').click(); var $dropdown = $('.modal .o_form_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"); }); QUnit.test('one2many list with inline form view', function (assert) { assert.expect(2); 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, false, { int_field: 123, product_id: 41 }]]); } return this._super(route, args); }, }); form.$buttons.find('.o_form_button_edit').click(); form.$('tbody td.o_form_field_x2many_list_row_add a').click(); // write in the many2one field $('.modal .o_form_field_many2one input').click(); var $dropdown = $('.modal .o_form_field_many2one input').autocomplete('widget'); $dropdown.find('li:eq(1) a').mouseenter(); $dropdown.find('li:eq(1) a').click(); // write in the integer field $('.modal .modal-body input.o_field_widget').val('123').trigger('input'); $('.modal .modal-footer button:eq(0)').click(); // save and close 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(); }); QUnit.test('one2many list edition, some basic functionality', function (assert) { assert.expect(3); this.data.partner.fields.foo.default = false; var form = createView({ View: FormView, model: 'partner', data: this.data, arch: '
' + '' + '' + '' + '' + '' + '
', res_id: 1, }); form.$buttons.find('.o_form_button_edit').click(); form.$('tbody td.o_form_field_x2many_list_row_add a').click(); assert.strictEqual(form.$('td input.o_field_widget.o_form_input').length, 1, "should have created a row in edit mode"); form.$('td input.o_field_widget.o_form_input').val('a').trigger('input'); assert.strictEqual(form.$('td input.o_field_widget.o_form_input').length, 1, "should not have unselected the row after edition"); form.$('td input.o_field_widget.o_form_input').val('abc').trigger('input'); form.$buttons.find('.o_form_button_save').click(); assert.strictEqual(form.$('td:contains(abc)').length, 1, "should have a row with the correct value"); }); QUnit.test('one2many list, the context is properly evaluated and sent', function (assert) { assert.expect(2); var form = createView({ View: FormView, model: 'partner', data: this.data, arch: '
' + '' + '' + '' + '' + '' + '' + '', res_id: 1, mockRPC: function (route, args) { if (args.method === 'default_get') { var context = args.kwargs.context; assert.strictEqual(context.hello, "world"); assert.strictEqual(context.abc, 10); } return this._super.apply(this, arguments); }, }); form.$buttons.find('.o_form_button_edit').click(); form.$('tbody td.o_form_field_x2many_list_row_add a').click(); }); 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_form_field_x2many_list_row_add a').click(); }); 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_form_field_x2many_list_row_add a').click(); }); QUnit.test('parent data is properly sent on an onchange rpc, new record', function (assert) { assert.expect(1); this.data.partner.onchanges = {bar: function () {}}; var form = createView({ View: FormView, model: 'partner', data: this.data, arch: '
' + '' + '' + '' + '' + '' + '' + '', mockRPC: function (route, args) { if (args.method === 'onchange') { var fieldValues = args.args[1]; assert.strictEqual(fieldValues.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_form_field_x2many_list_row_add a').click(); }); QUnit.module('FieldMany2Many'); QUnit.test('many2many kanban: edition', function (assert) { assert.expect(28); 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, 21].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_form_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_form_field_many2many .o-kanban-button-new').length, '"Add" button should be visible in edit'); // edit existing subrecord form.$('.oe_kanban_global_click:first()').click(); $('.modal .o_form_view .o_form_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_form_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_form_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_form_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) .modal-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) .delete_icon').click(); 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'); // save the record form.$buttons.find('.o_form_button_save').click(); }); QUnit.test('many2many kanban: create action disabled', function (assert) { assert.expect(4); this.data.partner.records[0].timmy = [12, 14]; var form = createView({ View: FormView, model: 'partner', data: this.data, arch:'
' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '', archs: { 'partner_type,false,list': '', 'partner_type,false,search': '' + '' + '', }, res_id: 1, session: {user_context: {}}, manualDestroy: true, }); 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(9); this.data.partner.records[0].timmy = [12, 14]; this.data.partner_type.fields.float_field = {string: 'Float', type: 'float'}; var form = createView({ View: FormView, model: 'partner', data: this.data, arch: '
' + '' + '' + '' + '' + '' + '' + '' + '' + '', res_id: 1, }); assert.ok(!form.$('.o_list_record_delete').length, 'delete icon should not be visible in readonly'); assert.ok(!form.$('.o_form_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_delete').length, 'delete icon should be visible in edit'); assert.ok(form.$('.o_form_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 .o_form_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'); // create new subrecords // TODO when 'Add an item' will be implemented // delete subrecords form.$('.o_list_record_delete:first()').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(), 'silver', 'the remaining subrecord should be "aaa"'); }); QUnit.test('many2many list (editable): edition', function (assert) { assert.expect(11); this.data.partner.records[0].timmy = [12, 14]; this.data.partner_type.fields.float_field = {string: 'Float', type: 'float'}; var form = createView({ View: FormView, model: 'partner', data: this.data, arch: '
' + '' + '' + '' + '' + '' + '' + '' + '' + '', res_id: 1, }); assert.ok(!form.$('.o_list_record_delete').length, 'delete icon should not be visible in readonly'); assert.ok(!form.$('.o_form_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_delete').length, 'delete icon should be visible in edit'); assert.ok(form.$('.o_form_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 .o_form_input:first()').val('new name').trigger('input'); // FIXME: this doesn"t work for now as the x2many fields are reset on field changed // form.$('.o_list_view tbody td:nth(1)').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'); // add new subrecords // TODO when 'Add an item' will be implemented // delete subrecords form.$('.o_list_record_delete:first()').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(), 'silver', 'the remaining subrecord should be "aaa"'); }); 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_form_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_form_field_x2many_list_row_add').length, '"Add an item" link should be available in edit'); // TODO: click on add an item and test that the select create dialog has no 'Create' button // form.$('.o_form_field_x2many_list_row_add').click(); }); QUnit.module('FieldStatus'); QUnit.test('static statusbar widget on many2one field', function (assert) { assert.expect(5); this.data.partner.fields.trululu.domain = "[('bar', '=', True)]"; this.data.partner.records.forEach(function (record) { record.bar = true; }); var count = 0; var nb_fields_fetched; var form = createView({ View: FormView, model: 'partner', data: this.data, arch:'
' + '' + // the following field seem useless, but its presence was the // cause of a crash when evaluating the field domain. '' + '', mockRPC: function (route, args) { if (route === '/web/dataset/search_read') { count++; nb_fields_fetched = args.fields.length; } return this._super.apply(this, arguments); }, res_id: 1, }); assert.strictEqual(count, 1, 'once search_read should have been done to fetch the relational values'); assert.strictEqual(nb_fields_fetched, 1, 'search_read should only fetch field id'); assert.strictEqual(form.$('.o_statusbar_status button').length, 3, "should have 3 status"); assert.strictEqual(form.$('.o_statusbar_status button.disabled').length, 3, "all status should be disabled"); assert.ok(form.$('.o_statusbar_status button[data-value="4"]').hasClass('btn-primary'), "selected status should be btn-primary"); // TODO: add a test to check that we can change the status }); QUnit.test('clickable statusbar widget on many2one field', function (assert) { assert.expect(2); var form = createView({ View: FormView, model: 'partner', data: this.data, arch:'
' + '' + '', res_id: 1, }); assert.ok(form.$('.o_statusbar_status button[data-value="4"]').hasClass('btn-primary disabled'), "selected status should be btn-primary and disabled"); assert.strictEqual(form.$('.o_statusbar_status button.btn-default:not(.disabled)').length, 2, "other status should be btn-default and not disabled"); }); QUnit.test('statusbar with no status', function (assert) { assert.expect(2); this.data.product.records = []; var form = createView({ View: FormView, model: 'partner', data: this.data, arch:'
' + '' + '', res_id: 1, }); assert.ok(form.$('.o_statusbar_status').hasClass('o_form_field_empty'), 'statusbar widget should have class o_form_field_empty'); assert.strictEqual(form.$('.o_statusbar_status').children().length, 0, 'statusbar widget should be empty'); }); QUnit.module('FieldSelection'); QUnit.test('widget selection in a list view', function (assert) { assert.expect(3); this.data.partner.records.forEach(function (r) { r.color = 'red'; }); var list = createView({ View: ListView, model: 'partner', data: this.data, arch: '' + '' + '', }); assert.strictEqual(list.$('td:contains(Red)').length, 3, "should have 3 rows with correct value"); list.$('td:contains(Red):first').click(); var $td = list.$('tbody tr.o_selected_row td:not(.o_list_record_selector)'); assert.strictEqual($td.find('select').length, 1, "td should have a child 'select'"); assert.strictEqual($td.contents().length, 1, "select tag should be only child of td"); }); QUnit.test('widget selection on a many2one field', function (assert) { assert.expect(7); var count = 0; var form = createView({ View: FormView, model: 'partner', data: this.data, arch: '
' + '' + '', res_id: 1, mockRPC: function (route, args) { count++; return this._super(route, args); }, }); assert.ok(!form.$('select').length, "should not have a select tag in dom"); form.$buttons.find('.o_form_button_edit').click(); assert.ok(form.$('select').length, "should have a select tag in dom"); assert.ok(form.$('option:contains(xphone)').length, "should have fetched xphone option"); assert.ok(form.$('option:contains(xpad)').length, "should have fetched xpad option"); assert.strictEqual(form.$('select').val(), "false", "should not have any value"); form.$('select').val(37).trigger('input'); assert.strictEqual(form.$('select').val(), "37", "should have a value of xphone"); count = 0; form.reload(); assert.strictEqual(count, 1, "should not reload product_id relation"); }); QUnit.module('FieldMany2ManyTags'); QUnit.test('fieldmany2many tags: rendering and edition', function (assert) { assert.expect(17); this.data.partner.records[0].timmy = [12, 14]; this.data.partner_type.records.push({id: 13, display_name: "red", color: 8}); 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].timmy; assert.strictEqual(commands.length, 1, "should have generated one command"); assert.strictEqual(commands[0][0], 6, "generated command should be REPLACE WITH"); assert.ok(_.isEqual(_.sortBy(commands[0][2], _.identity.bind(_)), [12, 13]), "new value should be [12, 13]"); } return this._super.apply(this, arguments); }, }); assert.strictEqual(form.$('.o_form_field_many2manytags > span').length, 2, "should contain 2 tags"); assert.ok(form.$('span:contains(gold)').length, 'should have fetched and rendered gold partner tag'); assert.ok(form.$('span:contains(silver)').length, 'should have fetched and rendered silver partner tag'); assert.strictEqual(form.$('span:first()').data('color'), 2, 'should have correctly fetched the color'); form.$buttons.find('.o_form_button_edit').click(); assert.strictEqual(form.$('.o_form_field_many2manytags > span').length, 2, "should still contain 2 tags in edit mode"); assert.ok(form.$('.o_tag_color_2 .o_badge_text:contains(gold)').length, 'first tag should still contain "gold" and be color 2 in edit mode'); assert.strictEqual(form.$('.o_form_field_many2manytags .o_delete').length, 2, "tags should contain a delete button"); // add an other existing tag var $input = form.$('.o_form_field_many2manytags input'); $input.click(); // opens the dropdown assert.strictEqual($input.autocomplete('widget').find('li').length, 1, "autocomplete dropdown should have 1 entry"); assert.strictEqual($input.autocomplete('widget').find('li a:contains("red")').length, 1, "autocomplete dropdown should contain 'red'"); $input.autocomplete('widget').find('li').click(); // add 'red' assert.strictEqual(form.$('.o_form_field_many2manytags > span').length, 3, "should contain 3 tags"); assert.ok(form.$('.o_form_field_many2manytags > span:contains("red")').length, "should contain newly added tag 'red'"); assert.ok(form.$('.o_form_field_many2manytags > span[data-color=8]:contains("red")').length, "should have fetched the color of added tag"); // remove tag with id 14 form.$('.o_form_field_many2manytags span[data-id=14] .o_delete').click(); assert.strictEqual(form.$('.o_form_field_many2manytags > span').length, 2, "should contain 2 tags"); assert.ok(!form.$('.o_form_field_many2manytags > span:contains("silver")').length, "should not contain tag 'silver' anymore"); // save the record (should do the write RPC with the correct commands) form.$buttons.find('.o_form_button_save').click(); // TODO: it would be nice to test the behaviors of the autocomplete dropdown // (like refining the research, creating new tags...), but ui-autocomplete // makes it difficult to test }); QUnit.test('fieldmany2many tags in a new record', function (assert) { assert.expect(7); var form = createView({ View: FormView, model: 'partner', data: this.data, arch:'
' + '' + '', mockRPC: function (route, args) { if (route === '/web/dataset/call_kw/partner/create') { var commands = args.args[0].timmy; assert.strictEqual(commands.length, 1, "should have generated one command"); assert.strictEqual(commands[0][0], 6, "generated command should be REPLACE WITH"); assert.ok(_.isEqual(commands[0][2], [12]), "new value should be [12]"); } return this._super.apply(this, arguments); } }); assert.ok(form.$('.o_form_view').hasClass('o_form_editable'), "form should be in edit mode"); var $input = form.$('.o_form_field_many2manytags input'); $input.click(); // opens the dropdown assert.strictEqual($input.autocomplete('widget').find('li').length, 3, "autocomplete dropdown should have 3 entries (2 values + 'Search and Edit...')"); $input.autocomplete('widget').find('li:first()').click(); // adds a tag assert.strictEqual(form.$('.o_form_field_many2manytags > span').length, 1, "should contain 1 tag"); assert.ok(form.$('.o_form_field_many2manytags > span:contains("gold")').length, "should contain newly added tag 'gold'"); // save the record (should do the write RPC with the correct commands) form.$buttons.find('.o_form_button_save').click(); }); QUnit.test('fieldmany2many tags: update color', function (assert) { assert.expect(2); this.data.partner.records[0].timmy = [12, 14]; var form = createView({ View: FormView, model: 'partner', data: this.data, arch:'
' + '' + '', res_id: 1, }); // Update the color in readonly form.$('span:first()').click(); $('.o_colorpicker span[data-color="1"]').trigger('mousedown'); // choose color 1 assert.strictEqual(form.$('span:first()').data('color'), 1, 'should have correctly updated the color (in readonly)'); // Update the color in edit form.$buttons.find('.o_form_button_edit').click(); form.$('span:first()').click(); $('.o_colorpicker span[data-color="6"]').trigger('mousedown'); // choose color 6 assert.strictEqual(form.$('span:first()').data('color'), 6, 'should have correctly updated the color (in edit)'); }); QUnit.module('FieldRadio'); QUnit.test('fieldradio widget on a many2one in a new record', function (assert) { assert.expect(6); var form = createView({ View: FormView, model: 'partner', data: this.data, arch: '
' + '' + '', }); assert.ok(form.$('div.o_radio_item').length, "should have rendered outer div"); assert.strictEqual(form.$('input.o_form_radio').length, 2, "should have 2 possible choices"); assert.ok(form.$('label.o_form_label:contains(xphone)').length, "one of them should be xphone"); assert.strictEqual(form.$('input:checked').length, 0, "none of the input should be checked"); form.$("input.o_form_radio:first").click(); assert.strictEqual(form.$('input:checked').length, 1, "one of the input should be checked"); form.$buttons.find('.o_form_button_save').click(); var newRecord = _.last(this.data.partner.records); assert.strictEqual(newRecord.product_id, 37, "should have saved record with correct value"); }); QUnit.test('fieldradio widget on a selection in a new record', function (assert) { assert.expect(4); var form = createView({ View: FormView, model: 'partner', data: this.data, arch: '
' + '' + '', }); assert.ok(form.$('div.o_radio_item').length, "should have rendered outer div"); assert.strictEqual(form.$('input.o_form_radio').length, 2, "should have 2 possible choices"); assert.ok(form.$('label.o_form_label:contains(Red)').length, "one of them should be Red"); // click on 2nd option form.$("input.o_form_radio").eq(1).click(); form.$buttons.find('.o_form_button_save').click(); var newRecord = _.last(this.data.partner.records); assert.strictEqual(newRecord.color, 'black', "should have saved record with correct value"); }); QUnit.module('FieldMany2ManyCheckBoxes'); QUnit.test('widget many2many_checkboxes', function (assert) { assert.expect(6); this.data.partner.records[0].timmy = [12]; var form = createView({ View: FormView, model: 'partner', data: this.data, arch:'
' + '' + '
', res_id: 1, }); assert.strictEqual(form.$('div.o_form_field div.o_checkbox').length, 2, "should have fetched and displayed the 2 values of the many2many"); assert.ok(form.$('div.o_form_field div.o_checkbox input').eq(0).prop('checked'), "first checkbox should be checked"); assert.notOk(form.$('div.o_form_field div.o_checkbox input').eq(1).prop('checked'), "second checkbox should not be checked"); assert.ok(form.$('div.o_form_field div.o_checkbox input').prop('disabled'), "the checkboxes should be disabled"); form.$buttons.find('.o_form_button_edit').click(); assert.notOk(form.$('div.o_form_field div.o_checkbox input').prop('disabled'), "the checkboxes should not be disabled"); form.$('div.o_form_field div.o_checkbox input').eq(1).click(); form.$buttons.find('.o_form_button_save').click(); assert.deepEqual(this.data.partner.records[0].timmy, [12, 14], "should have added the second element to the many2many"); }); }); }); });