odoo.define('web.view_dialogs_tests', function (require) { "use strict"; var dialogs = require('web.view_dialogs'); var ListController = require('web.ListController'); var testUtils = require('web.test_utils'); var Widget = require('web.Widget'); var FormView = require('web.FormView'); const { browser } = require('@web/core/browser/browser'); const { patchWithCleanup } = require('@web/../tests/helpers/utils'); const cpHelpers = require('@web/../tests/search/helpers'); var createView = testUtils.createView; async function createParent(params) { var widget = new Widget(); params.server = await testUtils.mock.addMockEnvironment(widget, params); return widget; } QUnit.module('LegacyViews', { beforeEach: function () { this.data = { partner: { fields: { display_name: { string: "Displayed name", type: "char" }, foo: {string: "Foo", type: 'char'}, bar: {string: "Bar", type: "boolean"}, instrument: {string: 'Instruments', type: 'many2one', relation: 'instrument'}, }, records: [ {id: 1, foo: 'blip', display_name: 'blipblip', bar: true}, {id: 2, foo: 'ta tata ta ta', display_name: 'macgyver', bar: false}, {id: 3, foo: 'piou piou', display_name: "Jack O'Neill", bar: true}, ], }, instrument: { fields: { name: {string: "name", type: "char"}, badassery: {string: 'level', type: 'many2many', relation: 'badassery', domain: [['level', '=', 'Awsome']]}, }, }, badassery: { fields: { level: {string: 'level', type: "char"}, }, records: [ {id: 1, level: 'Awsome'}, ], }, product: { fields : { name: {string: "name", type: "char" }, partner : {string: 'Doors', type: 'one2many', relation: 'partner'}, }, records: [ {id: 1, name: 'The end'}, ], }, }; }, }, function () { QUnit.module('ViewDialog (legacy)'); QUnit.test('formviewdialog buttons in footer are positioned properly', async function (assert) { assert.expect(2); var parent = await createParent({ data: this.data, archs: { 'partner,false,form': '
' + '' + '' + '' + '' + '
', }, }); new dialogs.FormViewDialog(parent, { res_model: 'partner', res_id: 1, }).open(); await testUtils.nextTick(); assert.notOk($('.modal-body button').length, "should not have any button in body"); assert.strictEqual($('.modal-footer button').length, 1, "should have only one button in footer"); parent.destroy(); }); QUnit.test('formviewdialog buttons in footer are not duplicated', async function (assert) { assert.expect(2); this.data.partner.fields.poney_ids = {string: "Poneys", type: "one2many", relation: 'partner'}; this.data.partner.records[0].poney_ids = []; var parent = await createParent({ data: this.data, archs: { 'partner,false,form': '
' + '' + '' + '
', }, }); new dialogs.FormViewDialog(parent, { res_model: 'partner', res_id: 1, }).open(); await testUtils.nextTick(); assert.strictEqual($('.modal button.btn-primary').length, 1, "should have 1 buttons in modal"); await testUtils.dom.click($('.o_field_x2many_list_row_add a')); await testUtils.fields.triggerKeydown($('input.o_input'), 'escape'); assert.strictEqual($('.modal button.btn-primary').length, 1, "should still have 1 buttons in modal"); parent.destroy(); }); QUnit.test('SelectCreateDialog use domain, group_by and search default', async function (assert) { assert.expect(3); var search = 0; var parent = await createParent({ data: this.data, archs: { 'partner,false,list': '' + '' + '' + '', 'partner,false,search': '' + '' + '' + '' + '' + '', }, mockRPC: function (route, args) { if (args.method === 'web_read_group') { assert.deepEqual(args.kwargs, { context: { search_default_foo: "piou", search_default_groupby_bar: true, }, domain: ["&", ["display_name", "like", "a"], "&", ["display_name", "ilike", "piou"], ["foo", "ilike", "piou"]], fields: ["display_name", "foo", "bar"], groupby: ["bar"], orderby: '', lazy: true, limit: 80, }, "should search with the complete domain (domain + search), and group by 'bar'"); } if (search === 0 && route === '/web/dataset/search_read') { search++; assert.deepEqual(args, { context: { search_default_foo: "piou", search_default_groupby_bar: true, bin_size: true }, // not part of the test, may change domain: ["&", ["display_name", "like", "a"], "&", ["display_name", "ilike", "piou"], ["foo", "ilike", "piou"]], fields: ["display_name", "foo"], model: "partner", limit: 80, sort: "" }, "should search with the complete domain (domain + search)"); } else if (search === 1 && route === '/web/dataset/search_read') { assert.deepEqual(args, { context: { search_default_foo: "piou", search_default_groupby_bar: true, bin_size: true }, // not part of the test, may change domain: [["display_name", "like", "a"]], fields: ["display_name", "foo"], model: "partner", limit: 80, sort: "" }, "should search with the domain"); } return this._super.apply(this, arguments); }, }); new dialogs.SelectCreateDialog(parent, { no_create: true, readonly: true, res_model: 'partner', domain: [['display_name', 'like', 'a']], context: { search_default_groupby_bar: true, search_default_foo: 'piou', }, }).open(); await testUtils.nextTick(); const modal = document.body.querySelector(".modal"); await cpHelpers.removeFacet(modal, "Bar"); await cpHelpers.removeFacet(modal); parent.destroy(); }); QUnit.test('SelectCreateDialog correctly evaluates domains', async function (assert) { assert.expect(1); var parent = await createParent({ data: this.data, archs: { 'partner,false,list': '' + '' + '' + '', 'partner,false,search': '' + '' + '', }, mockRPC: function (route, args) { if (route === '/web/dataset/search_read') { assert.deepEqual(args.domain, [['id', '=', 2]], "should have correctly evaluated the domain"); } return this._super.apply(this, arguments); }, session: { user_context: {uid: 2}, }, }); new dialogs.SelectCreateDialog(parent, { no_create: true, readonly: true, res_model: 'partner', domain: "[['id', '=', uid]]", }).open(); await testUtils.nextTick(); parent.destroy(); }); QUnit.test('SelectCreateDialog list view in readonly', async function (assert) { assert.expect(1); var parent = await createParent({ data: this.data, archs: { 'partner,false,list': '' + '' + '' + '', 'partner,false,search': '' }, }); var dialog; new dialogs.SelectCreateDialog(parent, { res_model: 'partner', }).open().then(function (result) { dialog = result; }); await testUtils.nextTick(); // click on the first row to see if the list is editable await testUtils.dom.click(dialog.$('.o_legacy_list_view tbody tr:first td:not(.o_list_record_selector):first')); assert.equal(dialog.$('.o_legacy_list_view tbody tr:first td:not(.o_list_record_selector):first input').length, 0, "list view should not be editable in a SelectCreateDialog"); parent.destroy(); }); QUnit.test('SelectCreateDialog cascade x2many in create mode', async function (assert) { assert.expect(5); var form = await createView({ View: FormView, model: 'product', data: this.data, arch: '
' + '' + '' + '' + '' + '' + '' + '' + '', res_id: 1, archs: { 'partner,false,form': '
' + '' + '' + '', 'instrument,false,form': '
'+ ''+ '' + ''+ ''+ '' + '' + '', 'badassery,false,list': ''+ ''+ '', 'badassery,false,search': ''+ ''+ '', }, mockRPC: function(route, args) { if (route === '/web/dataset/call_kw/partner/get_formview_id') { return Promise.resolve(false); } if (route === '/web/dataset/call_kw/instrument/get_formview_id') { return Promise.resolve(false); } if (route === '/web/dataset/call_kw/instrument/create') { assert.deepEqual(args.args, [{badassery: [[6, false, [1]]], name: "ABC"}], 'The method create should have been called with the right arguments'); return Promise.resolve(false); } return this._super(route, args); }, }); await testUtils.form.clickEdit(form); await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); await testUtils.fields.many2one.createAndEdit("instrument"); var $modal = $('.modal-lg'); assert.equal($modal.length, 1, 'There should be one modal'); await testUtils.dom.click($modal.find('.o_field_x2many_list_row_add a')); var $modals = $('.modal-lg'); assert.equal($modals.length, 2, 'There should be two modals'); var $second_modal = $modals.not($modal); await testUtils.dom.click($second_modal.find('.o_list_table.table.table-sm.table-striped.o_list_table_ungrouped .o_data_row input[type=checkbox]')); await testUtils.dom.click($second_modal.find('.o_select_button')); $modal = $('.modal-lg'); assert.equal($modal.length, 1, 'There should be one modal'); assert.equal($modal.find('.o_data_cell').text(), 'Awsome', 'There should be one item in the list of the modal'); await testUtils.dom.click($modal.find('.btn.btn-primary')); form.destroy(); }); QUnit.test('Form dialog and subview with _view_ref contexts', async function (assert) { assert.expect(2); this.data.instrument.records = [{id: 1, name: 'Tromblon', badassery: [1]}]; this.data.partner.records[0].instrument = 1; var form = await createView({ View: FormView, model: 'partner', data: this.data, arch: '
' + '' + '' + '', res_id: 1, archs: { 'instrument,false,form': '
'+ ''+ '' + '', 'badassery,false,list': ''+ ''+ '', }, viewOptions: { mode: 'edit', }, mockRPC: function(route, args) { if (args.method === 'get_formview_id') { return Promise.resolve(false); } return this._super(route, args); }, interceptsPropagate: { load_views: function (ev) { var evaluatedContext = ev.data.context; if (ev.data.modelName === 'instrument') { assert.deepEqual(evaluatedContext, {tree_view_ref: 'some_tree_view'}, 'The correct _view_ref should have been sent to the server, first time'); } if (ev.data.modelName === 'badassery') { assert.deepEqual(evaluatedContext, { base_model_name: 'instrument', tree_view_ref: 'some_other_tree_view', }, 'The correct _view_ref should have been sent to the server for the subview'); } }, }, }); await testUtils.dom.click(form.$('.o_field_widget[name="instrument"] button.o_external_button')); form.destroy(); }); QUnit.test("Form dialog replaces the context with _createContext method when specified", async function (assert) { assert.expect(5); const parent = await createParent({ data: this.data, archs: { "partner,false,form": `
`, }, mockRPC: function (route, args) { if (args.method === "create") { assert.step(JSON.stringify(args.kwargs.context)); } return this._super(route, args); }, }); new dialogs.FormViewDialog(parent, { res_model: "partner", context: { answer: 42 }, _createContext: () => ({ dolphin: 64 }), }).open(); await testUtils.nextTick(); assert.notOk($(".modal-body button").length, "should not have any button in body"); assert.strictEqual($(".modal-footer button").length, 3, "should have 3 buttons in footer"); await testUtils.dom.click($(".modal-footer button:contains(Save & New)")); await testUtils.dom.click($(".modal-footer button:contains(Save & New)")); assert.verifySteps(['{"answer":42}', '{"dolphin":64}']); parent.destroy(); }); QUnit.test("Form dialog keeps full context when no _createContext is specified", async function (assert) { assert.expect(5); const parent = await createParent({ data: this.data, archs: { "partner,false,form": `
`, }, mockRPC: function (route, args) { if (args.method === "create") { assert.step(JSON.stringify(args.kwargs.context)); } return this._super(route, args); }, }); new dialogs.FormViewDialog(parent, { res_model: "partner", context: { answer: 42 } }).open(); await testUtils.nextTick(); assert.notOk($(".modal-body button").length, "should not have any button in body"); assert.strictEqual($(".modal-footer button").length, 3, "should have 3 buttons in footer"); await testUtils.dom.click($(".modal-footer button:contains(Save & New)")); await testUtils.dom.click($(".modal-footer button:contains(Save & New)")); assert.verifySteps(['{"answer":42}', '{"answer":42}']); parent.destroy(); }); QUnit.test('SelectCreateDialog: save current search', async function (assert) { assert.expect(4); testUtils.mock.patch(ListController, { getOwnedQueryParams: function () { return { context: { shouldBeInFilterContext: true, }, }; }, }); // save favorite needs this patchWithCleanup(browser, { setTimeout: (fn) => fn(), }); var parent = await createParent({ data: this.data, archs: { 'partner,false,list': '' + '' + '', 'partner,false,search': '' + '' + '', }, env: { dataManager: { create_filter: function (filter) { assert.strictEqual(filter.domain, `[("bar", "=", True)]`, "should save the correct domain"); const expectedContext = { group_by: [], // default groupby is an empty list shouldBeInFilterContext: true, }; assert.deepEqual(filter.context, expectedContext, "should save the correct context"); }, } }, }); var dialog; new dialogs.SelectCreateDialog(parent, { context: {shouldNotBeInFilterContext: false}, res_model: 'partner', }).open().then(function (result) { dialog = result; }); await testUtils.nextTick(); assert.containsN(dialog, '.o_data_row', 3, "should contain 3 records"); // filter on bar const modal = document.body.querySelector(".modal"); await cpHelpers.toggleFilterMenu(modal); await cpHelpers.toggleMenuItem(modal, "Bar"); assert.containsN(dialog, '.o_data_row', 2, "should contain 2 records"); // save filter await cpHelpers.toggleFavoriteMenu(modal); await cpHelpers.toggleSaveFavorite(modal); await cpHelpers.editFavoriteName(modal, "some name"); await cpHelpers.saveFavorite(modal); testUtils.mock.unpatch(ListController); parent.destroy(); }); QUnit.test('SelectCreateDialog calls on_selected with every record matching the domain', async function (assert) { assert.expect(3); const parent = await createParent({ data: this.data, archs: { 'partner,false,list': '' + '' + '' + '', 'partner,false,search': '' + '' + '', }, session: {}, }); new dialogs.SelectCreateDialog(parent, { res_model: 'partner', on_selected: function(records) { assert.equal(records.length, 3); assert.strictEqual(records.map((r) => r.display_name).toString(), "blipblip,macgyver,Jack O'Neill"); assert.strictEqual(records.map((r) => r.id).toString(), "1,2,3"); } }).open(); await testUtils.nextTick(); await testUtils.dom.click($('thead .o_list_record_selector input')); await testUtils.dom.click($('.o_list_selection_box .o_list_select_domain')); await testUtils.dom.click($('.modal .o_select_button')); parent.destroy(); }); QUnit.test('SelectCreateDialog calls on_selected with every record matching without selecting a domain', async function (assert) { assert.expect(3); const parent = await createParent({ data: this.data, archs: { 'partner,false,list': '' + '' + '' + '', 'partner,false,search': '' + '' + '', }, session: {}, }); new dialogs.SelectCreateDialog(parent, { res_model: 'partner', on_selected: function(records) { assert.equal(records.length, 2); assert.strictEqual(records.map((r) => r.display_name).toString(), "blipblip,macgyver"); assert.strictEqual(records.map((r) => r.id).toString(), "1,2"); } }).open(); await testUtils.nextTick(); await testUtils.dom.click($('thead .o_list_record_selector input')); await testUtils.dom.click($('.o_list_selection_box ')); await testUtils.dom.click($('.modal .o_select_button')); parent.destroy(); }); QUnit.test('propagate can_create onto the search popup o2m', async function (assert) { assert.expect(4); this.data.instrument.records = [ {id: 1, name: 'Tromblon1'}, {id: 2, name: 'Tromblon2'}, {id: 3, name: 'Tromblon3'}, {id: 4, name: 'Tromblon4'}, {id: 5, name: 'Tromblon5'}, {id: 6, name: 'Tromblon6'}, {id: 7, name: 'Tromblon7'}, {id: 8, name: 'Tromblon8'}, ]; var form = await createView({ View: FormView, model: 'partner', data: this.data, arch: '
' + '' + '' + '', res_id: 1, archs: { 'instrument,false,list': ''+ ''+ '', 'instrument,false,search': ''+ ''+ '', }, viewOptions: { mode: 'edit', }, mockRPC: function(route, args) { if (args.method === 'get_formview_id') { return Promise.resolve(false); } return this._super(route, args); }, }); await testUtils.fields.many2one.clickOpenDropdown('instrument'); assert.containsNone(form, '.ui-autocomplete a:contains(Start typing...)'); await testUtils.fields.editInput(form.el.querySelector(".o_field_many2one[name=instrument] input"), "a"); assert.containsNone(form, '.ui-autocomplete a:contains(Create and Edit)'); await testUtils.fields.editInput(form.el.querySelector(".o_field_many2one[name=instrument] input"), ""); await testUtils.fields.many2one.clickItem('instrument', 'Search More...'); var $modal = $('.modal-dialog.modal-lg'); assert.strictEqual($modal.length, 1, 'Modal present'); assert.strictEqual($modal.find('.modal-footer button').text(), "Cancel", 'Only the cancel button is present in modal'); form.destroy(); }); QUnit.test('formviewdialog is not closed when button handlers return a rejected promise', async function (assert) { assert.expect(3); this.data.partner.fields.poney_ids = { string: "Poneys", type: "one2many", relation: 'partner' }; this.data.partner.records[0].poney_ids = []; var reject = true; var parent = await createParent({ data: this.data, archs: { 'partner,false,form': '
' + '' + '
', }, }); new dialogs.FormViewDialog(parent, { res_model: 'partner', res_id: 1, buttons: [{ text: 'Click me !', classes: "btn-secondary o_form_button_magic", close: true, click: function () { return reject ? Promise.reject() : Promise.resolve(); }, }], }).open(); await testUtils.nextTick(); assert.strictEqual($('.modal').length, 1, "should have a modal displayed"); await testUtils.dom.click($('.modal .o_form_button_magic')); assert.strictEqual($('.modal').length, 1, "modal should still be opened"); reject = false; await testUtils.dom.click($('.modal .o_form_button_magic')); assert.strictEqual($('.modal').length, 0, "modal should be closed"); parent.destroy(); }); }); });