summaryrefslogtreecommitdiff
path: root/addons/web/static/tests
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/tests')
-rw-r--r--addons/web/static/tests/chrome/action_manager_tests.js63
-rw-r--r--addons/web/static/tests/chrome/keyboard_navigation_mixin_tests.js31
-rw-r--r--addons/web/static/tests/core/pyeval_tests.js2
-rw-r--r--addons/web/static/tests/fields/relational_fields_tests.js310
-rw-r--r--addons/web/static/tests/helpers/mock_server.js6
-rw-r--r--addons/web/static/tests/helpers/test_utils.js48
-rw-r--r--addons/web/static/tests/views/calendar_tests.js95
-rw-r--r--addons/web/static/tests/views/graph_tests.js16
-rw-r--r--addons/web/static/tests/views/kanban_tests.js156
-rw-r--r--addons/web/static/tests/views/list_tests.js43
-rw-r--r--addons/web/static/tests/views/pivot_tests.js33
11 files changed, 737 insertions, 66 deletions
diff --git a/addons/web/static/tests/chrome/action_manager_tests.js b/addons/web/static/tests/chrome/action_manager_tests.js
index dfafdc85801..fd29e91c73c 100644
--- a/addons/web/static/tests/chrome/action_manager_tests.js
+++ b/addons/web/static/tests/chrome/action_manager_tests.js
@@ -12,6 +12,7 @@ var ListController = require('web.ListController');
var StandaloneFieldManagerMixin = require('web.StandaloneFieldManagerMixin');
var RamStorage = require('web.RamStorage');
var ReportService = require('web.ReportService');
+var SessionStorageService = require('web.SessionStorageService');
var testUtils = require('web.test_utils');
var Widget = require('web.Widget');
var createActionManager = testUtils.createActionManager;
@@ -2860,6 +2861,68 @@ QUnit.module('ActionManager', {
actionManager.destroy();
});
+ QUnit.test("current act_window action is stored in session_storage", function (assert) {
+ assert.expect(1);
+
+ var expectedAction = _.extend({}, _.findWhere(this.actions, {id: 3}), {
+ context: {},
+ });
+ var actionManager = createActionManager({
+ actions: this.actions,
+ archs: this.archs,
+ data: this.data,
+ services: [SessionStorageService.extend({
+ setItem: function (key, value) {
+ assert.strictEqual(value, JSON.stringify(expectedAction),
+ "should store the executed action in the sessionStorage");
+ },
+ })],
+ });
+
+ actionManager.doAction(3);
+
+ actionManager.destroy();
+ });
+
+ QUnit.test("store evaluated context of current action in session_storage", function (assert) {
+ // this test ensures that we don't store stringified instances of
+ // CompoundContext in the session_storage, as they would be meaningless
+ // once restored
+ assert.expect(1);
+
+ var expectedAction = _.extend({}, _.findWhere(this.actions, {id: 4}), {
+ context: {
+ active_model: 'partner',
+ active_id: 1,
+ active_ids: [1],
+ },
+ });
+ var checkSessionStorage = false;
+ var actionManager = createActionManager({
+ actions: this.actions,
+ archs: this.archs,
+ data: this.data,
+ services: [SessionStorageService.extend({
+ setItem: function (key, value) {
+ if (checkSessionStorage) {
+ assert.strictEqual(value, JSON.stringify(expectedAction),
+ "should correctly store the executed action in the sessionStorage");
+ }
+ },
+ })],
+ });
+
+ // execute an action and open a record in form view
+ actionManager.doAction(3);
+ actionManager.$('.o_list_view .o_data_row:first').click();
+
+ // click on 'Execute action' button (it executes an action with a CompoundContext as context)
+ checkSessionStorage = true;
+ actionManager.$('.o_form_view button:contains(Execute action)').click();
+
+ actionManager.destroy();
+ });
+
QUnit.module('Actions in target="new"');
QUnit.test('can execute act_window actions in target="new"', function (assert) {
diff --git a/addons/web/static/tests/chrome/keyboard_navigation_mixin_tests.js b/addons/web/static/tests/chrome/keyboard_navigation_mixin_tests.js
new file mode 100644
index 00000000000..38f13e7a117
--- /dev/null
+++ b/addons/web/static/tests/chrome/keyboard_navigation_mixin_tests.js
@@ -0,0 +1,31 @@
+odoo.define('web.keyboard_navigation_mixin_tests', function (require) {
+"use strict";
+
+var KeyboardNavigationMixin = require('web.KeyboardNavigationMixin');
+var Widget = require('web.Widget');
+
+QUnit.module('KeyboardNavigationMixin', function () {
+ QUnit.test('aria-keyshortcuts is added on elements with accesskey', function (assert) {
+ assert.expect(1);
+ var $target = $('#qunit-fixture');
+ var KeyboardWidget = Widget.extend(KeyboardNavigationMixin, {
+ start: function () {
+ var $button = $('<button>').text('Click Me!').attr('accesskey', 'o');
+ // we need to define the accesskey because it will not be assigned on invisible buttons
+ this.$el.append($button);
+ return this._super.apply(this, arguments);
+ },
+ });
+ var w = new KeyboardWidget();
+ w.appendTo($target);
+
+ // minimum set of attribute to generate a native event that works with the mixin
+ var e = new Event("keydown");
+ e.key = '';
+ e.altKey = true;
+ w.$el[0].dispatchEvent(e);
+
+ assert.ok(w.$el.find('button[aria-keyshortcuts]')[0], 'the aria-keyshortcuts is set on the button');
+ });
+});
+});
diff --git a/addons/web/static/tests/core/pyeval_tests.js b/addons/web/static/tests/core/pyeval_tests.js
index 89bfa32872b..737466c8c93 100644
--- a/addons/web/static/tests/core/pyeval_tests.js
+++ b/addons/web/static/tests/core/pyeval_tests.js
@@ -508,7 +508,6 @@ QUnit.module('core', function () {
{
"default_attachment_ids": [],
"default_body": "",
- "default_content_subtype": "html",
"default_model": "res.users",
"default_parent_id": false,
"default_res_id": 1
@@ -526,7 +525,6 @@ QUnit.module('core', function () {
assert.deepEqual(result.context, {
"default_attachment_ids": [],
"default_body": "",
- "default_content_subtype": "html",
"default_model": "res.users",
"default_parent_id": false,
"default_res_id": 1,
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index b16d812993e..1012974bb58 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -599,6 +599,35 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('many2one in non edit mode', function (assert) {
+ assert.expect(3);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="trululu"/>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.strictEqual(form.$('a.o_form_uri').length, 1,
+ "should display 1 m2o link in form");
+ assert.strictEqual(form.$('a.o_form_uri').attr('href'), "#id=4&model=partner",
+ "href should contain id and model");
+
+ // Remove value from many2one and then save, there should not have href with id and model on m2o anchor
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_field_many2one input').val('').trigger('keyup').trigger('focusout');
+ form.$buttons.find('.o_form_button_save').click();
+
+ assert.strictEqual(form.$('a.o_form_uri').attr('href'), "#",
+ "href should have #");
+
+ form.destroy();
+ });
+
QUnit.test('many2one searches with correct value', function (assert) {
var done = assert.async();
assert.expect(6);
@@ -1208,8 +1237,167 @@ QUnit.module('relational_fields', {
form.destroy();
});
- QUnit.test('list in form: discard newly added element with empty required field (onchange)', function (assert) {
- assert.expect(8);
+ QUnit.test('list in form: name_get with unique ids (default_get)', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.records[0].display_name = "MyTrululu";
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<sheet>' +
+ '<field name="p">' +
+ '<tree editable="bottom">' +
+ '<field name="trululu"/>' +
+ '</tree>' +
+ '</field>' +
+ '</sheet>' +
+ '</form>',
+ mockRPC: function (route, args) {
+ if (args.method === 'default_get') {
+ return $.when({
+ p: [
+ [0, 0, { trululu: 1 }],
+ [0, 0, { trululu: 1 }]
+ ]
+ });
+ }
+ if (args.method === 'name_get') {
+ assert.deepEqual(args.args[0], _.uniq(args.args[0]),
+ "should not have duplicates in name_get rpc");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ assert.strictEqual(form.$('td.o_data_cell').text(), "MyTrululuMyTrululu",
+ "both records should have the correct display_name for trululu field");
+
+ form.destroy();
+ });
+
+ QUnit.test('list in form: show name of many2one fields in multi-page (default_get)', function (assert) {
+ assert.expect(4);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<sheet>' +
+ '<field name="p">' +
+ '<tree editable="bottom" limit="1">' +
+ '<field name="display_name"/>' +
+ '<field name="trululu"/>' +
+ '</tree>' +
+ '</field>' +
+ '</sheet>' +
+ '</form>',
+ mockRPC: function (route, args) {
+ if (args.method === 'default_get') {
+ return $.when({
+ p: [
+ [0, 0, { display_name: 'record1', trululu: 1 }],
+ [0, 0, { display_name: 'record2', trululu: 2 }]
+ ]
+ });
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ assert.strictEqual(form.$('td.o_data_cell').first().text(),
+ "record1", "should show display_name of 1st record");
+ assert.strictEqual(form.$('td.o_data_cell').first().next().text(),
+ "first record", "should show display_name of trululu of 1st record");
+
+ form.$('button.o_pager_next').click();
+
+ assert.strictEqual(form.$('td.o_data_cell').first().text(),
+ "record2", "should show display_name of 2nd record");
+ assert.strictEqual(form.$('td.o_data_cell').first().next().text(),
+ "second record", "should show display_name of trululu of 2nd record");
+
+ form.destroy();
+ });
+
+ QUnit.test('list in form: discard newly added element with empty required field (onchange in default_get)', function (assert) {
+ // variant of the test "list in form: discard newly added element with
+ // empty required field (default_get)", in which the `default_get`
+ // performs an `onchange` at the same time. This `onchange` may create
+ // some records, which should not be abandoned on discard, similarly
+ // to records created directly by `default_get`
+ assert.expect(7);
+
+ var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY;
+ relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0;
+
+ this.data.partner.onchanges = {
+ product_id: function (obj) {
+ if (obj.product_id === 37) {
+ obj.p = [[0, 0, { display_name: "entry", trululu: false }]];
+ }
+ },
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="product_id"/>' +
+ '<field name="p">' +
+ '<tree editable="bottom">' +
+ '<field name="display_name"/>' +
+ '<field name="trululu" required="1"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ mockRPC: function (route, args) {
+ if (args.method === 'default_get') {
+ return $.when({
+ product_id: 37,
+ });
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ // check that there is a record in the editable list with empty string as required field
+ assert.strictEqual(form.$('.o_data_row').length, 1,
+ "should have a row in the editable list");
+ assert.strictEqual($('td.o_data_cell').first().text(), "entry",
+ "should have the correct displayed name");
+ var requiredField = $('td.o_data_cell.o_required_modifier');
+ assert.strictEqual(requiredField.length, 1,
+ "should have a required field on this record");
+ assert.strictEqual(requiredField.text(), "",
+ "should have empty string in the required field on this record");
+
+ // click on empty required field in editable list record
+ requiredField.click();
+ // click off so that the required field still stay empty
+ $('body').click();
+
+ // record should not be dropped
+ assert.strictEqual(form.$('.o_data_row').length, 1,
+ "should not have dropped record in the editable list");
+ assert.strictEqual($('td.o_data_cell').first().text(), "entry",
+ "should still have the correct displayed name");
+ assert.strictEqual($('td.o_data_cell.o_required_modifier').text(), "",
+ "should still have empty string in the required field");
+
+ relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY;
+ form.destroy();
+ });
+
+ QUnit.test('list in form: discard newly added element with empty required field (onchange after default_get)', function (assert) {
+ // discarding a record from an `onchange` in a `default_get` should not
+ // abandon the record. However, any `onchange` after `default_get`
+ // should be abandoned on discard
+ assert.expect(6);
var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY;
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0;
@@ -1220,7 +1408,6 @@ QUnit.module('relational_fields', {
obj.p = [[0, 0, { display_name: "entry", trululu: false }]];
}
},
- trululu: function () {},
};
var form = createView({
@@ -1262,13 +1449,9 @@ QUnit.module('relational_fields', {
// click off so that the required field still stay empty
$('body').click();
- // record should not be dropped
- assert.strictEqual(form.$('.o_data_row').length, 1,
- "should not have droppped record in the editable list");
- assert.strictEqual($('td.o_data_cell').first().text(), "entry",
- "should still have the correct displayed name");
- assert.strictEqual($('td.o_data_cell.o_required_modifier').text(), "",
- "should still have empty string in the required field");
+ // record should be dropped
+ assert.strictEqual(form.$('.o_data_row').length, 0,
+ "should have dropped record in the editable list");
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY;
form.destroy();
@@ -2243,6 +2426,43 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('x2many list sorted by many2one', function (assert) {
+ assert.expect(3);
+
+ this.data.partner.records[0].p = [1, 2, 4];
+ this.data.partner.fields.trululu.sortable = true;
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="p">' +
+ '<tree>' +
+ '<field name="id"/>' +
+ '<field name="trululu"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.strictEqual(form.$('.o_data_row .o_list_number').text(), '124',
+ "should have correct order initially");
+
+ form.$('.o_list_view thead th:nth(1)').click();
+
+ assert.strictEqual(form.$('.o_data_row .o_list_number').text(), '412',
+ "should have correct order (ASC)");
+
+ form.$('.o_list_view thead th:nth(1)').click();
+
+ assert.strictEqual(form.$('.o_data_row .o_list_number').text(), '214',
+ "should have correct order (DESC)");
+
+ form.destroy();
+ });
+
QUnit.test('many2many list add *many* records, remove, re-add', function (assert) {
assert.expect(5);
@@ -5763,7 +5983,7 @@ QUnit.module('relational_fields', {
rpcCount++;
if (args.method === 'write') {
assert.deepEqual(args.args[1].p, [[0, args.args[1].p[0][1], {
- int_field: 123, product_id: 41
+ foo: false, int_field: 123, product_id: 41,
}]]);
}
return this._super(route, args);
@@ -8921,6 +9141,74 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('one2many form view with action button', function (assert) {
+ // once the action button is clicked, the record is reloaded (via the
+ // on_close handler, executed because the python method does not return
+ // any action, or an ir.action.act_window_close) ; this test ensures that
+ // it reloads the fields of the opened view (i.e. the form in this case).
+ // See https://github.com/odoo/odoo/issues/24189
+ assert.expect(7);
+
+ var data = this.data;
+ data.partner.records[0].p = [2];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: data,
+ res_id: 1,
+ arch: '<form string="Partners">' +
+ '<field name="p">' +
+ '<tree><field name="display_name"/></tree>' +
+ '<form>' +
+ '<button type="action" string="Set Timmy"/>' +
+ '<field name="timmy"/>' +
+ '</form>' +
+ '</field>' +
+ '</form>',
+ archs: {
+ 'partner_type,false,list': '<tree><field name="display_name"/></tree>',
+ },
+ intercepts: {
+ execute_action: function (ev) {
+ data.partner.records[1].display_name = 'new name';
+ data.partner.records[1].timmy = [12];
+ ev.data.on_closed();
+ },
+ },
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ assert.strictEqual(form.$('.o_data_row').length, 1,
+ "there should be one record in the one2many");
+ assert.strictEqual(form.$('.o_data_cell').text(), 'second record',
+ "initial display_name of o2m record should be correct");
+
+ // open one2many record in form view
+ form.$('.o_data_cell:first').click();
+ assert.strictEqual($('.modal .o_form_view').length, 1,
+ "should have opened the form view in a dialog");
+ assert.strictEqual($('.modal .o_form_view .o_data_row').length, 0,
+ "there should be no record in the many2many");
+
+ // click on the action button
+ $('.modal .o_form_view button').click();
+ assert.strictEqual($('.modal .o_data_row').length, 1,
+ "fields in the o2m form view should have been read");
+ assert.strictEqual($('.modal .o_data_cell').text(), 'gold',
+ "many2many subrecord should have been fetched");
+
+ // save the dialog
+ $('.modal .modal-footer .btn-primary').click();
+
+ assert.strictEqual(form.$('.o_data_cell').text(), 'new name',
+ "fields in the o2m list view should have been read as well");
+
+ form.destroy();
+ });
+
QUnit.module('FieldMany2Many');
QUnit.test('many2many kanban: edition', function (assert) {
diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js
index b57b59eb2a7..5afdc3dd447 100644
--- a/addons/web/static/tests/helpers/mock_server.js
+++ b/addons/web/static/tests/helpers/mock_server.js
@@ -116,12 +116,12 @@ var MockServer = Class.extend({
console.log('%c[rpc] response' + route, 'color: blue; font-weight: bold;', JSON.parse(resultString));
}
return JSON.parse(resultString);
- }).fail(function (result) {
+ }, function (result, event) {
var errorString = JSON.stringify(result || false);
if (debug) {
console.log('%c[rpc] response (error) ' + route, 'color: orange; font-weight: bold;', JSON.parse(errorString));
}
- return JSON.parse(errorString);
+ return $.Deferred().reject(errorString, event || $.Event());
});
},
@@ -854,7 +854,7 @@ var MockServer = Class.extend({
*/
_mockReadProgressBar: function (model, kwargs) {
var domain = kwargs.domain;
- var groupBy = kwargs.groupBy;
+ var groupBy = kwargs.group_by;
var progress_bar = kwargs.progress_bar;
var records = this._getRecords(model, domain || []);
diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js
index 2da3eb5ddd9..6c05076ef98 100644
--- a/addons/web/static/tests/helpers/test_utils.js
+++ b/addons/web/static/tests/helpers/test_utils.js
@@ -733,53 +733,6 @@ function patch (target, props) {
});
}
}
-/**
- * In Phantomjs, there is a crash when calling window.getSelection
- * in order for the tests to work, for the specific test that uses it, replace
- * the default window.getSelection by a mock
- *
- * usage:
- * QUnit.test('...',function(done){
- * var unpatchWindowGetSelection = testUtils.patchWindowGetSelection();
- *
- * // do something that needs to use window.getSelection()
- * assert.strictEqual(....);
- *
- * // restore the original function
- * unpatchWindowGetSelection();
- *
- * // finish the test
- * done();
- * })
- *
- * @returns {function} the unpatch function
- */
-function patchWindowGetSelection() {
- var originalWindowGetSelection = window.getSelection;
- window.getSelection = function () {
- return {
- removeAllRanges: function () {},
- addRange: function (range) {},
- getRangeAt: function (index) {
- return {
- startOffset : 0
- };
- },
- anchorNode: {
- parentNode: {
- childNodes: [{
- outerHTML: "@",
- nodeType: 3,
- textContent: '@',
- }],
- },
- },
- };
- };
- return function () {
- window.getSelection = originalWindowGetSelection;
- };
-}
/**
* Unpatches a given Class or Object.
@@ -831,7 +784,6 @@ return $.when(
intercept: intercept,
observe: observe,
patch: patch,
- patchWindowGetSelection: patchWindowGetSelection,
removeSrcAttribute: removeSrcAttribute,
triggerKeypressEvent: triggerKeypressEvent,
triggerMouseEvent: triggerMouseEvent,
diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js
index f1356558a23..9728d8a7c74 100644
--- a/addons/web/static/tests/views/calendar_tests.js
+++ b/addons/web/static/tests/views/calendar_tests.js
@@ -1895,6 +1895,101 @@ QUnit.module('Views', {
calendar.destroy();
});
+
+ QUnit.test('form_view_id attribute works (for creating events)', function (assert) {
+ assert.expect(1);
+
+ var calendar = createView({
+ View: CalendarView,
+ model: 'event',
+ data: this.data,
+ arch: '<calendar class="o_calendar_test" '+
+ 'date_start="start" '+
+ 'date_stop="stop" '+
+ 'mode="month" '+
+ 'form_view_id="42">'+
+ '<field name="name"/>'+
+ '</calendar>',
+ archs: archs,
+ viewOptions: {
+ initialDate: initialDate,
+ },
+ mockRPC: function (route, args) {
+ if (args.method === "create") {
+ // we simulate here the case where a create call with just
+ // the field name fails. This is a normal flow, the server
+ // reject the create rpc (quick create), then the web client
+ // fall back to a form view. This happens typically when a
+ // model has required fields
+ return $.Deferred().reject('None shall pass!');
+ }
+ return this._super(route, args);
+ },
+ intercepts: {
+ do_action: function (event) {
+ assert.strictEqual(event.data.action.views[0][0], 42,
+ "should do a do_action with view id 42");
+ },
+ },
+ });
+
+ var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)');
+ testUtils.triggerMouseEvent($cell, "mousedown");
+ testUtils.triggerMouseEvent($cell, "mouseup");
+ var $input = $('.modal-body input:first');
+ $input.val("It's just a fleshwound").trigger('input');
+ $('.modal button.btn:contains(Create)').trigger('click');
+
+ calendar.destroy();
+ });
+
+ QUnit.test('calendar fallback to form view id in action if necessary', function (assert) {
+ assert.expect(1);
+
+ var calendar = createView({
+ View: CalendarView,
+ model: 'event',
+ data: this.data,
+ arch: '<calendar class="o_calendar_test" '+
+ 'date_start="start" '+
+ 'date_stop="stop" '+
+ 'mode="month"> '+
+ '<field name="name"/>'+
+ '</calendar>',
+ archs: archs,
+ viewOptions: {
+ initialDate: initialDate,
+ action: {views: [[1, 'kanban'], [43, 'form']]}
+ },
+ mockRPC: function (route, args) {
+ if (args.method === "create") {
+ // we simulate here the case where a create call with just
+ // the field name fails. This is a normal flow, the server
+ // reject the create rpc (quick create), then the web client
+ // fall back to a form view. This happens typically when a
+ // model has required fields
+ return $.Deferred().reject('None shall pass!');
+ }
+ return this._super(route, args);
+ },
+ intercepts: {
+ do_action: function (event) {
+ assert.strictEqual(event.data.action.views[0][0], 43,
+ "should do a do_action with view id 43");
+ },
+ },
+ });
+
+ var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)');
+ testUtils.triggerMouseEvent($cell, "mousedown");
+ testUtils.triggerMouseEvent($cell, "mouseup");
+ var $input = $('.modal-body input:first');
+ $input.val("It's just a fleshwound").trigger('input');
+ $('.modal button.btn:contains(Create)').trigger('click');
+
+ calendar.destroy();
+ });
+
});
});
diff --git a/addons/web/static/tests/views/graph_tests.js b/addons/web/static/tests/views/graph_tests.js
index a13130e4b27..028eb4cb3a3 100644
--- a/addons/web/static/tests/views/graph_tests.js
+++ b/addons/web/static/tests/views/graph_tests.js
@@ -602,6 +602,22 @@ QUnit.module('Views', {
done();
});
});
+
+ QUnit.test('graph view crash when moving from search view using Down key', function (assert) {
+ assert.expect(1);
+
+ var graph = createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph string="Partners" type="pie">' +
+ '<field name="bar"/>' +
+ '</graph>',
+ });
+ graph.renderer.giveFocus();
+ assert.ok(true,"should not generate any error");
+ graph.destroy();
+ });
});
});
diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js
index 04003c568a3..04c1c603118 100644
--- a/addons/web/static/tests/views/kanban_tests.js
+++ b/addons/web/static/tests/views/kanban_tests.js
@@ -3273,6 +3273,132 @@ QUnit.module('Views', {
kanban.destroy();
});
+ // XXX test deactivated in saas~11.2 as kanban archiving has been removed (and readded in saas~11.4)
+ QUnit.skip('column progressbars on archiving records update counter', function (assert) {
+ assert.expect(4);
+
+ // add active field on partner model and make all records active
+ this.data.partner.fields.active = {string: 'Active', type: 'char', default: true};
+
+ var kanban = createView({
+ View: KanbanView,
+ model: 'partner',
+ data: this.data,
+ arch:
+ '<kanban>' +
+ '<field name="active"/>' +
+ '<field name="bar"/>' +
+ '<field name="int_field"/>' +
+ '<progressbar field="foo" colors=\'{"yop": "success", "gnap": "warning", "blip": "danger"}\' sum_field="int_field"/>' +
+ '<templates><t t-name="kanban-box">' +
+ '<div>' +
+ '<field name="name"/>' +
+ '</div>' +
+ '</t></templates>' +
+ '</kanban>',
+ groupBy: ['bar'],
+ });
+
+ assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_counter_side').text(), "36",
+ "counter should contain the correct value");
+ assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_counter_progress > .progress-bar:first').data('originalTitle'), "1 yop",
+ "the counter progressbars should be correctly displayed");
+
+ // archive all records of the second columns
+ kanban.$('.o_kanban_group:eq(1) .o_column_archive').click();
+
+ assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_counter_side').text(), "0",
+ "counter should contain the correct value");
+ assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_counter_progress > .progress-bar:first').data('originalTitle'), "0 yop",
+ "the counter progressbars should have been correctly updated");
+
+ kanban.destroy();
+ });
+
+ QUnit.skip('kanban with progressbars: correctly update env when archiving records', function (assert) {
+ assert.expect(3);
+
+ // add active field on partner model and make all records active
+ this.data.partner.fields.active = {string: 'Active', type: 'char', default: true};
+
+ var kanban = createView({
+ View: KanbanView,
+ model: 'partner',
+ data: this.data,
+ arch:
+ '<kanban>' +
+ '<field name="active"/>' +
+ '<field name="bar"/>' +
+ '<field name="int_field"/>' +
+ '<progressbar field="foo" colors=\'{"yop": "success", "gnap": "warning", "blip": "danger"}\' sum_field="int_field"/>' +
+ '<templates><t t-name="kanban-box">' +
+ '<div>' +
+ '<field name="name"/>' +
+ '</div>' +
+ '</t></templates>' +
+ '</kanban>',
+ groupBy: ['bar'],
+ intercepts: {
+ env_updated: function (ev) {
+ assert.step(ev.data.ids);
+ },
+ },
+ });
+
+ // archive all records of the first column
+ kanban.$('.o_kanban_group:first .o_column_archive').click();
+
+ assert.verifySteps([
+ [1, 2, 3, 4],
+ [1, 2, 3],
+ ]);
+
+ kanban.destroy();
+ });
+
+ QUnit.test('RPCs when (re)loading kanban view progressbars', function (assert) {
+ assert.expect(9);
+
+ var kanban = createView({
+ View: KanbanView,
+ model: 'partner',
+ data: this.data,
+ arch:
+ '<kanban>' +
+ '<field name="bar"/>' +
+ '<field name="int_field"/>' +
+ '<progressbar field="foo" colors=\'{"yop": "success", "gnap": "warning", "blip": "danger"}\' sum_field="int_field"/>' +
+ '<templates><t t-name="kanban-box">' +
+ '<div>' +
+ '<field name="name"/>' +
+ '</div>' +
+ '</t></templates>' +
+ '</kanban>',
+ groupBy: ['bar'],
+ mockRPC: function (route, args) {
+ assert.step(args.method || route);
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ kanban.reload();
+
+ assert.verifySteps([
+ // initial load
+ 'read_group',
+ '/web/dataset/search_read',
+ '/web/dataset/search_read',
+ 'read_progress_bar',
+ // reload
+ 'read_group',
+ '/web/dataset/search_read',
+ '/web/dataset/search_read',
+ 'read_progress_bar',
+ ]);
+
+ kanban.destroy();
+ });
+
QUnit.test('drag & drop records grouped by m2o with progressbar', function (assert) {
assert.expect(4);
@@ -3342,7 +3468,7 @@ QUnit.module('Views', {
"Initial count should be Three");
kanban.$('.bg-success-full').click();
var lastCount = parseInt(kanban.$('.o_kanban_counter_side').eq(1).text());
- assert.strictEqual(lastCount, 0,
+ assert.strictEqual(lastCount, 1,
"kanban counters should vary according to what subgroup is selected");
kanban.destroy();
@@ -3638,6 +3764,34 @@ QUnit.module('Views', {
kanban.destroy();
});
+
+ QUnit.test('keyboard navigation on kanban when the focus is on a link that' +
+ 'has an action and the kanban has no oe_kanban_global_... class', function(assert){
+ assert.expect(1)
+ var kanban = createView({
+ View: KanbanView,
+ model: 'partner',
+ data: this.data,
+ arch: '<kanban class="o_kanban_test"><templates><t t-name="kanban-box">' +
+ '<div><a type="edit">Edit</a></div>' +
+ '</t></templates></kanban>',
+ });
+
+ testUtils.intercept(kanban, 'switch_view', function (event) {
+ assert.deepEqual(event.data, {
+ view_type: 'form',
+ res_id: 1,
+ mode: 'edit',
+ model: 'partner',
+ },'When selecting focusing a card and hitting ENTER, the first link or button is clicked');
+ });
+ kanban.$('.o_kanban_record').first().focus().trigger($.Event('keydown', {
+ keyCode: $.ui.keyCode.ENTER,
+ which: $.ui.keyCode.ENTER,
+ }));
+
+ kanban.destroy();
+ });
});
});
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index c948528dcc6..fb4ccee9f8b 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -2414,7 +2414,7 @@ QUnit.module('Views', {
// Press 'Tab' -> should go to next line
// add a value in the cell because the Tab on an empty first cell would activate the next widget in the view
- list.$('.o_selected_row input').val(11).trigger('input');
+ list.$('.o_selected_row input').val(11).trigger('input');
list.$('.o_selected_row input').trigger({type: 'keydown', which: 9});
assert.ok(list.$('.o_data_row:nth(3)').hasClass('o_selected_row'),
"fourth row should be in edition");
@@ -3431,6 +3431,47 @@ QUnit.module('Views', {
testUtils.unpatch(mixins.ParentedMixin);
});
+ QUnit.test('concurrent reloads finishing in inverse order', function (assert) {
+ assert.expect(3);
+
+ var blockSearchRead = false;
+ var def = $.Deferred();
+ var list = createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '<tree><field name="foo"/></tree>',
+ mockRPC: function (route) {
+ var result = this._super.apply(this, arguments);
+ if (route === '/web/dataset/search_read' && blockSearchRead) {
+ return $.when(def).then(_.constant(result));
+ }
+ return result;
+ },
+ });
+
+ assert.strictEqual(list.$('.o_list_view .o_data_row').length, 4,
+ "list view should contain 4 records");
+
+ // reload with a domain (this request is blocked)
+ blockSearchRead = true;
+ list.reload({domain: [['foo', '=', 'yop']]});
+
+ assert.strictEqual(list.$('.o_list_view .o_data_row').length, 4,
+ "list view should still contain 4 records (search_read being blocked)");
+
+ // reload without the domain
+ blockSearchRead = false;
+ list.reload({domain: []});
+
+ // unblock the RPC
+ def.resolve();
+ assert.strictEqual(list.$('.o_list_view .o_data_row').length, 4,
+ "list view should still contain 4 records");
+
+ list.destroy();
+ });
+
QUnit.test('list view on a "noCache" model', function (assert) {
assert.expect(8);
diff --git a/addons/web/static/tests/views/pivot_tests.js b/addons/web/static/tests/views/pivot_tests.js
index a1d9e1fae1f..4d7358f2574 100644
--- a/addons/web/static/tests/views/pivot_tests.js
+++ b/addons/web/static/tests/views/pivot_tests.js
@@ -1145,4 +1145,37 @@ QUnit.module('Views', {
pivot.destroy();
});
+
+ QUnit.test('parallel data loading should discard all but the last one', function (assert) {
+ assert.expect(2);
+
+ var def;
+
+ var pivot = createView({
+ View: PivotView,
+ model: "partner",
+ data: this.data,
+ arch: '<pivot>' +
+ '<field name="foo" type="measure"/>' +
+ '</pivot>',
+ mockRPC: function (route, args) {
+ var result = this._super.apply(this, arguments);
+ if (args.method === 'read_group') {
+ return $.when(def).then(_.constant(result));
+ }
+ return result;
+ },
+ });
+
+ def = $.Deferred();
+ pivot.update({groupBy: ['product_id']});
+ pivot.update({groupBy: ['product_id', 'customer']});
+ def.resolve();
+
+ assert.strictEqual(pivot.$('.o_pivot_cell_value').length, 6,
+ "should have 6 cells");
+ assert.strictEqual(pivot.$('tbody tr').length, 6,
+ "should have 6 rows");
+ pivot.destroy();
+ });
});});