summaryrefslogtreecommitdiff
path: root/addons/web/static/tests
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-10-09 02:08:17 +0200
committerChristophe Simonis <chs@odoo.com>2019-10-09 02:08:17 +0200
commitd74b4518052066446e2a1975a91ce53c745ca75a (patch)
treec08180f88a512d15e37fbec628931f02b3825f65 /addons/web/static/tests
parentec162dd0cb5622ab6e56db1a63dab5f531a96f4a (diff)
parentf4105eb9c7010f8693e5d2fd20c0bd9d9cc2e483 (diff)
[MERGE] forward port branch 13.0 up to f4105eb9c70
Diffstat (limited to 'addons/web/static/tests')
-rw-r--r--addons/web/static/tests/chrome/action_manager_tests.js39
-rw-r--r--addons/web/static/tests/fields/basic_fields_tests.js213
-rw-r--r--addons/web/static/tests/fields/field_utils_tests.js34
-rw-r--r--addons/web/static/tests/fields/relational_fields/field_many2one_tests.js4
-rw-r--r--addons/web/static/tests/fields/relational_fields/field_one2many_tests.js36
-rw-r--r--addons/web/static/tests/fields/relational_fields_mobile_tests.js66
-rw-r--r--addons/web/static/tests/fields/relational_fields_tests.js43
-rw-r--r--addons/web/static/tests/helpers/test_utils_create.js1
-rw-r--r--addons/web/static/tests/helpers/test_utils_dom.js4
-rw-r--r--addons/web/static/tests/views/calendar_tests.js145
-rw-r--r--addons/web/static/tests/views/control_panel_tests.js79
-rw-r--r--addons/web/static/tests/views/kanban_tests.js36
-rw-r--r--addons/web/static/tests/views/list_tests.js783
-rw-r--r--addons/web/static/tests/views/search_view_tests.js9
-rw-r--r--addons/web/static/tests/widgets/data_export_tests.js116
15 files changed, 1322 insertions, 286 deletions
diff --git a/addons/web/static/tests/chrome/action_manager_tests.js b/addons/web/static/tests/chrome/action_manager_tests.js
index 002a52f572f..b878e642b4f 100644
--- a/addons/web/static/tests/chrome/action_manager_tests.js
+++ b/addons/web/static/tests/chrome/action_manager_tests.js
@@ -4227,6 +4227,45 @@ QUnit.module('ActionManager', {
actionManager.destroy();
});
+
+ QUnit.test('Call twice clearUncommittedChanges in a row does not display twice the discard warning', async function (assert) {
+ assert.expect(4);
+
+ var actionManager = await createActionManager({
+ actions: this.actions,
+ archs: this.archs,
+ data: this.data,
+ intercepts: {
+ clear_uncommitted_changes: function () {
+ actionManager.clearUncommittedChanges();
+ },
+ },
+ });
+
+ // execute an action and edit existing record
+ await actionManager.doAction(3);
+
+ await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first'));
+ assert.containsOnce(actionManager, '.o_form_view.o_form_readonly');
+
+ await testUtils.dom.click($('.o_control_panel .o_form_button_edit'));
+ assert.containsOnce(actionManager, '.o_form_view.o_form_editable');
+
+ await testUtils.fields.editInput(actionManager.$('input[name=foo]'), 'val');
+ actionManager.trigger_up('clear_uncommitted_changes');
+ await testUtils.nextTick();
+
+ assert.containsOnce($('body'), '.modal'); // confirm discard dialog
+ // confirm discard changes
+ await testUtils.dom.click($('.modal .modal-footer .btn-primary'));
+
+ actionManager.trigger_up('clear_uncommitted_changes');
+ await testUtils.nextTick();
+
+ assert.containsNone($('body'), '.modal');
+
+ actionManager.destroy();
+ });
});
});
diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js
index aa86ff925e4..c4bfc8a4341 100644
--- a/addons/web/static/tests/fields/basic_fields_tests.js
+++ b/addons/web/static/tests/fields/basic_fields_tests.js
@@ -511,6 +511,42 @@ QUnit.module('basic_fields', {
form.destroy();
});
+ QUnit.test('float field in list view no widget', async function (assert) {
+ assert.expect(5);
+
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<sheet>' +
+ '<field name="qux" digits="[5,3]"/>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 2,
+ });
+
+ assert.doesNotHaveClass(form.$('.o_field_widget'), 'o_field_empty',
+ 'Float field should be considered set for value 0.');
+ assert.strictEqual(form.$('.o_field_widget').first().text(), '0.000',
+ 'The value should be displayed properly.');
+
+ await testUtils.form.clickEdit(form);
+ assert.strictEqual(form.$('input[name=qux]').val(), '0.000',
+ 'The value should be rendered with correct precision.');
+
+ await testUtils.fields.editInput(form.$('input[name=qux]'), '108.2458938598598');
+ assert.strictEqual(form.$('input[name=qux]').val(), '108.2458938598598',
+ 'The value should not be formated yet.');
+
+ await testUtils.fields.editInput(form.$('input[name=qux]'), '18.8958938598598');
+ await testUtils.form.clickSave(form);
+ assert.strictEqual(form.$('.o_field_widget').first().text(), '18.896',
+ 'The new value should be rounded properly.');
+
+ form.destroy();
+ });
+
QUnit.test('float field in form view', async function (assert) {
assert.expect(5);
@@ -1073,7 +1109,7 @@ QUnit.module('basic_fields', {
});
QUnit.test('char field translatable', async function (assert) {
- assert.expect(4);
+ assert.expect(12);
this.data.partner.fields.foo.translate = true;
@@ -1092,6 +1128,9 @@ QUnit.module('basic_fields', {
'</sheet>' +
'</form>',
res_id: 1,
+ session: {
+ user_context: {lang: 'en_US'},
+ },
mockRPC: function (route, args) {
if (route === "/web/dataset/call_button" && args.method === 'translate_fields') {
assert.deepEqual(args.args, ["partner",1,"foo"], 'should call "call_button" route');
@@ -1101,7 +1140,18 @@ QUnit.module('basic_fields', {
});
}
if (route === "/web/dataset/call_kw/res.lang/get_installed") {
- return Promise.resolve([["en_US"], ["fr_BE"]]);
+ return Promise.resolve([["en_US", "English"], ["fr_BE", "French (Belgium)"]]);
+ }
+ if (args.method === "search_read" && args.model == "ir.translation") {
+ return Promise.resolve([
+ {lang: 'en_US', src: 'yop', value: 'yop', id: 42},
+ {lang: 'fr_BE', src: 'yop', value: 'valeur français', id: 43}
+ ]);
+ }
+ if (args.method === "write" && args.model == "ir.translation") {
+ assert.deepEqual(args.args[1], {value: "english value"},
+ "the new translation value should be written");
+ return Promise.resolve();
}
return this._super.apply(this, arguments);
},
@@ -1114,6 +1164,117 @@ QUnit.module('basic_fields', {
await testUtils.nextTick();
assert.containsOnce($(document), '.modal', 'a translate modal should be visible');
+ assert.containsN($('.modal .o_translation_dialog'), '.translation', 2,
+ 'two rows should be visible');
+
+ var $enField = $('.modal .o_translation_dialog .translation:first() input');
+ assert.strictEqual($enField.val(), 'yop',
+ 'English translation should be filled');
+ assert.strictEqual($('.modal .o_translation_dialog .translation:last() input').val(), 'valeur français',
+ 'French translation should be filled');
+
+ await testUtils.fields.editInput($enField, "english value");
+ await testUtils.dom.click($('.modal button.btn-primary')); // save
+ await testUtils.nextTick();
+
+ var $foo = form.$('input[type="text"].o_field_char');
+ assert.strictEqual($foo.val(), "english value",
+ "the new translation was not transfered to modified record");
+
+ await testUtils.fields.editInput($foo, "new english value");
+
+ await testUtils.dom.click($button);
+ await testUtils.nextTick();
+
+ assert.strictEqual($('.modal .o_translation_dialog .translation:first() input').val(), 'new english value',
+ 'Modified value should be used instead of translation');
+ assert.strictEqual($('.modal .o_translation_dialog .translation:last() input').val(), 'valeur français',
+ 'French translation should be filled');
+
+ form.destroy();
+
+ _t.database.multi_lang = multiLang;
+ });
+
+ QUnit.test('html field translatable', async function (assert) {
+ assert.expect(6);
+
+ this.data.partner.fields.foo.translate = true;
+
+ var multiLang = _t.database.multi_lang;
+ _t.database.multi_lang = true;
+
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="foo"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ session: {
+ user_context: {lang: 'en_US'},
+ },
+ mockRPC: function (route, args) {
+ if (route === "/web/dataset/call_button" && args.method === 'translate_fields') {
+ assert.deepEqual(args.args, ["partner",1,"foo"], 'should call "call_button" route');
+ return Promise.resolve({
+ domain: [],
+ context: {
+ search_default_name: 'partner,foo',
+ translation_type: 'char',
+ translation_show_src: true,
+ },
+ });
+ }
+ if (route === "/web/dataset/call_kw/res.lang/get_installed") {
+ return Promise.resolve([["en_US", "English"], ["fr_BE", "French (Belgium)"]]);
+ }
+ if (args.method === "search_read" && args.model == "ir.translation") {
+ return Promise.resolve([
+ {lang: 'en_US', src: 'first paragraph', value: 'first paragraph', id: 42},
+ {lang: 'en_US', src: 'second paragraph', value: 'second paragraph', id: 43},
+ {lang: 'fr_BE', src: 'first paragraph', value: 'premier paragraphe', id: 44},
+ {lang: 'fr_BE', src: 'second paragraph', value: 'deuxième paragraphe', id: 45},
+ ]);
+ }
+ if (args.method === "write" && args.model == "ir.translation") {
+ assert.deepEqual(args.args[1], {value: "first paragraph modified"},
+ "Wrong update on translation");
+ return Promise.resolve();
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+ await testUtils.form.clickEdit(form);
+ var $foo = form.$('input[type="text"].o_field_char');
+
+ // this will not affect the translate_fields effect until the record is
+ // saved but is set for consistency of the test
+ await testUtils.fields.editInput($foo, "<p>first paragraph</p><p>second paragraph</p>");
+
+ var $button = form.$('input[type="text"].o_field_char + .o_field_translate');
+ await testUtils.dom.click($button);
+ await testUtils.nextTick();
+
+ assert.containsOnce($(document), '.modal', 'a translate modal should be visible');
+ assert.containsN($('.modal .o_translation_dialog'), '.translation', 4,
+ 'four rows should be visible');
+
+ var $enField = $('.modal .o_translation_dialog .translation:first() input');
+ assert.strictEqual($enField.val(), 'first paragraph',
+ 'first part of english translation should be filled');
+
+ await testUtils.fields.editInput($enField, "first paragraph modified");
+ await testUtils.dom.click($('.modal button.btn-primary')); // save
+ await testUtils.nextTick();
+
+ assert.strictEqual($foo.val(), "<p>first paragraph</p><p>second paragraph</p>",
+ "the new partial translation should not be transfered");
form.destroy();
@@ -2636,7 +2797,7 @@ QUnit.module('basic_fields', {
QUnit.module('FieldDateRange');
QUnit.test('Datetime field', async function (assert) {
- assert.expect(19);
+ assert.expect(20);
this.data.partner.fields.datetime_end = {string: 'Datetime End', type: 'datetime'};
this.data.partner.records[0].datetime_end = '2017-03-13 00:00:00';
@@ -2685,6 +2846,8 @@ QUnit.module('basic_fields', {
"active end date hour should be '5' in date range picker");
assert.strictEqual($('.daterangepicker:first .drp-calendar.right .minuteselect').val(), '30',
"active end date minute should be '30' in date range picker");
+ assert.containsN($('.daterangepicker:first .drp-calendar.left .minuteselect'), 'option', 12,
+ "minute selection should contain 12 options (1 for each 5 minutes)");
// Close picker
await testUtils.dom.click($('.daterangepicker:first .cancelBtn'));
@@ -3548,7 +3711,7 @@ QUnit.module('basic_fields', {
View: ListView,
model: 'partner',
data: this.data,
- arch: '<tree editable="bottom">' +
+ arch: '<tree multi_edit="1">' +
'<field name="datetime"/>' +
'</tree>',
translateParameters: { // Avoid issues due to localization formats
@@ -6440,7 +6603,7 @@ QUnit.module('basic_fields', {
});
QUnit.test('Field Color: default widget state', async function (assert) {
- assert.expect(3);
+ assert.expect(4);
var form = await createView({
View: FormView,
@@ -6465,6 +6628,46 @@ QUnit.module('basic_fields', {
await testUtils.dom.click($('.modal .btn:contains("Discard")'));
+ assert.strictEqual(document.activeElement, form.$('.o_field_color')[0],
+ "Focus should go back to the color field");
+
+ form.destroy();
+ });
+
+ QUnit.test('Field Color: behaviour in different views', async function (assert) {
+ assert.expect(2);
+
+ this.data.partner.records[0].p = [4, 2];
+ this.data.partner.records[1].hex_color = '#ff0080';
+
+ const form = await createView({
+ arch: '<form>' +
+ '<field name="hex_color" widget="color"/>' +
+ '<field name="p">' +
+ '<tree editable="top">' +
+ '<field name="display_name"/>' +
+ '<field name="hex_color" widget="color"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ data: this.data,
+ model: 'partner',
+ res_id: 1,
+ View: FormView,
+ });
+
+ await testUtils.dom.click(form.$('.o_field_color:first()'));
+ assert.containsNone($(document.body), '.modal',
+ "Color field in readonly shouldn't be editable");
+
+ const rowInitialHeight = form.$('.o_data_row:first()').height();
+
+ await testUtils.form.clickEdit(form);
+ await testUtils.dom.click(form.$('.o_data_row:first() .o_data_cell:first()'));
+
+ assert.strictEqual(rowInitialHeight, form.$('.o_data_row:first()').height(),
+ "Color field shouldn't change the color height when edited");
+
form.destroy();
});
diff --git a/addons/web/static/tests/fields/field_utils_tests.js b/addons/web/static/tests/fields/field_utils_tests.js
index c4281b38c87..e0b9564d13c 100644
--- a/addons/web/static/tests/fields/field_utils_tests.js
+++ b/addons/web/static/tests/fields/field_utils_tests.js
@@ -353,5 +353,39 @@ QUnit.test('parse datetime', function (assert) {
core._t.database.parameters = originalParameters;
});
+QUnit.test('parse date without separator', function (assert) {
+ assert.expect(8);
+
+ var originalParameters = _.clone(core._t.database.parameters);
+
+ _.extend(core._t.database.parameters, {date_format: '%d.%m/%Y'});
+ var dateFormat = "DD.MM/YYYY";
+
+ assert.throws(function () {fieldUtils.parse.date("1197")}, /is not a correct/, "Wrongly formated dates should be invalid");
+ assert.throws(function () {fieldUtils.parse.date("0131")}, /is not a correct/, "Wrongly formated dates should be invalid");
+ assert.throws(function () {fieldUtils.parse.date("970131")}, /is not a correct/, "Wrongly formated dates should be invalid");
+ assert.equal(fieldUtils.parse.date("3101").format(dateFormat), "31.01/" + moment.utc().year());
+ assert.equal(fieldUtils.parse.date("31.01").format(dateFormat), "31.01/" + moment.utc().year());
+ assert.equal(fieldUtils.parse.date("310197").format(dateFormat), "31.01/1997");
+ assert.equal(fieldUtils.parse.date("310117").format(dateFormat), "31.01/2017");
+ assert.equal(fieldUtils.parse.date("31011985").format(dateFormat), "31.01/1985");
+
+ core._t.database.parameters = originalParameters;
+});
+
+QUnit.test('parse datetime without separator', function (assert) {
+ assert.expect(3);
+
+ var originalParameters = _.clone(core._t.database.parameters);
+
+ _.extend(core._t.database.parameters, {date_format: '%d.%m/%Y', time_format: '%H:%M/%S'});
+ var dateTimeFormat = "DD.MM/YYYY HH:mm/ss";
+
+ assert.equal(fieldUtils.parse.datetime("3101198508").format(dateTimeFormat), "31.01/1985 08:00/00");
+ assert.equal(fieldUtils.parse.datetime("310119850833").format(dateTimeFormat), "31.01/1985 08:33/00");
+ assert.equal(fieldUtils.parse.datetime("31/01/1985 08").format(dateTimeFormat), "31.01/1985 08:00/00");
+
+ core._t.database.parameters = originalParameters;
+});
});
});
diff --git a/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js b/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js
index 4d5b5d8aa89..081bd4e3598 100644
--- a/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js
+++ b/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js
@@ -557,7 +557,7 @@ QUnit.module('fields', {}, function () {
// save the modal and make sure an onchange is triggered
await testUtils.dom.click($('.modal .modal-footer .btn-primary').first());
- assert.verifySteps(['read', 'get_formview_id', 'load_views', 'read', 'write', 'onchange', 'read']);
+ assert.verifySteps(['read', 'get_formview_id', 'load_views', 'read', 'write', 'read', 'onchange']);
// save the main record, and check that no extra rpcs are done (record
// is not dirty, only a related record was modified)
@@ -571,7 +571,7 @@ QUnit.module('fields', {}, function () {
const list = await createView({
arch: `
- <tree editable="top">
+ <tree multi_edit="1">
<field name="trululu"/>
</tree>`,
data: this.data,
diff --git a/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js b/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js
index 35b4be9f25e..60f3398c88c 100644
--- a/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js
+++ b/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js
@@ -8705,6 +8705,42 @@ QUnit.module('fields', {}, function () {
form.destroy();
});
+
+ QUnit.test('editable one2many list with oe_read_only button', async function (assert) {
+ assert.expect(9);
+
+ const form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: `<form>
+ <field name="turtles">
+ <tree editable="bottom">
+ <field name="turtle_foo"/>
+ <button name="do_it" type="object" class="oe_read_only"/>
+ </tree>
+ </field>
+ </form>`,
+ res_id: 1,
+ });
+
+ assert.containsN(form, '.o_list_view thead th:visible', 2);
+ assert.containsN(form, '.o_list_view tbody .o_data_row td:visible', 2);
+ assert.containsN(form, '.o_list_view tfoot td:visible', 2);
+ assert.containsNone(form, '.o_list_record_remove_header');
+
+ await testUtils.form.clickEdit(form);
+
+ // should have two visible columns in edit: foo + trash
+ assert.hasClass(form.$('.o_form_view'), 'o_form_editable');
+ assert.containsN(form, '.o_list_view thead th:visible', 2);
+ assert.containsN(form, '.o_list_view tbody .o_data_row td:visible', 2);
+ assert.containsN(form, '.o_list_view tfoot td:visible', 2);
+ assert.containsOnce(form, '.o_list_record_remove_header');
+
+ form.destroy();
+ });
+
});
});
});
diff --git a/addons/web/static/tests/fields/relational_fields_mobile_tests.js b/addons/web/static/tests/fields/relational_fields_mobile_tests.js
new file mode 100644
index 00000000000..cf46aa2c1cc
--- /dev/null
+++ b/addons/web/static/tests/fields/relational_fields_mobile_tests.js
@@ -0,0 +1,66 @@
+odoo.define("web.relational_fields_mobile_tests", function (require) {
+"use strict";
+
+const FormView = require("web.FormView");
+const testUtils = require("web.test_utils");
+
+QUnit.module("fields", {}, function () {
+ QUnit.module("relational_fields", {
+ beforeEach() {
+ this.data = {
+ partner: {
+ fields: {
+ display_name: { string: "Displayed name", type: "char" },
+ p: {string: "one2many field", type: "one2many", relation: "partner", relation_field: "trululu"},
+ trululu: {string: "Trululu", type: "many2one", relation: "partner"},
+ },
+ records: [{
+ id: 1,
+ display_name: "first record",
+ p: [2, 4],
+ trululu: 4,
+ }, {
+ id: 2,
+ display_name: "second record",
+ p: [],
+ trululu: 1,
+ }, {
+ id: 4,
+ display_name: "aaa",
+ }],
+ },
+ };
+ },
+ }, function () {
+ QUnit.module("FieldOne2Many");
+
+ QUnit.test("one2many on mobile: display list if present without kanban view", async function (assert) {
+ assert.expect(2);
+
+ const form = await testUtils.createView({
+ View: FormView,
+ model: "partner",
+ data: this.data,
+ arch: `
+ <form>
+ <field name="p">
+ <tree>
+ <field name="display_name"/>
+ </tree>
+ </field>
+ </form>
+ `,
+ res_id: 1,
+ });
+
+ await testUtils.form.clickEdit(form);
+ assert.containsOnce(form, ".o_field_x2many_list",
+ "should display one2many's list");
+ assert.containsN(form, ".o_field_x2many_list .o_data_row", 2,
+ "should display 2 records in one2many's list");
+
+ 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 24ce3e892c8..751eb1cb137 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -2600,6 +2600,49 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('interact with reference field changed by onchange', async function (assert) {
+ assert.expect(2);
+
+ this.data.partner.onchanges = {
+ bar: function (obj) {
+ if (!obj.bar) {
+ obj.reference = 'partner,1';
+ }
+ },
+ };
+ const form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: `<form>
+ <field name="bar"/>
+ <field name="reference"/>
+ </form>`,
+ mockRPC: function (route, args) {
+ if (args.method === 'create') {
+ assert.deepEqual(args.args[0], {
+ bar: false,
+ reference: 'partner,4',
+ });
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ // trigger the onchange to set a value for the reference field
+ await testUtils.dom.click(form.$('.o_field_boolean input'));
+
+ assert.strictEqual(form.$('.o_field_widget[name=reference] select').val(), 'partner');
+
+ // manually update reference field
+ await testUtils.fields.many2one.searchAndClickItem('reference', {search: 'aaa'});
+
+ // save
+ await testUtils.form.clickSave(form);
+
+ form.destroy();
+ });
+
QUnit.test('default_get and onchange with a reference field', async function (assert) {
assert.expect(8);
diff --git a/addons/web/static/tests/helpers/test_utils_create.js b/addons/web/static/tests/helpers/test_utils_create.js
index 892a946bba0..ee23648a49b 100644
--- a/addons/web/static/tests/helpers/test_utils_create.js
+++ b/addons/web/static/tests/helpers/test_utils_create.js
@@ -288,6 +288,7 @@ function createControlPanel(params) {
}
var viewOptions = _.defaults({}, params.viewOptions, {
+ context: params.context,
modelName: params.model,
searchMenuTypes: params.searchMenuTypes,
viewInfo: params.viewInfo,
diff --git a/addons/web/static/tests/helpers/test_utils_dom.js b/addons/web/static/tests/helpers/test_utils_dom.js
index 87013bd4696..a4315002cab 100644
--- a/addons/web/static/tests/helpers/test_utils_dom.js
+++ b/addons/web/static/tests/helpers/test_utils_dom.js
@@ -40,6 +40,7 @@ var concurrency = require('web.concurrency');
* @param {jQuery|HTMLElement} [options.mousedownTarget=undefined] target of the mousedown event
* @param {jQuery|HTMLElement} [options.mousemoveTarget=undefined] target of the mousemove event
* @param {jQuery|HTMLElement} [options.mouseupTarget=undefined] target of the mouseup event
+ * @param {jQuery|HTMLElement} [options.ctrlKey=undefined] if the ctrl key should be considered pressed at the time of mouseup
* @returns {Promise}
*/
function dragAndDrop($el, $to, options) {
@@ -102,7 +103,8 @@ function dragAndDrop($el, $to, options) {
triggerEvent(options.mouseupTarget || el || $el, 'mouseup', {
which: 1,
pageX: toOffset.left,
- pageY: toOffset.top
+ pageY: toOffset.top,
+ ctrlKey: options.ctrlKey,
});
if (options.withTrailingClick) {
triggerEvent(options.mouseupTarget || el || $el, 'click');
diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js
index 86e31103f5a..70ca4c521f4 100644
--- a/addons/web/static/tests/views/calendar_tests.js
+++ b/addons/web/static/tests/views/calendar_tests.js
@@ -27,6 +27,7 @@ CalendarRenderer.include({
var createCalendarView = testUtils.createCalendarView;
+// 2016-12-12 08:00:00
var initialDate = new Date(2016, 11, 12, 8, 0, 0);
initialDate = new Date(initialDate.getTime() - initialDate.getTimezoneOffset()*60*1000);
@@ -628,7 +629,7 @@ QUnit.module('Views', {
QUnit.test('default week start (US)', function (assert) {
// if not given any option, default week start is on Sunday
- assert.expect(1);
+ assert.expect(3);
var done = assert.async();
createCalendarView({
@@ -646,9 +647,21 @@ QUnit.module('Views', {
viewOptions: {
initialDate: initialDate,
},
+ mockRPC: function (route, args) {
+ if (args.method === 'search_read' && args.model === 'event') {
+ assert.deepEqual(args.kwargs.domain, [
+ ["start","<=","2016-12-17 23:59:59"],
+ ["stop",">=","2016-12-11 00:00:00"]
+ ],
+ 'The domain to search events in should be correct');
+ }
+ return this._super.apply(this, arguments);
+ }
}).then(function (calendar) {
assert.strictEqual(calendar.$('.fc-day-header').first().text(), "Sun 11",
"The first day of the week should be Sunday");
+ assert.strictEqual(calendar.$('.fc-day-header').last().text(), "Sat 17",
+ "The last day of the week should be Saturday");
calendar.destroy();
done();
});
@@ -656,7 +669,7 @@ QUnit.module('Views', {
QUnit.test('European week start', function (assert) {
// the week start depends on the locale
- assert.expect(1);
+ assert.expect(3);
var done = assert.async();
createCalendarView({
@@ -677,9 +690,21 @@ QUnit.module('Views', {
translateParameters: {
week_start: 1,
},
+ mockRPC: function (route, args) {
+ if (args.method === 'search_read' && args.model === 'event') {
+ assert.deepEqual(args.kwargs.domain, [
+ ["start","<=","2016-12-18 23:59:59"],
+ ["stop",">=","2016-12-12 00:00:00"]
+ ],
+ 'The domain to search events in should be correct');
+ }
+ return this._super.apply(this, arguments);
+ }
}).then(function (calendar) {
assert.strictEqual(calendar.$('.fc-day-header').first().text(), "Mon 12",
"The first day of the week should be Monday");
+ assert.strictEqual(calendar.$('.fc-day-header').last().text(), "Sun 18",
+ "The last day of the week should be Sunday");
calendar.destroy();
done();
});
@@ -2706,6 +2731,122 @@ QUnit.module('Views', {
calendar.destroy();
});
+ QUnit.test('default week start (US) month mode', async function (assert) {
+ // if not given any option, default week start is on Sunday
+ assert.expect(8);
+
+ // 2019-09-12 08:00:00
+ var initDate = new Date(2019, 8, 12, 8, 0, 0);
+ initDate = new Date(initDate.getTime() - initDate.getTimezoneOffset()*60*1000);
+
+ var calendar = await createCalendarView({
+ View: CalendarView,
+ model: 'event',
+ data: this.data,
+ arch:
+ '<calendar class="o_calendar_test" '+
+ 'date_start="start" '+
+ 'date_stop="stop" '+
+ 'mode="month">'+
+ '</calendar>',
+ archs: archs,
+
+ viewOptions: {
+ initialDate: initDate,
+ },
+ mockRPC: function (route, args) {
+ if (args.method === 'search_read' && args.model === 'event') {
+ assert.deepEqual(args.kwargs.domain, [
+ ["start","<=","2019-10-12 23:59:59"],
+ ["stop",">=","2019-09-01 00:00:00"]
+ ],
+ 'The domain to search events in should be correct');
+ }
+ return this._super.apply(this, arguments);
+ }
+ });
+
+ assert.strictEqual(calendar.$('.fc-day-header').first().text(), "Sunday",
+ "The first day of the week should be Sunday");
+ assert.strictEqual(calendar.$('.fc-day-header').last().text(), "Saturday",
+ "The last day of the week should be Saturday");
+
+ var $firstDay = calendar.$('.fc-day-top').first();
+
+ assert.strictEqual($firstDay.find('.fc-week-number').text(), "36",
+ "The number of the week should be correct");
+ assert.strictEqual($firstDay.find('.fc-day-number').text(), "1",
+ "The first day of the week should be 2019-09-01");
+ assert.strictEqual($firstDay.data('date'), "2019-09-01",
+ "The first day of the week should be 2019-09-01");
+
+ var $lastDay = calendar.$('.fc-day-top').last();
+ assert.strictEqual($lastDay.text(), "12",
+ "The last day of the week should be 2019-10-12");
+ assert.strictEqual($lastDay.data('date'), "2019-10-12",
+ "The last day of the week should be 2019-10-12");
+
+ calendar.destroy();
+ });
+
+ QUnit.test('European week start month mode', async function (assert) {
+ assert.expect(8);
+
+ // 2019-09-12 08:00:00
+ var initDate = new Date(2019, 8, 12, 8, 0, 0);
+ initDate = new Date(initDate.getTime() - initDate.getTimezoneOffset()*60*1000);
+
+ var calendar = await createCalendarView({
+ View: CalendarView,
+ model: 'event',
+ data: this.data,
+ arch:
+ '<calendar class="o_calendar_test" '+
+ 'date_start="start" '+
+ 'date_stop="stop" '+
+ 'mode="month">'+
+ '</calendar>',
+ archs: archs,
+
+ viewOptions: {
+ initialDate: initDate,
+ },
+ translateParameters: {
+ week_start: 1,
+ },
+ mockRPC: function (route, args) {
+ if (args.method === 'search_read' && args.model === 'event') {
+ assert.deepEqual(args.kwargs.domain, [
+ ["start","<=","2019-10-06 23:59:59"],
+ ["stop",">=","2019-08-26 00:00:00"]
+ ],
+ 'The domain to search events in should be correct');
+ }
+ return this._super.apply(this, arguments);
+ }
+ });
+
+ assert.strictEqual(calendar.$('.fc-day-header').first().text(), "Monday",
+ "The first day of the week should be Monday");
+ assert.strictEqual(calendar.$('.fc-day-header').last().text(), "Sunday",
+ "The last day of the week should be Sunday");
+
+ var $firstDay = calendar.$('.fc-day-top').first();
+ assert.strictEqual($firstDay.find('.fc-week-number').text(), "35",
+ "The number of the week should be correct");
+ assert.strictEqual($firstDay.find('.fc-day-number').text(), "26",
+ "The first day of the week should be 2019-09-01");
+ assert.strictEqual($firstDay.data('date'), "2019-08-26",
+ "The first day of the week should be 2019-08-26");
+
+ var $lastDay = calendar.$('.fc-day-top').last();
+ assert.strictEqual($lastDay.text(), "6",
+ "The last day of the week should be 2019-10-06");
+ assert.strictEqual($lastDay.data('date'), "2019-10-06",
+ "The last day of the week should be 2019-10-06");
+
+ calendar.destroy();
+ });
});
});
diff --git a/addons/web/static/tests/views/control_panel_tests.js b/addons/web/static/tests/views/control_panel_tests.js
index f51070091b2..d86e1b5428e 100644
--- a/addons/web/static/tests/views/control_panel_tests.js
+++ b/addons/web/static/tests/views/control_panel_tests.js
@@ -1,16 +1,18 @@
odoo.define('web.control_panel_tests', function (require) {
"use strict";
-var ControlPanelView = require('web.ControlPanelView');
-var testUtils = require('web.test_utils');
+const AbstractAction = require('web.AbstractAction');
+const ControlPanelView = require('web.ControlPanelView');
+const core = require('web.core');
+const testUtils = require('web.test_utils');
-var createControlPanel = testUtils.createControlPanel;
+const createActionManager = testUtils.createActionManager;
+const createControlPanel = testUtils.createControlPanel;
-function createControlPanelFactory(arch, fields, params) {
- params = params || {};
+function createControlPanelFactory(arch, fields) {
arch = arch || "<search></search>";
fields = fields || {};
- var viewInfo = {arch: arch, fields: fields};
+ var viewInfo = {arch: arch, fields: fields};
var controlPanelFactory = new ControlPanelView({viewInfo: viewInfo, context: {}});
return controlPanelFactory;
}
@@ -337,29 +339,6 @@ QUnit.module('Views', {
);
});
- QUnit.module('Control Panel Rendering');
-
- QUnit.test('invisible filters are not rendered', async function (assert) {
- assert.expect(2);
- var controlPanel = await createControlPanel({
- model: 'partner',
- arch: "<search>" +
- "<filter name=\"filterA\" string=\"A\" domain=\"[]\"/>" +
- "<filter name=\"filterB\" string=\"B\" invisible=\"1\" domain=\"[]\"/>" +
- "</search>",
- data: this.data,
- searchMenuTypes: ['filter'],
- context: {
- search_disable_custom_filters: true,
- },
- });
- await testUtils.dom.click(controlPanel.$('.o_filters_menu_button'));
- assert.containsOnce(controlPanel, '.o_menu_item a:contains("A")');
- assert.containsOnce(controlPanel, '.o_menu_item.d-none a:contains("B")');
-
- controlPanel.destroy();
- });
-
QUnit.module('Control Panel behaviour');
QUnit.test('remove a facet with backspace', async function (assert) {
@@ -397,23 +376,51 @@ QUnit.module('Views', {
QUnit.module('Control Panel Rendering');
+ QUnit.test('default breadcrumb in abstract action', async function (assert) {
+ assert.expect(1);
+
+ const ConcreteAction = AbstractAction.extend({
+ hasControlPanel: true,
+ });
+ core.action_registry.add('ConcreteAction', ConcreteAction);
+
+ const actionManager = await createActionManager();
+ await actionManager.doAction({id: 1,
+ name: 'A Concrete Action',
+ tag: 'ConcreteAction',
+ type: 'ir.actions.client',
+ });
+
+ assert.strictEqual(actionManager.$('.breadcrumb').text(), 'A Concrete Action');
+
+ actionManager.destroy();
+ });
+
QUnit.test('invisible filters are not rendered', async function (assert) {
- assert.expect(2);
+ assert.expect(5);
var controlPanel = await createControlPanel({
model: 'partner',
- arch: "<search>" +
- "<filter name=\"filterA\" string=\"A\" domain=\"[]\"/>" +
- "<filter name=\"filterB\" string=\"B\" invisible=\"1\" domain=\"[]\"/>" +
- "</search>",
+ arch: `<search>
+ <filter name="filterA" string="A" domain="[]"/>
+ <filter name="filterB" string="B" invisible="1" domain="[]"/>
+ </search>`,
data: this.data,
searchMenuTypes: ['filter'],
context: {
- search_disable_custom_filters: true,
+ search_default_filterB: true,
},
});
await testUtils.dom.click(controlPanel.$('.o_filters_menu_button'));
assert.containsOnce(controlPanel, '.o_menu_item a:contains("A")');
- assert.containsOnce(controlPanel, '.o_menu_item.d-none a:contains("B")');
+ assert.containsNone(controlPanel, '.o_menu_item a:contains("B")');
+ // default filter should be activated even if invisible
+ assert.containsOnce(controlPanel, '.o_searchview_facet .o_facet_values:contains(B)');
+
+ // Triggers an update of the filter menu
+ await testUtils.dom.click(controlPanel.$('.o_filters_menu .o_menu_item'));
+ // The displayed filters shhould be the same as before
+ assert.containsOnce(controlPanel, '.o_menu_item a:contains("A")');
+ assert.containsNone(controlPanel, '.o_menu_item a:contains("B")');
controlPanel.destroy();
});
diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js
index fcb7df4ff29..03c492245b5 100644
--- a/addons/web/static/tests/views/kanban_tests.js
+++ b/addons/web/static/tests/views/kanban_tests.js
@@ -2799,6 +2799,42 @@ QUnit.module('Views', {
kanban.destroy();
});
+ QUnit.test('completely prevent drag and drop if records_draggable set to false', async function (assert) {
+ assert.expect(6);
+
+ var envIDs = [1, 3, 2, 4]; // the ids that should be in the environment during this test
+ var kanban = await createView({
+ View: KanbanView,
+ model: 'partner',
+ data: this.data,
+ arch: '<kanban class="o_kanban_test" records_draggable="false">' +
+ '<field name="bar"/>' +
+ '<templates><t t-name="kanban-box">' +
+ '<div><field name="foo"/></div>' +
+ '</t></templates></kanban>',
+ groupBy: ['product_id'],
+ });
+
+ // testing initial state
+ assert.containsN(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record', 2);
+ assert.containsN(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record', 2);
+ assert.deepEqual(kanban.exportState().resIds, envIDs);
+
+ // attempt to drag&drop a record in another column
+ var $record = kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record:first');
+ var $group = kanban.$('.o_kanban_group:nth-child(2)');
+ await testUtils.dom.dragAndDrop($record, $group, {withTrailingClick: true});
+
+ // should not drag&drop record
+ assert.containsN(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record', 2,
+ "First column should still contain 2 records");
+ assert.containsN(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record', 2,
+ "Second column should still contain 2 records");
+ assert.deepEqual(kanban.exportState().resIds, envIDs, "Records should not have moved");
+
+ kanban.destroy();
+ });
+
QUnit.test('kanban view with default_group_by', async function (assert) {
assert.expect(7);
this.data.partner.records.product_id = 1;
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index 82607c43689..32c868a9bb0 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -98,6 +98,7 @@ QUnit.module('Views', {
"ir.translation": {
fields: {
lang_code: {type: "char"},
+ src: {type: "char"},
value: {type: "char"},
res_id: {type: "integer"},
name: {type: "char"},
@@ -1537,7 +1538,7 @@ QUnit.module('Views', {
QUnit.test('width of some of the fields should be hardcoded if no data', async function (assert) {
const assertions = [
- { field: 'bar', expected: 40, type: 'Boolean' },
+ { field: 'bar', expected: 50, type: 'Boolean' },
{ field: 'int_field', expected: 74, type: 'Integer' },
{ field: 'qux', expected: 92, type: 'Float' },
{ field: 'date', expected: 92, type: 'Date' },
@@ -1577,7 +1578,7 @@ QUnit.module('Views', {
QUnit.test('width of some fields should be hardcoded if no data, and list initially invisible', async function (assert) {
const assertions = [
- { field: 'bar', expected: 40, type: 'Boolean' },
+ { field: 'bar', expected: 50, type: 'Boolean' },
{ field: 'int_field', expected: 74, type: 'Integer' },
{ field: 'qux', expected: 92, type: 'Float' },
{ field: 'date', expected: 92, type: 'Date' },
@@ -1635,8 +1636,103 @@ QUnit.module('Views', {
form.destroy();
});
+ QUnit.test('editable list: overflowing table', async function (assert) {
+ assert.expect(1);
+
+ this.data.bar = {
+ fields: {
+ titi: { string: "Small char", type: "char", sortable: true },
+ grosminet: { string: "Beeg char", type: "char", sortable: true },
+ },
+ records: [
+ {
+ id: 1,
+ titi: "Tiny text",
+ grosminet:
+ // Just want to make sure that the table is overflowed
+ `Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Donec est massa, gravida eget dapibus ac, eleifend eget libero.
+ Suspendisse feugiat sed massa eleifend vestibulum. Sed tincidunt
+ velit sed lacinia lacinia. Nunc in fermentum nunc. Vestibulum ante
+ ipsum primis in faucibus orci luctus et ultrices posuere cubilia
+ Curae; Nullam ut nisi a est ornare molestie non vulputate orci.
+ Nunc pharetra porta semper. Mauris dictum eu nulla a pulvinar. Duis
+ eleifend odio id ligula congue sollicitudin. Curabitur quis aliquet
+ nunc, ut aliquet enim. Suspendisse malesuada felis non metus
+ efficitur aliquet.`,
+ },
+ ],
+ };
+ const list = await createView({
+ arch: `
+ <tree editable="top">
+ <field name="titi"/>
+ <field name="grosminet" widget="char"/>
+ </tree>`,
+ data: this.data,
+ model: 'bar',
+ View: ListView,
+ });
+
+ assert.strictEqual(list.$('table').width(), list.$('.o_list_view').width(),
+ "Table should not be stretched by its content");
+
+ list.destroy();
+ });
+
+ QUnit.test('editable list: overflowing table (3 columns)', async function (assert) {
+ assert.expect(4);
+
+ const longText = `Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Donec est massa, gravida eget dapibus ac, eleifend eget libero.
+ Suspendisse feugiat sed massa eleifend vestibulum. Sed tincidunt
+ velit sed lacinia lacinia. Nunc in fermentum nunc. Vestibulum ante
+ ipsum primis in faucibus orci luctus et ultrices posuere cubilia
+ Curae; Nullam ut nisi a est ornare molestie non vulputate orci.
+ Nunc pharetra porta semper. Mauris dictum eu nulla a pulvinar. Duis
+ eleifend odio id ligula congue sollicitudin. Curabitur quis aliquet
+ nunc, ut aliquet enim. Suspendisse malesuada felis non metus
+ efficitur aliquet.`;
+
+ this.data.bar = {
+ fields: {
+ titi: { string: "Small char", type: "char", sortable: true },
+ grosminet1: { string: "Beeg char 1", type: "char", sortable: true },
+ grosminet2: { string: "Beeg char 2", type: "char", sortable: true },
+ grosminet3: { string: "Beeg char 3", type: "char", sortable: true },
+ },
+ records: [{
+ id: 1,
+ titi: "Tiny text",
+ grosminet1: longText,
+ grosminet2: longText + longText,
+ grosminet3: longText + longText + longText,
+ }],
+ };
+ const list = await createView({
+ arch: `
+ <tree editable="top">
+ <field name="titi"/>
+ <field name="grosminet1" class="large"/>
+ <field name="grosminet3" class="large"/>
+ <field name="grosminet2" class="large"/>
+ </tree>`,
+ data: this.data,
+ model: 'bar',
+ View: ListView,
+ });
+
+ assert.strictEqual(list.$('table').width(), list.$('.o_list_view').width());
+ const largeCells = list.$('.o_data_cell.large');
+ assert.strictEqual(largeCells[0].offsetWidth, largeCells[1].offsetWidth);
+ assert.strictEqual(largeCells[1].offsetWidth, largeCells[2].offsetWidth);
+ assert.ok(list.$('.o_data_cell:not(.large)')[0].offsetWidth < largeCells[0].offsetWidth);
+
+ list.destroy();
+ });
+
QUnit.test('editable list: list view in an initially unselected notebook page', async function (assert) {
- assert.expect(9);
+ assert.expect(8);
this.data.foo.records = [{ id: 1, o2m: [1] }];
this.data.bar = {
@@ -1745,23 +1841,6 @@ QUnit.module('Views', {
"list has been correctly frozen after sorting a column");
form.destroy();
-
- // Case 5: resize a column
- form = await createView(formOptions);
- await testUtils.dom.click(form.$('.nav-item:last-child .nav-link'));
-
- const resizeHandle = form.$('.o_resize')[0];
- await testUtils.dom.dragAndDrop(resizeHandle, resizeHandle, {
- mousemoveTarget: window,
- mouseupTarget: window,
- });
- [titi, grosminet] = form.$('th');
- assert.ok(
- titi.style.width.split('px')[0] > 80 &&
- grosminet.style.width.split('px')[0] > 700,
- "list has been correctly frozen after resizing a column");
-
- form.destroy();
});
QUnit.test('editable list: list view hidden by an invisible modifier', async function (assert) {
@@ -1907,7 +1986,7 @@ QUnit.module('Views', {
QUnit.test('width of some of the fields should be hardcoded if no data (grouped case)', async function (assert) {
const assertions = [
- { field: 'bar', expected: 40, type: 'Boolean' },
+ { field: 'bar', expected: 50, type: 'Boolean' },
{ field: 'int_field', expected: 74, type: 'Integer' },
{ field: 'qux', expected: 92, type: 'Float' },
{ field: 'date', expected: 92, type: 'Date' },
@@ -2019,6 +2098,34 @@ QUnit.module('Views', {
list.destroy();
});
+ QUnit.test('column widths are kept when switching records in edition', async function (assert) {
+ assert.expect(4);
+
+ const list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: `<tree editable="bottom">
+ <field name="m2o"/>
+ <field name="text"/>
+ </tree>`,
+ });
+
+ const width = list.$('th[data-name="m2o"]')[0].offsetWidth;
+
+ await testUtils.dom.click(list.$('.o_data_row:first .o_data_cell:first'));
+
+ assert.hasClass(list.$('.o_data_row:first'), 'o_selected_row');
+ assert.strictEqual(list.$('th[data-name="m2o"]')[0].offsetWidth, width);
+
+ await testUtils.dom.click(list.$('.o_data_row:nth(1) .o_data_cell:first'));
+
+ assert.hasClass(list.$('.o_data_row:nth(1)'), 'o_selected_row');
+ assert.strictEqual(list.$('th[data-name="m2o"]')[0].offsetWidth, width);
+
+ list.destroy();
+ });
+
QUnit.test('column widths are re-computed on window resize', async function (assert) {
assert.expect(2);
@@ -2089,7 +2196,7 @@ QUnit.module('Views', {
View: ListView,
model: 'foo',
data: this.data,
- arch: '<tree editable="bottom">' +
+ arch: '<tree multi_edit="1">' +
'<field name="datetime"/>' +
'<field name="text"/>' +
'</tree>',
@@ -3544,193 +3651,6 @@ QUnit.module('Views', {
list.destroy();
});
- QUnit.test('export list view to xls with nested groups', async function (assert) {
- assert.expect(5);
-
- let currencies = {};
- _.each(this.data.res_currency.records, function (currency) {
- currencies[currency.id] = currency;
- });
-
- let list = await createView({
- View: ListView,
- model: 'foo',
- data: this.data,
- arch: `
- <tree download="1">
- <field name="foo"/>
- <field name="bar"/>
- <field name="int_field" sum="Total"/>
- <field name="datetime"/>
- <field name="amount" widget="monetary"/>
- <field name="currency_id" invisible="1"/>
- <field name="o2m"/>
- <field name="m2o"/>
- <field name="m2m"/>
- </tree>`,
- groupBy: ['foo', 'bar'],
- session: {
- currencies: currencies,
- get_file(args) {
- let data = JSON.parse(args.data.data);
- assert.strictEqual(args.url, '/web/list/export_xls',
- "should call get_file with the correct url");
- assert.deepEqual(data.columns, [
- {field: "foo", string: "Foo"},
- {field: "bar", string: "Bar"},
- {field: "int_field", aggregateValue: 32, string: "int_field"},
- {field: "datetime", string: "Datetime Field"},
- {field: "amount", string: "Monetary field"},
- {field: "o2m", string: "O2M field"},
- {field: "m2o", string: "M2O field"},
- {field: "m2m", string: "M2M field"},
- ], "columns should be in the correct order with the string and aggregate value")
- assert.deepEqual(data.groups[0], {
- // Group "yop"
- isGrouped: true,
- count: 1,
- aggregateValues: {int_field: 10},
- hideHeader: false,
- value: "yop",
- data: [{
- // Group "yop > true"
- isGrouped: false,
- count: 1,
- aggregateValues: {int_field: 10},
- hideHeader: false,
- value: "true",
- data: [{
- // Record "yop > true"
- foo: "yop",
- bar: "True",
- int_field: "10",
- datetime: "12/12/2016 10:55:05",
- amount: "1200.00€",
- m2m: "2 records",
- m2o: "Value 1",
- o2m: "No records",
- }]
- }]
- });
- assert.deepEqual(data.groups[1], {
- // Group "blip"
- isGrouped: true,
- count: 2,
- aggregateValues: {int_field: 5},
- hideHeader: false,
- value: "blip",
- data: [{
- // Group "blip > true"
- isGrouped: false,
- count: 1,
- aggregateValues: {int_field: 9},
- hideHeader: false,
- value: "true",
- data: [],
- }, {
- // Group "blip > false"
- isGrouped: false,
- count: 1,
- aggregateValues: {int_field: -4},
- hideHeader: false,
- value: "false",
- data: [],
- }]
- });
- assert.deepEqual(data.groups[2], {
- // Group "gnap"
- isGrouped: true,
- count: 1,
- aggregateValues: {int_field: 17},
- hideHeader: false,
- value: "gnap",
- data: [],
- });
- args.complete();
- },
- },
- });
-
- // open the first group
- await testUtils.dom.click(list.$('.o_group_header:first'));
-
- // open subgroup
- let $openGroup = list.$('tbody:nth(1)');
- await testUtils.dom.click($openGroup.find('.o_group_header:first'));
-
- // open the second group
- await testUtils.dom.click(list.$('.o_group_header:nth(2)'));
-
- // Download
- await testUtils.dom.click(list.$buttons.find('.o_list_download'));
-
- list.destroy();
- });
-
- QUnit.test('export list view to xls', async function (assert) {
- assert.expect(3);
-
- let currencies = {};
- _.each(this.data.res_currency.records, function (currency) {
- currencies[currency.id] = currency;
- });
-
- let list = await createView({
- View: ListView,
- model: 'foo',
- data: this.data,
- arch: `
- <tree>
- <field name="foo"/>
- <field name="bar"/>
- <field name="int_field" sum="Total"/>
- <field name="datetime"/>
- <field name="amount" widget="monetary"/>
- <field name="currency_id" invisible="1"/>
- <field name="o2m"/>
- <field name="m2o"/>
- <field name="m2m"/>
- </tree>`,
- session: {
- currencies: currencies,
- get_file(args) {
- let data = JSON.parse(args.data.data);
- assert.strictEqual(args.url, '/web/list/export_xls',
- "should call get_file with the correct url");
- assert.deepEqual(data.columns, [
- {field: "foo", string: "Foo"},
- {field: "bar", string: "Bar"},
- {field: "int_field", aggregateValue: 32, string: "int_field"},
- {field: "datetime", string: "Datetime Field"},
- {field: "amount", string: "Monetary field"},
- {field: "o2m", string: "O2M field"},
- {field: "m2o", string: "M2O field"},
- {field: "m2m", string: "M2M field"},
- ], "columns should be in the correct order with the string and aggregate value")
- assert.deepEqual(data.groups[0], {
- isGrouped: false,
- count: 4,
- aggregateValues: {},
- hideHeader: true,
- value: "Undefined",
- data: [
- {foo: "yop", bar: "True", int_field: "10", datetime: "12/12/2016 10:55:05", amount: "1200.00€", m2m: "2 records", m2o: "Value 1", o2m: "No records"},
- {foo: "blip", bar: "True", int_field: "9", datetime: "", amount: "$500.00", m2m: "3 records", m2o: "Value 2", o2m: "No records"},
- {foo: "gnap", bar: "True", int_field: "17", datetime: "", amount: "$300.00", m2m: "No records", m2o: "Value 1", o2m: "No records"},
- {foo: "blip", bar: "False", int_field: "-4", datetime: "", amount: "$0.00", m2m: "1 record", m2o: "Value 1", o2m: "No records"},
- ],
- });
- args.complete();
- },
- },
- });
-
- // Download
- await testUtils.dom.click(list.$buttons.find('.o_list_download'));
-
- list.destroy();
- });
-
QUnit.test('grouped list on selection field at level 2', async function (assert) {
assert.expect(4);
@@ -5392,7 +5312,7 @@ QUnit.module('Views', {
View: ListView,
model: 'foo',
data: this.data,
- arch: '<tree editable="top">' +
+ arch: '<tree multi_edit="1">' +
'<field name="foo"/>' +
'</tree>',
mockRPC: function (route, args) {
@@ -5426,7 +5346,7 @@ QUnit.module('Views', {
assert.expect(2);
const list = await createView({
- arch: `<tree editable="top"><field name="foo"/></tree>`,
+ arch: `<tree editable="top" multi_edit="1"><field name="foo"/></tree>`,
data: this.data,
model: 'foo',
View: ListView,
@@ -5455,7 +5375,7 @@ QUnit.module('Views', {
View: ListView,
model: 'foo',
data: this.data,
- arch: '<tree editable="bottom">' +
+ arch: '<tree editable="bottom" multi_edit="1">' +
'<field name="foo"/>' +
'<field name="int_field"/>' +
'</tree>',
@@ -5510,7 +5430,7 @@ QUnit.module('Views', {
await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(1)'));
await testUtils.fields.editInput(list.$('.o_field_widget[name=int_field]'), 666);
await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_data_cell:eq(0)'));
- assert.ok($('.modal').text().includes('2 selected records'), "the number of records should be correctly displayed");
+ assert.ok($('.modal').text().includes('those 2 records'), "the number of records should be correctly displayed");
await testUtils.dom.click($('.modal .btn-primary'));
assert.containsNone(list, '.o_data_cell input.o_field_widget', "no field should be editable anymore");
assert.verifySteps(['write', 'read']);
@@ -5525,8 +5445,32 @@ QUnit.module('Views', {
list.destroy();
});
- QUnit.test('editable list view: multi edition and onchanges', async function (assert) {
- assert.expect(10);
+ QUnit.test('create in multi editable list', async function (assert) {
+ assert.expect(1);
+
+ var list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '<tree multi_edit="1">' +
+ '<field name="foo"/>' +
+ '<field name="int_field"/>' +
+ '</tree>',
+ intercepts: {
+ switch_view: function (ev) {
+ assert.strictEqual(ev.data.view_type, 'form');
+ },
+ },
+ });
+
+ // click on CREATE (should trigger a switch_view)
+ await testUtils.dom.click($('.o_list_button_add'));
+
+ list.destroy();
+ });
+
+ QUnit.test('editable list view: multi edition cannot call onchanges', async function (assert) {
+ assert.expect(15);
this.data.foo.onchanges = {
foo: function (obj) {
@@ -5537,7 +5481,7 @@ QUnit.module('Views', {
View: ListView,
model: 'foo',
data: this.data,
- arch: '<tree editable="bottom">' +
+ arch: '<tree multi_edit="1">' +
'<field name="foo"/>' +
'<field name="int_field"/>' +
'</tree>',
@@ -5550,19 +5494,24 @@ QUnit.module('Views', {
},
});
- // edit a single record to validate that onchanges work
+ assert.verifySteps(['/web/dataset/search_read']);
+
+ // select and edit a single record
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input'));
await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(0)'));
await testUtils.fields.editInput(list.$('.o_field_widget[name=foo]'), 'hi');
await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(1)'));
- await testUtils.dom.click(list.$('.o_list_button_discard'));
- assert.containsOnce(document.body, '.modal'); // discard dialog
+ assert.containsOnce(document.body, '.modal'); // save dialog
await testUtils.dom.click($('.modal .btn-primary'));
- assert.verifySteps(['/web/dataset/search_read', 'onchange']);
+ assert.strictEqual(list.$('.o_data_row:eq(0) .o_data_cell').text(), "hi2");
+ assert.strictEqual(list.$('.o_data_row:eq(1) .o_data_cell').text(), "blip9");
- // select two records
- await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input'));
+ assert.verifySteps(['write', 'read']);
+
+ // select the second record (the first one is still selected)
+ assert.ok(list.$('.o_data_row:eq(0) .o_list_record_selector input').is(':checked'));
await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_list_record_selector input'));
// edit foo, first row
@@ -5588,7 +5537,7 @@ QUnit.module('Views', {
View: ListView,
model: 'foo',
data: this.data,
- arch: '<tree editable="bottom">' +
+ arch: '<tree multi_edit="1">' +
'<field name="foo" required="1"/>' +
'<field name="int_field"/>' +
'</tree>',
@@ -5636,7 +5585,7 @@ QUnit.module('Views', {
View: ListView,
model: 'foo',
data: this.data,
- arch: '<tree editable="bottom">' +
+ arch: '<tree multi_edit="1">' +
'<field name="foo"/>' +
'<field name="m2o"/>' +
'</tree>',
@@ -5672,7 +5621,7 @@ QUnit.module('Views', {
const list = await createView({
arch: `
- <tree editable="top">
+ <tree editable="top" multi_edit="1">
<field name="foo"/>
</tree>`,
data: this.data,
@@ -5710,7 +5659,7 @@ QUnit.module('Views', {
const list = await createView({
arch: `
- <tree editable="top">
+ <tree multi_edit="1">
<field name="foo"/>
</tree>`,
data: this.data,
@@ -5740,13 +5689,13 @@ QUnit.module('Views', {
});
QUnit.test('editable list view: multi edition with readonly modifiers', async function (assert) {
- assert.expect(5);
+ assert.expect(4);
var list = await createView({
View: ListView,
model: 'foo',
data: this.data,
- arch: '<tree editable="top">' +
+ arch: '<tree multi_edit="1">' +
'<field name="id"/>' +
'<field name="foo"/>' +
'<field name="int_field" attrs=\'{"readonly": [("id", ">" , 2)]}\'/>' +
@@ -5766,17 +5715,19 @@ QUnit.module('Views', {
// edit a field
await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(1)'));
await testUtils.fields.editInput(list.$('.o_field_widget[name=int_field]'), 666);
- assert.ok($('.modal').text().includes('2 valid'),
- "the number of records should be correctly displayed (only 2 not readonly)");
- assert.ok($('.modal').text().includes('2 invalid'),
- "should display the number of invalid records");
+
+ const modalText = $('.modal-body').text()
+ .split(" ").filter(w => w.trim() !== '').join(" ")
+ .split("\n").join('');
+ assert.strictEqual(modalText,
+ "Among the 4 selected records, 2 are valid for this update. Are you sure you want to " +
+ "perform the following update on those 2 records ? Field: int_field Update to: 666");
await testUtils.dom.click($('.modal .btn-primary'));
assert.strictEqual(list.$('.o_data_row:eq(0) .o_data_cell').text(), "1yop666",
"the first row should be updated");
assert.strictEqual(list.$('.o_data_row:eq(1) .o_data_cell').text(), "2blip666",
"the second row should be updated");
-
list.destroy();
});
@@ -5804,6 +5755,100 @@ QUnit.module('Views', {
list.destroy();
});
+ QUnit.test('editable list view: multi edition server error handling', async function (assert) {
+ assert.expect(3);
+
+ var list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '<tree multi_edit="1">' +
+ '<field name="foo" required="1"/>' +
+ '</tree>',
+ mockRPC: function (route, args) {
+ if (args.method === 'write') {
+ return Promise.reject();
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ // select two records
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input'));
+ await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_list_record_selector input'));
+
+ // edit a line and confirm
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(0)'));
+ await testUtils.fields.editInput(list.$('.o_selected_row .o_field_widget[name=foo]'), "abc");
+ await testUtils.dom.click('body');
+ await testUtils.dom.click($('.modal .btn-primary'));
+ // Server error: if there was a crash manager, there would be an open error at this point...
+ assert.strictEqual(list.$('.o_data_row:eq(0) .o_data_cell').text(), 'yop',
+ "first cell should have discarded any change");
+ assert.strictEqual(list.$('.o_data_row:eq(1) .o_data_cell').text(), 'blip',
+ "second selected record should not have changed");
+ assert.containsNone(list, '.o_data_cell input.o_field_widget',
+ "no field should be editable anymore");
+
+ list.destroy();
+ });
+
+ QUnit.test('editable readonly list view: navigation', async function (assert) {
+ assert.expect(6);
+
+ const list = await createView({
+ arch: `
+ <tree multi_edit="1">
+ <field name="foo"/>
+ <field name="int_field"/>
+ </tree>`,
+ data: this.data,
+ intercepts: {
+ switch_view: function (event) {
+ assert.strictEqual(event.data.res_id, 3,
+ "'switch_view' event has been triggered");
+ },
+ },
+ model: 'foo',
+ View: ListView,
+ });
+
+ // select 2 records
+ await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_list_record_selector input'));
+ await testUtils.dom.click(list.$('.o_data_row:eq(3) .o_list_record_selector input'));
+
+ // toggle a row mode
+ await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_data_cell:eq(1)'));
+ assert.hasClass(list.$('.o_data_row:eq(1)'), 'o_selected_row',
+ "the second row should be selected");
+
+ // Keyboard navigation only interracts with selected elements
+ await testUtils.fields.triggerKeydown(list.$('tr.o_selected_row input.o_field_widget'), 'enter');
+ assert.hasClass(list.$('.o_data_row:eq(3)'), 'o_selected_row',
+ "the fourth row should be selected");
+
+ await testUtils.fields.triggerKeydown($(document.activeElement), 'tab');
+ await testUtils.fields.triggerKeydown($(document.activeElement), 'tab');
+ assert.hasClass(list.$('.o_data_row:eq(1)'), 'o_selected_row',
+ "the second row should be selected again");
+
+ await testUtils.fields.triggerKeydown($(document.activeElement), 'tab');
+ await testUtils.fields.triggerKeydown($(document.activeElement), 'tab');
+ assert.hasClass(list.$('.o_data_row:eq(3)'), 'o_selected_row',
+ "the fourth row should be selected again");
+
+ await testUtils.dom.click(list.$('.o_data_row:eq(2) .o_data_cell:eq(0)'));
+ assert.containsNone(list, '.o_data_cell input.o_field_widget',
+ "no row should be editable anymore");
+ // Clicking on an unselected record while no row is being edited will open the record (switch_view)
+ await testUtils.dom.click(list.$('.o_data_row:eq(2) .o_data_cell:eq(0)'));
+
+ await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_list_record_selector input'));
+ await testUtils.dom.click(list.$('.o_data_row:eq(3) .o_list_record_selector input'));
+
+ list.destroy();
+ });
+
QUnit.test('editable list view: multi edition: edit and validate last row', async function (assert) {
assert.expect(3);
@@ -5811,7 +5856,7 @@ QUnit.module('Views', {
View: ListView,
model: 'foo',
data: this.data,
- arch: '<tree editable="bottom">' +
+ arch: '<tree multi_edit="1">' +
'<field name="foo"/>' +
'<field name="int_field"/>' +
'</tree>',
@@ -5839,6 +5884,238 @@ QUnit.module('Views', {
list.destroy();
});
+ QUnit.test('editable readonly list view: navigation in grouped list', async function (assert) {
+ assert.expect(6);
+
+ const list = await createView({
+ arch: `
+ <tree multi_edit="1">
+ <field name="foo"/>
+ </tree>`,
+ data: this.data,
+ groupBy: ['bar'],
+ intercepts: {
+ switch_view: function (event) {
+ assert.strictEqual(event.data.res_id, 3,
+ "'switch_view' event has been triggered");
+ },
+ },
+ model: 'foo',
+ View: ListView,
+ });
+
+ // Open both groups
+ await testUtils.dom.click(list.$('.o_group_header:first'));
+ await testUtils.dom.click(list.$('.o_group_header:last'));
+
+ // select 2 records
+ await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_list_record_selector input'));
+ await testUtils.dom.click(list.$('.o_data_row:eq(3) .o_list_record_selector input'));
+
+ // toggle a row mode
+ await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_data_cell:eq(0)'));
+ assert.hasClass(list.$('.o_data_row:eq(1)'), 'o_selected_row',
+ "the second row should be selected");
+
+ // Keyboard navigation only interracts with selected elements
+ await testUtils.fields.triggerKeydown(list.$('tr.o_selected_row input.o_field_widget'), 'enter');
+ assert.hasClass(list.$('.o_data_row:eq(3)'), 'o_selected_row',
+ "the fourth row should be selected");
+
+ await testUtils.fields.triggerKeydown($(document.activeElement), 'tab');
+ assert.hasClass(list.$('.o_data_row:eq(1)'), 'o_selected_row',
+ "the second row should be selected again");
+
+ await testUtils.fields.triggerKeydown($(document.activeElement), 'tab');
+ assert.hasClass(list.$('.o_data_row:eq(3)'), 'o_selected_row',
+ "the fourth row should be selected again");
+
+ await testUtils.dom.click(list.$('.o_data_row:eq(2) .o_data_cell:eq(0)'));
+ assert.containsNone(list, '.o_data_cell input.o_field_widget', "no row should be editable anymore");
+ await testUtils.dom.click(list.$('.o_data_row:eq(2) .o_data_cell:eq(0)'));
+
+ list.destroy();
+ });
+
+ QUnit.test('editable readonly list view: single edition behaves like a multi-edition', async function (assert) {
+ assert.expect(3);
+
+ const list = await createView({
+ arch: `
+ <tree multi_edit="1">
+ <field name="foo" required="1"/>
+ </tree>`,
+ data: this.data,
+ model: 'foo',
+ View: ListView,
+ });
+
+ // select a record
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input'));
+
+ // edit a field (invalid input)
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(0)'));
+ await testUtils.fields.editInput(list.$('.o_field_widget[name=foo]'), "");
+
+ assert.containsOnce($('body'),'.modal', "should have a modal");
+ await testUtils.dom.click($('.modal button.btn'));
+
+ // edit a field
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(0)'));
+ await testUtils.fields.editInput(list.$('.o_field_widget[name=foo]'), "bar");
+
+ assert.containsOnce($('body'),'.modal', "should have a modal");
+ await testUtils.dom.click($('.modal button.btn.btn-primary'));
+
+
+ assert.strictEqual(list.$('.o_data_row:eq(0) .o_data_cell').text(), "bar",
+ "the first row should be updated");
+
+ list.destroy();
+ });
+
+ QUnit.test('editable readonly list view: multi edition', async function (assert) {
+ assert.expect(14);
+
+ const list = await createView({
+ arch:
+ `<tree multi_edit="1">
+ <field name="foo"/>
+ <field name="int_field"/>
+ </tree>`,
+ data: this.data,
+ mockRPC: function (route, args) {
+ assert.step(args.method || route);
+ if (args.method === 'write') {
+ assert.deepEqual(args.args, [[1, 2], { int_field: 666 }],
+ "should write on multi records");
+ } else if (args.method === 'read') {
+ if (args.args[0].length !== 1) {
+ assert.deepEqual(args.args, [[1, 2], ['foo', 'int_field']],
+ "should batch the read");
+ }
+ }
+ return this._super.apply(this, arguments);
+ },
+ model: 'foo',
+ View: ListView,
+ });
+
+ assert.verifySteps(['/web/dataset/search_read']);
+
+ // select two records
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input'));
+ await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_list_record_selector input'));
+
+ // edit a field
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(1)'));
+ await testUtils.fields.editInput(list.$('.o_field_widget[name=int_field]'), 666);
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(0)'));
+
+ assert.containsOnce(document.body, '.modal',
+ "modal appears when switching cells");
+
+ await testUtils.dom.click($('.modal .btn:contains(Cancel)'));
+
+ assert.strictEqual(list.$('.o_data_row:eq(0) .o_data_cell').text(), 'yop10',
+ "changes have been discarded and row is back to readonly");
+
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(1)'));
+ await testUtils.fields.editInput(list.$('.o_field_widget[name=int_field]'), 666);
+ await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_data_cell:eq(0)'));
+
+ assert.containsOnce(document.body, '.modal',
+ "there should be an opened modal");
+ assert.ok($('.modal').text().includes('those 2 records'),
+ "the number of records should be correctly displayed");
+
+ await testUtils.dom.click($('.modal .btn-primary'));
+
+ assert.verifySteps(['write', 'read']);
+ assert.strictEqual(list.$('.o_data_row:eq(0) .o_data_cell').text(), "yop666",
+ "the first row should be updated");
+ assert.strictEqual(list.$('.o_data_row:eq(1) .o_data_cell').text(), "blip666",
+ "the second row should be updated");
+ assert.containsNone(list, '.o_data_cell input.o_field_widget',
+ "no field should be editable anymore");
+
+ list.destroy();
+ });
+
+ QUnit.test('editable list view: m2m tags in grouped list', async function (assert) {
+ assert.expect(2);
+
+ const list = await createView({
+ arch: `
+ <tree editable="top" multi_edit="1">
+ <field name="bar"/>
+ <field name="m2m" widget="many2many_tags"/>
+ </tree>`,
+ data: this.data,
+ groupBy: ['bar'],
+ model: 'foo',
+ View: ListView,
+ });
+
+ // Opens first group
+ await testUtils.dom.click(list.$('.o_group_header:first'));
+
+ assert.notEqual(list.$('.o_data_row:first').text(), list.$('.o_data_row:last').text(),
+ "First row and last row should have different values");
+
+ await testUtils.dom.click(list.$('thead .o_list_record_selector:first input'));
+ await testUtils.dom.click(list.$('.o_data_row:first .o_data_cell:eq(1)'));
+ await testUtils.dom.click(list.$('.o_selected_row .o_field_many2manytags .o_delete:first'));
+ await testUtils.dom.click($('.modal .btn-primary'));
+
+ assert.strictEqual(list.$('.o_data_row:first').text(), list.$('.o_data_row:last').text(),
+ "All rows should have been correctly updated");
+
+ list.destroy();
+ });
+
+ QUnit.test('editable list: edit many2one from external link', async function (assert) {
+ assert.expect(2);
+
+ const list = await createView({
+ arch: `
+ <tree editable="top" multi_edit="1">
+ <field name="m2o"/>
+ </tree>`,
+ archs: {
+ 'bar,false,form': '<form string="Bar"><field name="display_name"/></form>',
+ },
+ data: this.data,
+ mockRPC: function (route, args) {
+ if (args.method === 'get_formview_id') {
+ return Promise.resolve(false);
+ }
+ return this._super(route, args);
+ },
+ model: 'foo',
+ View: ListView,
+ });
+
+ await testUtils.dom.click(list.$('thead .o_list_record_selector:first input'));
+ await testUtils.dom.click(list.$('.o_data_row:first .o_data_cell:eq(0)'));
+ await testUtils.dom.click(list.$('.o_external_button:first'));
+
+ // Change the M2O value in the Form dialog
+ await testUtils.fields.editInput($('.modal input:first'), "OOF");
+ await testUtils.dom.click($('.modal .btn-primary'));
+
+ assert.strictEqual($('.modal .o_field_widget[name=m2o]').text(), "OOF",
+ "Value of the m2o should be updated in the confirmation dialog");
+
+ // Close the confirmation dialog
+ await testUtils.dom.click($('.modal .btn-primary'));
+
+ assert.strictEqual(list.$('.o_data_cell:first').text(), "OOF",
+ "Value of the m2o should be updated in the list");
+
+ list.destroy();
+ });
+
QUnit.test('list grouped by date:month', async function (assert) {
assert.expect(1);
@@ -8060,6 +8337,44 @@ QUnit.module('Views', {
list.destroy();
delete widgetRegistry.map.test;
});
+
+ QUnit.test('enter edition in editable list with multi_edit = 0', async function (assert) {
+ assert.expect(1);
+
+ var list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '<tree editable="top" multi_edit="0">' +
+ '<field name="int_field"/>' +
+ '</tree>',
+ });
+
+ // click on int_field cell of first row
+ await testUtils.dom.click(list.$('.o_data_row:first .o_data_cell:nth(0)'));
+ assert.strictEqual(document.activeElement.name, "int_field");
+
+ list.destroy();
+ });
+
+ QUnit.test('enter edition in editable list with multi_edit = 1', async function (assert) {
+ assert.expect(1);
+
+ var list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '<tree editable="top" multi_edit="1">' +
+ '<field name="int_field"/>' +
+ '</tree>',
+ });
+
+ // click on int_field cell of first row
+ await testUtils.dom.click(list.$('.o_data_row:first .o_data_cell:nth(0)'));
+ assert.strictEqual(document.activeElement.name, "int_field");
+
+ list.destroy();
+ });
});
});
diff --git a/addons/web/static/tests/views/search_view_tests.js b/addons/web/static/tests/views/search_view_tests.js
index bd75f92811b..43645318f6c 100644
--- a/addons/web/static/tests/views/search_view_tests.js
+++ b/addons/web/static/tests/views/search_view_tests.js
@@ -372,7 +372,7 @@ QUnit.module('Search View', {
assert.expect(7);
const unpatchDate = patchDate(2019,6,31,13,43,0);
-
+
var actionManager = await createActionManager({
actions: this.actions,
archs: this.archs,
@@ -524,7 +524,7 @@ QUnit.module('Search View', {
await testUtils.dom.click($('.o_search_options button span.fa-filter'));
await testUtils.dom.click($('.o_filters_menu .o_menu_item a').first());
await testUtils.dom.click($('.o_graph_controller .o_control_panel .o_cp_buttons button').eq(1));
- await testUtils.dom.click($('.o_graph_controller .o_group_by_menu .o_menu_item').eq(1));
+ await testUtils.dom.click($('.o_graph_controller .o_group_by_menu .o_menu_item').eq(1));
assert.doesNotHaveClass($('.o_graph_controller .o_group_by_menu .o_menu_item > .dropdown-item').eq(1), 'selected',
'groupby should be still unselected');
actionManager.destroy();
@@ -701,6 +701,7 @@ QUnit.module('Search View', {
// DateTime case
$autocomplete = $('.o_searchview_input');
await stringToEvent($autocomplete, '07/15/1983 00:00:00');
+ await testUtils.fields.triggerKey('down', $autocomplete, 'down');
await testUtils.fields.triggerKey('up', $autocomplete, 'enter');
assert.equal($('.o_searchview_facet .o_facet_values').text().trim(), '07/15/1983 00:00:00',
@@ -1116,7 +1117,7 @@ QUnit.module('Search View', {
// activate Foo a
await testUtils.fields.triggerKey('press', $('.o_searchview_input'), 97);
await testUtils.fields.triggerKey('up', $('.o_searchview_input'), 'enter');
-
+
// activate Date Filter with This Month
await testUtils.dom.click($('button .fa-filter'));
await testUtils.dom.click($('.o_filters_menu .o_menu_item a'));
@@ -1604,7 +1605,7 @@ QUnit.module('Search View', {
var $filterDropdown = $('.modal .o_filters_menu');
await testUtils.dom.click($filterDropdown.find('.o_add_custom_filter'));
- assert.containsN($filterDropdown, '.o_input', 3);
+ assert.containsN($filterDropdown, '.o_input', 4);
// We really are interested in the click event
// We do it twice on each input to make sure
diff --git a/addons/web/static/tests/widgets/data_export_tests.js b/addons/web/static/tests/widgets/data_export_tests.js
index 6d29e8e888d..c9c51219571 100644
--- a/addons/web/static/tests/widgets/data_export_tests.js
+++ b/addons/web/static/tests/widgets/data_export_tests.js
@@ -14,12 +14,22 @@ QUnit.module('widgets', {
partner: {
fields: {
foo: {string: "Foo", type: "char"},
+ bar: {string: "Bar", type: "char"},
},
records: [
{
id: 1,
foo: "yop",
- },
+ bar: "bar-blup",
+ }, {
+ id: 2,
+ foo: "yop",
+ bar: "bar-yop",
+ }, {
+ id: 3,
+ foo: "blup",
+ bar: "bar-blup",
+ }
]
},
'ir.exports': {
@@ -239,7 +249,7 @@ QUnit.module('widgets', {
},
});
// Open the export modal
- await testUtils.dom.click(list.$('.o_data_row .o_list_record_selector input'));
+ await testUtils.dom.click(list.$('thead .o_list_record_selector input'));
await testUtils.dom.click(list.sidebar.$('.o_dropdown_toggler_btn:contains(Action)'));
await testUtils.dom.click(list.sidebar.$('a:contains(Export)'));
@@ -257,6 +267,108 @@ QUnit.module('widgets', {
assert.strictEqual($('.modal .o_fields_list li').length, 1, "There should be only one field in list");
list.destroy();
});
+
+ QUnit.test('Direct export button invisible', async function (assert) {
+ assert.expect(1)
+
+ let list = await createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: `<tree export_xlsx="0"><field name="foo"/></tree>`,
+ });
+ assert.containsNone(list, '.o_list_export_xlsx')
+ list.destroy();
+ });
+
+ QUnit.test('Direct export list ', async function (assert) {
+ assert.expect(2)
+
+ let list = await createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: `
+ <tree export_xlsx="1">
+ <field name="foo"/>
+ <field name="bar"/>
+ </tree>`,
+ domain: [['bar', '!=', 'glou']],
+ session: {
+ get_file(args) {
+ let data = JSON.parse(args.data.data);
+ assert.strictEqual(args.url, '/web/export/xlsx', "should call get_file with the correct url");
+ assert.deepEqual(data, {
+ context: {},
+ model: 'partner',
+ domain: [['bar', '!=', 'glou']],
+ groupby: [],
+ ids: false,
+ import_compat: false,
+ fields: [{
+ name: 'foo',
+ label: 'Foo',
+ }, {
+ name: 'bar',
+ label: 'Bar',
+ }]
+ }, "should be called with correct params")
+ args.complete();
+ },
+ },
+ });
+
+ // select a record => should not be taken into account
+ await testUtils.dom.click(list.$('.o_data_row .o_list_record_selector input')[0]);
+ // Download
+ await testUtils.dom.click(list.$buttons.find('.o_list_export_xlsx'));
+
+ list.destroy();
+ });
+
+ QUnit.test('Direct export grouped list ', async function (assert) {
+ assert.expect(2)
+
+ let list = await createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ debug: true,
+ arch: `
+ <tree>
+ <field name="foo"/>
+ <field name="bar"/>
+ </tree>`,
+ groupBy: ['foo', 'bar'],
+ domain: [['bar', '!=', 'glou']],
+ session: {
+ get_file(args) {
+ let data = JSON.parse(args.data.data);
+ assert.strictEqual(args.url, '/web/export/xlsx', "should call get_file with the correct url");
+ assert.deepEqual(data, {
+ context: {},
+ model: 'partner',
+ domain: [['bar', '!=', 'glou']],
+ groupby: ['foo', 'bar'],
+ ids: false,
+ import_compat: false,
+ fields: [{
+ name: 'foo',
+ label: 'Foo',
+ }, {
+ name: 'bar',
+ label: 'Bar',
+ }]
+ }, "should be called with correct params")
+ args.complete();
+ },
+ },
+ });
+
+ await testUtils.dom.click(list.$buttons.find('.o_list_export_xlsx'));
+
+ list.destroy();
+ });
});
});