diff options
Diffstat (limited to 'addons/web/static/tests')
| -rw-r--r-- | addons/web/static/tests/chrome/action_manager_tests.js | 63 | ||||
| -rw-r--r-- | addons/web/static/tests/chrome/keyboard_navigation_mixin_tests.js | 31 | ||||
| -rw-r--r-- | addons/web/static/tests/core/pyeval_tests.js | 2 | ||||
| -rw-r--r-- | addons/web/static/tests/fields/relational_fields_tests.js | 310 | ||||
| -rw-r--r-- | addons/web/static/tests/helpers/mock_server.js | 6 | ||||
| -rw-r--r-- | addons/web/static/tests/helpers/test_utils.js | 48 | ||||
| -rw-r--r-- | addons/web/static/tests/views/calendar_tests.js | 95 | ||||
| -rw-r--r-- | addons/web/static/tests/views/graph_tests.js | 16 | ||||
| -rw-r--r-- | addons/web/static/tests/views/kanban_tests.js | 156 | ||||
| -rw-r--r-- | addons/web/static/tests/views/list_tests.js | 43 | ||||
| -rw-r--r-- | addons/web/static/tests/views/pivot_tests.js | 33 |
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(); + }); });}); |
