diff options
| author | Christophe Simonis <chs@odoo.com> | 2019-07-29 14:10:30 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2019-07-29 14:10:30 +0200 |
| commit | d5e1fd16b4fd796cc25b2b2d694dadb375110ce7 (patch) | |
| tree | 35d130b4161985cd9ca3071a59980b52e8401be5 /addons/web | |
| parent | ae71f95371b4a5c99c10a23e3553c5badc45a1ac (diff) | |
| parent | cda4f3c308ca233aead81f5ae97d250b5b7bf82f (diff) | |
[MERGE] forward port branch saas-12.4 up to cda4f3c308c
Diffstat (limited to 'addons/web')
19 files changed, 402 insertions, 88 deletions
diff --git a/addons/web/i18n/km.po b/addons/web/i18n/km.po index ad4e16ada6e..292c6c554c0 100644 --- a/addons/web/i18n/km.po +++ b/addons/web/i18n/km.po @@ -526,7 +526,7 @@ msgstr "កំណត់យក" #: code:addons/web/static/src/js/views/list/list_controller.js:154 #, python-format msgid "Archive" -msgstr "" +msgstr "សម្រេចគោលដៅ" #. module: web #. openerp-web @@ -1239,7 +1239,7 @@ msgstr "" #: code:addons/web/static/src/js/views/form/form_controller.js:184 #, python-format msgid "Duplicate" -msgstr "" +msgstr "ត្រួត" #. module: web #. openerp-web @@ -1440,7 +1440,7 @@ msgstr "" #: code:addons/web/static/src/xml/base.xml:1353 #, python-format msgid "Favorites" -msgstr "" +msgstr "ចំណូលចិត្ត" #. module: web #. openerp-web @@ -1901,14 +1901,14 @@ msgstr "" #: code:addons/web/static/src/xml/kanban.xml:42 #, python-format msgid "Load more... (" -msgstr "" +msgstr "ផ្ទុកបន្ថែម" #. module: web #. openerp-web #: code:addons/web/static/src/js/chrome/loading.js:58 #, python-format msgid "Loading" -msgstr "" +msgstr "ដំណើរការ" #. module: web #. openerp-web diff --git a/addons/web/static/src/js/chrome/action_manager_act_window.js b/addons/web/static/src/js/chrome/action_manager_act_window.js index ef9273bb484..1a08bf59a9a 100644 --- a/addons/web/static/src/js/chrome/action_manager_act_window.js +++ b/addons/web/static/src/js/chrome/action_manager_act_window.js @@ -295,6 +295,7 @@ ActionManager.include({ } var lazyViewDef; + var lazyControllerID; if (lazyView) { // if the main view is lazy-loaded, its (lazy-loaded) controller is inserted // into the controller stack (so that breadcrumbs can be correctly computed), @@ -304,6 +305,7 @@ ActionManager.include({ // this controller being lazy-loaded, this call is actually sync lazyViewDef = self._createViewController(action, lazyView.type, {}, {lazy: true}) .then(function (lazyLoadedController) { + lazyControllerID = lazyLoadedController.jsID; self.controllerStack.push(lazyLoadedController.jsID); }); } @@ -322,7 +324,13 @@ ActionManager.include({ action.controllerID = controller.jsID; return self._executeAction(action, options); }) - .guardedCatch(self._destroyWindowAction.bind(self, action)); + .guardedCatch(function () { + if (lazyControllerID) { + var index = self.controllerStack.indexOf(lazyControllerID); + self.controllerStack = self.controllerStack.slice(0, index); + } + self._destroyWindowAction(action); + }); }); }, /** diff --git a/addons/web/static/src/js/views/form/form_controller.js b/addons/web/static/src/js/views/form/form_controller.js index 83edf12212b..c80f939ddee 100644 --- a/addons/web/static/src/js/views/form/form_controller.js +++ b/addons/web/static/src/js/views/form/form_controller.js @@ -661,9 +661,12 @@ var FormController = BasicController.extend({ * @private * @param {OdooEvent} event */ - _onFormDialogDiscarded: function(e) { - e.stopPropagation(); - this.renderer.focusLastActivatedWidget(); + _onFormDialogDiscarded: function(ev) { + ev.stopPropagation(); + var isFocused = this.renderer.focusLastActivatedWidget(); + if (ev.data.callback) { + ev.data.callback(_.str.toBool(isFocused)); + } }, /** * Opens a one2many record (potentially new) in a dialog. This handler is diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js index 98331c9b409..7f37d1b9059 100644 --- a/addons/web/static/src/js/views/form/form_renderer.js +++ b/addons/web/static/src/js/views/form/form_renderer.js @@ -195,8 +195,11 @@ var FormRenderer = BasicRenderer.extend({ * field. */ focusLastActivatedWidget: function () { - this._activateNextFieldWidget(this.state, this.lastActivatedFieldIndex - 1, - { noAutomaticCreate: true }); + if (this.lastActivatedFieldIndex !== -1) { + return this._activateNextFieldWidget(this.state, this.lastActivatedFieldIndex - 1, + { noAutomaticCreate: true }); + } + return false; }, /** * returns the active tab pages for each notebook diff --git a/addons/web/static/src/js/views/kanban/kanban_column_progressbar.js b/addons/web/static/src/js/views/kanban/kanban_column_progressbar.js index 0c484931aae..6a9caa99a39 100644 --- a/addons/web/static/src/js/views/kanban/kanban_column_progressbar.js +++ b/addons/web/static/src/js/views/kanban/kanban_column_progressbar.js @@ -150,7 +150,7 @@ var KanbanColumnProgressBar = Widget.extend({ }); // Adapt active state - $bar.toggleClass('active progress-bar-striped', key === self.activeFilter); + $bar.toggleClass('progress-bar-animated progress-bar-striped', key === self.activeFilter); // Adapt width $bar.removeClass('o_bar_has_records transition-off'); diff --git a/addons/web/static/src/js/views/list/list_editable_renderer.js b/addons/web/static/src/js/views/list/list_editable_renderer.js index 3e60403fab2..2740c8e1567 100644 --- a/addons/web/static/src/js/views/list/list_editable_renderer.js +++ b/addons/web/static/src/js/views/list/list_editable_renderer.js @@ -480,34 +480,41 @@ ListRenderer.include({ * Returns the relative width according to the widget or the field type. * @see _renderHeader * - * @param {Object} column a `field` arch node + * @param {Object} column an arch node + * @returns {integer | string} either a weight factor (number) or a css + * width description (string) */ _getColumnWidthFactor: function (column) { - if (column.attrs.width) { - // the width attribute has precedence on the width factor - return 0; + if (!this.state.fieldsInfo.list[column.attrs.name]) { + // Unnamed columns get default value + return 1; + } + var field = this.state.fields[column.attrs.name]; + if (!field) { + // this is not a field. Probably a button or something of unknown + // width. + return 1; } - var fieldType = this.state.fields[column.attrs.name].type; var widget = this.state.fieldsInfo.list[column.attrs.name].Widget.prototype; if ('widthFactor' in widget) { return widget.widthFactor; } - switch (fieldType) { + switch (field.type) { case 'binary': return 1; - case 'boolean': return 0.4; + case 'boolean': return '40px'; case 'char': return 1; - case 'date': return 1; - case 'datetime': return 1.5; - case 'float': return 1; + case 'date': return '100px'; + case 'datetime': return '150px'; + case 'float': return '100px'; case 'html': return 3; - case 'integer': return 0.8; - case 'many2many': return 2.2; - case 'many2one': return 1.5; + case 'integer': return '80px'; + case 'many2many': return 2; + case 'many2one': return 2; case 'monetary': return 1.2; - case 'one2many': return 2.2; + case 'one2many': return 2.5; case 'reference': return 1.5; case 'selection': return 1.5; - case 'text': return 3; + case 'text': return 2.5; default: return 1; } }, @@ -720,24 +727,26 @@ ListRenderer.include({ }); }, /** - * Overridden to set weights on columns for the fixed layout. + * Overridden to set weights or explicit width on columns for the fixed layout. * * @override * @private */ _processColumns: function () { this._super.apply(this, arguments); - if (this.editable) { var self = this; this.columns.forEach(function (column) { - if (column.attrs.width_factor) { + if (column.attrs.width) { + // nothing to do + } else if (column.attrs.width_factor) { column.attrs.widthFactor = parseFloat(column.attrs.width_factor, 10); } else { - if (column.tag === 'field') { - column.attrs.widthFactor = self._getColumnWidthFactor(column); + var factor = self._getColumnWidthFactor(column); + if (typeof factor === 'string') { + column.attrs.width = factor; } else { - column.attrs.widthFactor = 1; + column.attrs.widthFactor = factor; } } }); @@ -818,15 +827,18 @@ ListRenderer.include({ var $thead = this._super.apply(this, arguments); if (this.editable) { + // we compute the sum of the weights for each columns, excluding + // those with a fixed width. var totalWidth = this.columns.reduce(function (acc, column) { - return acc + column.attrs.widthFactor; + return acc + (column.attrs.widthFactor || 0); }, 0); this.columns.forEach(function (column) { - var $cell = $thead.find('th[data-name=' + column.attrs.name + ']'); - if (column.attrs.width) { - $cell.css('width', column.attrs.width); - } else if (column.attrs.widthFactor) { - $cell.css('width', (column.attrs.widthFactor / totalWidth * 100) + '%'); + if (column.attrs.width || column.attrs.widthFactor) { + var width = column.attrs.width || ((column.attrs.widthFactor / totalWidth * 100) + '%'); + var $th = $thead.find('th[data-name=' + column.attrs.name + ']'); + if ($th.data('name')) { + $th.css('width', width); + } } }); } diff --git a/addons/web/static/src/js/views/list/list_model.js b/addons/web/static/src/js/views/list/list_model.js index 41178498c96..25fdf28954c 100644 --- a/addons/web/static/src/js/views/list/list_model.js +++ b/addons/web/static/src/js/views/list/list_model.js @@ -52,17 +52,19 @@ odoo.define('web.ListModel', function (require) { var model = records[0].model; var recordResIds = _.pluck(records, 'res_id'); var fieldNames = records[0].getFieldNames(); + var context = records[0].getContext(); return this._rpc({ model: model, method: 'write', args: [recordResIds, changes], - context: records[0].getContext(), + context: context, }).then(function () { return self._rpc({ model: model, method: 'read', args: [recordResIds, fieldNames], + context: context, }); }).then(function (results) { results.forEach(function (data) { @@ -94,14 +96,7 @@ odoo.define('web.ListModel', function (require) { options = options || {}; options.fetchRecordsWithGroups = true; return this._super(list, options).then(function (result) { - var prom; - if (!list.parentID) { - // groupbys buttons are only displayed on the first level of - // groupby so no need to fetch the extra fields for inner - // groups - prom = self._readGroupExtraFields(list); - } - return Promise.resolve(prom).then(_.constant(result)); + return self._readGroupExtraFields(list).then(_.constant(result)); }); }, /** diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js index b4d6e5e036b..8ed13c4d7da 100644 --- a/addons/web/static/src/js/views/list/list_renderer.js +++ b/addons/web/static/src/js/views/list/list_renderer.js @@ -599,16 +599,22 @@ var ListRenderer = BasicRenderer.extend({ var colspanBeforeAggregate; if (firstAggregateIndex !== -1) { // if there are aggregates, the first $th goes until the first - // aggregate then all cells between aggregates are rendered, then - // there is a last $th for the pager + // aggregate then all cells between aggregates are rendered colspanBeforeAggregate = firstAggregateIndex; var lastAggregateIndex = _.findLastIndex(this.columns, function (column) { return column.tag === 'field' && _.contains(aggregateKeys, column.attrs.name); }); cells = cells.concat(aggregateCells.slice(firstAggregateIndex, lastAggregateIndex + 1)); - cells.push($('<th>').attr('colspan', this.columns.length - 1 - lastAggregateIndex)); + var colSpan = this.columns.length - 1 - lastAggregateIndex; + if (colSpan > 0) { + cells.push($('<th>').attr('colspan', colSpan)); + } } else { - colspanBeforeAggregate = this.columns.length; + var colN = this.columns.length; + colspanBeforeAggregate = colN > 1 ? colN - 1 : 1; + if (colN > 1) { + cells.push($('<th>')); + } } if (this.hasSelectors) { colspanBeforeAggregate += 1; @@ -616,11 +622,11 @@ var ListRenderer = BasicRenderer.extend({ $th.attr('colspan', colspanBeforeAggregate); if (group.isOpen && !group.groupedBy.length && (group.count > group.data.length)) { - var $lastCell = cells[cells.length - 1]; var $pager = this._renderGroupPager(group); - $lastCell.addClass('o_group_pager').append($pager); + var $lastCell = cells[cells.length - 1]; + $lastCell.append($pager); } - if (groupLevel === 0 && group.isOpen && this.groupbys[groupBy]) { + if (group.isOpen && this.groupbys[groupBy]) { var $buttons = this._renderGroupButtons(group, this.groupbys[groupBy]); if ($buttons.length) { var $buttonSection = $('<div>', { diff --git a/addons/web/static/src/js/views/pivot/pivot_model.js b/addons/web/static/src/js/views/pivot/pivot_model.js index bf7c70e2859..a544ab15593 100644 --- a/addons/web/static/src/js/views/pivot/pivot_model.js +++ b/addons/web/static/src/js/views/pivot/pivot_model.js @@ -430,6 +430,7 @@ var PivotModel = AbstractModel.extend({ } if ('domain' in params) { this.data.domain = params.domain; + this.initialDomain = params.domain; } else { this.data.domain = this.initialDomain; } diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js index 1fb20232495..2eb3b7787e8 100644 --- a/addons/web/static/src/js/views/view_dialogs.js +++ b/addons/web/static/src/js/views/view_dialogs.js @@ -243,8 +243,13 @@ var FormViewDialog = ViewDialog.extend({ * @override */ _focusOnClose: function() { - this.trigger_up('form_dialog_discarded'); - return true; + var isFocusSet = false; + this.trigger_up('form_dialog_discarded', { + callback: function (isFocused) { + isFocusSet = isFocused; + }, + }); + return isFocusSet; }, /** @@ -426,8 +431,13 @@ var SelectCreateDialog = ViewDialog.extend({ * @override */ _focusOnClose: function() { - this.trigger_up('form_dialog_discarded'); - return true; + var isFocusSet = false; + this.trigger_up('form_dialog_discarded', { + callback: function (isFocused) { + isFocusSet = isFocused; + }, + }); + return isFocusSet; }, }); diff --git a/addons/web/static/src/scss/layout_standard.scss b/addons/web/static/src/scss/layout_standard.scss index e992d25b796..5cedf578c48 100644 --- a/addons/web/static/src/scss/layout_standard.scss +++ b/addons/web/static/src/scss/layout_standard.scss @@ -1,5 +1,8 @@ .o_standard_footer { margin-top: 200px; + .list-inline-item { + white-space: nowrap; + } } .o_report_layout_standard { diff --git a/addons/web/static/src/scss/list_view.scss b/addons/web/static/src/scss/list_view.scss index 3d25258eac5..a18d9f70083 100644 --- a/addons/web/static/src/scss/list_view.scss +++ b/addons/web/static/src/scss/list_view.scss @@ -125,18 +125,18 @@ padding-bottom: 0; } } - .o_group_pager { - position: relative; + .o_pager { + cursor: text; + user-select: none; + text-align: right; + padding-top: 0 !important; + padding-bottom: 0 !important; + margin-top: -2px; + margin-bottom: -2px; - .o_pager { - @include o-position-absolute(0, 16px); - cursor: text; - user-select: none; - padding-top: 3px !important; - padding-bottom: 0 !important; - } .o_pager_previous, .o_pager_next { max-height: 30px; + padding: 0 5px; background-color: $o-list-group-header-color; &:hover { background-color: darken($o-list-group-header-color, 10%); diff --git a/addons/web/static/src/xml/kanban.xml b/addons/web/static/src/xml/kanban.xml index 57104b4e8ad..aadda505b44 100644 --- a/addons/web/static/src/xml/kanban.xml +++ b/addons/web/static/src/xml/kanban.xml @@ -110,7 +110,7 @@ <div class="o_kanban_counter_progress progress"> <t t-foreach="widget.colors" t-as="color"> <t t-set="count" t-value="widget.subgroupCounts and widget.subgroupCounts[color] or 0"/> - <div t-attf-class="progress-bar transition-off bg-#{color_value}-full#{count ? ' o_bar_has_records' : ''}#{widget.activeFilter ? ' active progress-bar-striped' : ''}" + <div t-attf-class="progress-bar transition-off bg-#{color_value}-full#{count ? ' o_bar_has_records' : ''}#{widget.activeFilter ? ' progress-bar-animated progress-bar-striped' : ''}" t-att-data-filter="color" t-attf-data-original-title="#{count} #{color}" aria-valuemin="0" aria-valuemax="100" t-att-aria-valuenow="count*100" role="progressbar" diff --git a/addons/web/static/src/xml/menu.xml b/addons/web/static/src/xml/menu.xml index 6a4749d1c25..086d676e02a 100644 --- a/addons/web/static/src/xml/menu.xml +++ b/addons/web/static/src/xml/menu.xml @@ -20,7 +20,7 @@ </a> <div class="dropdown-menu" role="menu"> <t t-foreach="widget.getApps()" t-as="app"> - <a role="menuitem" + <a role="menuitem" t-attf-href="#menu_id=#{app.menuID}" class="dropdown-item o_app" t-att-data-menu-id="app.menuID" t-att-data-menu-xmlid="app.xmlID" diff --git a/addons/web/static/tests/chrome/action_manager_tests.js b/addons/web/static/tests/chrome/action_manager_tests.js index 6b572af4285..26ec7b1562b 100644 --- a/addons/web/static/tests/chrome/action_manager_tests.js +++ b/addons/web/static/tests/chrome/action_manager_tests.js @@ -943,6 +943,41 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); + QUnit.test('lazy loaded multi record view with failing mono record one', async function (assert) { + assert.expect(4); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + mockRPC: function (route, args) { + if (args.method === 'read') { + return Promise.reject(); + } + return this._super.apply(this, arguments); + }, + }); + + await actionManager.loadState({ + action: 3, + id: 2, + view_type: 'form', + }).then(function () { + assert.ok(false, 'should not resolve the deferred'); + }).catch(function () { + assert.ok(true, 'should reject the deferred'); + }); + + assert.containsNone(actionManager, '.o_form_view'); + assert.containsNone(actionManager, '.o_list_view'); + + await actionManager.doAction(1); + + assert.containsOnce(actionManager, '.o_kanban_view'); + + actionManager.destroy(); + }); + QUnit.test('change the viewType of the current action', async function (assert) { assert.expect(13); diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index 229d020bfbd..7f6fd2ede71 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -313,8 +313,8 @@ QUnit.module('relational_fields', { await testUtils.form.clickEdit(form); await testUtils.dom.click(form.$('.o_external_button')); - var $originalModal = $('.modal-dialog'); - var $focusedModal = $(document.activeElement).closest('.modal-dialog'); + var $originalModal = $('.modal'); + var $focusedModal = $(document.activeElement).closest('.modal'); assert.equal($originalModal.length, 1, 'There should be one modal'); assert.equal($originalModal[0], $focusedModal[0], 'Modal is focused'); @@ -322,8 +322,8 @@ QUnit.module('relational_fields', { // Open many2one modal of field in many2one modal await testUtils.dom.click($originalModal.find('.o_external_button')); - var $modals = $('.modal-dialog'); - $focusedModal = $(document.activeElement).closest('.modal-dialog'); + var $modals = $('.modal'); + $focusedModal = $(document.activeElement).closest('.modal'); assert.equal($modals.length, 2, 'There should be two modals'); assert.equal($modals[1], $focusedModal[0], 'Last modal is focused'); @@ -331,8 +331,8 @@ QUnit.module('relational_fields', { // Close second modal await testUtils.dom.click($modals.last().find('button[class="close"]')); - var $modal = $('.modal-dialog'); - $focusedModal = $(document.activeElement).closest('.modal-dialog'); + var $modal = $('.modal'); + $focusedModal = $(document.activeElement).closest('.modal'); assert.equal($modal.length, 1, 'There should be one modal'); assert.equal($modal[0], $originalModal[0], 'First modal is still opened'); diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index c1ffac7bd6f..f6d625b3d6d 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -4844,6 +4844,65 @@ QUnit.module('Views', { form.destroy(); }); + QUnit.module('focus and scroll test', async function () { + QUnit.test('no focus set on form when closing many2one modal if lastActivatedFieldIndex is not set', async function (assert) { + assert.expect(8); + + var form = await createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form string="Partners">' + + '<field name="display_name"/>' + + '<field name="foo"/>' + + '<field name="bar"/>' + + '<field name="p"/>' + + '<field name="timmy"/>' + + '<field name="product_ids"/>' + + '<field name="trululu"/>' + + '</form>', + res_id: 2, + archs: { + 'partner,false,list': '<tree><field name="display_name"/></tree>', + 'partner_type,false,list': '<tree><field name="name"/></tree>', + 'partner,false,form': '<form><field name="trululu"/></form>', + 'product,false,list': '<tree><field name="name"/></tree>', + }, + mockRPC: function (route, args) { + if (args.method === 'get_formview_id') { + return Promise.resolve(false); + } + return this._super(route, args); + }, + }); + + // set max-height to have scroll forcefully so that we can test scroll position after modal close + $('.o_content').css({'overflow': 'auto', 'max-height': '300px'}); + // Open many2one modal, lastActivatedFieldIndex will not set as we directly click on external button + await testUtils.form.clickEdit(form); + assert.strictEqual($(".o_content").scrollTop(), 0, "scroll position should be 0"); + + form.$(".o_field_many2one[name='trululu'] .o_input").focus(); + assert.notStrictEqual($(".o_content").scrollTop(), 0, "scroll position should not be 0"); + + await testUtils.dom.click(form.$('.o_external_button')); + // Close modal + await testUtils.dom.click($('.modal').last().find('button[class="close"]')); + assert.notStrictEqual($(".o_content").scrollTop(), 0, + "scroll position should not be 0 after closing modal"); + assert.containsNone(document.body, '.modal', 'There should be no modal'); + assert.doesNotHaveClass($('body'), 'modal-open', 'Modal is not said opened'); + assert.strictEqual(form.renderer.lastActivatedFieldIndex, -1, + "lastActivatedFieldIndex is -1"); + assert.equal(document.activeElement, $('body')[0], + 'body is focused, should not set focus on form widget'); + assert.notStrictEqual(document.activeElement, form.$('.o_field_many2one[name="trululu"] .o_input'), + 'field widget should not be focused when lastActivatedFieldIndex is -1'); + + form.destroy(); + }); + }); + QUnit.test('in create mode, autofocus fields are focused', async function (assert) { assert.expect(1); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index 2447be64837..cf76ee363f7 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -553,10 +553,29 @@ QUnit.module('Views', { hasSelectors: false, }); - assert.strictEqual(list.$('.o_group_header:first').children().length, 1, - "group header should have exactly 1 column"); + assert.strictEqual(list.$('.o_group_header:first').children().length, 2, + "group header should have exactly 2 column"); + assert.strictEqual(list.$('.o_group_header:first th').attr('colspan'), "1", + "the header should not span the whole table"); + list.destroy(); + }); + + QUnit.test('basic grouped list rendering 3 cols without selector', async function (assert) { + assert.expect(2); + + var list = await createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '<tree ><field name="foo"/><field name="bar"/><field name="text"/></tree>', + groupBy: ['bar'], + hasSelectors: false, + }); + + assert.strictEqual(list.$('.o_group_header:first').children().length, 2, + "group header should have exactly 2 columns"); assert.strictEqual(list.$('.o_group_header:first th').attr('colspan'), "2", - "the header should span the whole table"); + "the first header should span two columns"); list.destroy(); }); @@ -572,10 +591,29 @@ QUnit.module('Views', { hasSelectors: true, }); - assert.strictEqual(list.$('.o_group_header:first').children().length, 1, - "group header should have exactly 1 column"); + assert.strictEqual(list.$('.o_group_header:first').children().length, 2, + "group header should have exactly 2 columns"); + assert.strictEqual(list.$('.o_group_header:first th').attr('colspan'), "2", + "the header should not span the whole table"); + list.destroy(); + }); + + QUnit.test('basic grouped list rendering 3 cols with selector', async function (assert) { + assert.expect(2); + + var list = await createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '<tree ><field name="foo"/><field name="bar"/><field name="text"/></tree>', + groupBy: ['bar'], + hasSelectors: true, + }); + + assert.strictEqual(list.$('.o_group_header:first').children().length, 2, + "group header should have exactly 2 columns"); assert.strictEqual(list.$('.o_group_header:first th').attr('colspan'), "3", - "the header should span the whole table"); + "the header should not span the whole table"); list.destroy(); }); @@ -1391,6 +1429,39 @@ QUnit.module('Views', { list.destroy(); }); + QUnit.test('width of some of the fields should be hardcoded', async function (assert) { + const assertions = [ + { field: 'bar', expected: 40, type: 'Boolean' }, + { field: 'int_field', expected: 80, type: 'Integer' }, + { field: 'qux', expected: 100, type: 'Float' }, + { field: 'date', expected: 100, type: 'Date' }, + { field: 'datetime', expected: 150, type: 'Datetime' }, + ]; + assert.expect(assertions.length + 1); + + var list = await createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '<tree editable="top">' + + '<field name="bar"/>' + + '<field name="foo"/>' + + '<field name="int_field"/>' + + '<field name="qux"/>' + + '<field name="date"/>' + + '<field name="datetime"/>' + + '</tree>', + }); + + assertions.forEach(a => { + assert.strictEqual(list.$(`th[data-name="${a.field}"]`)[0].offsetWidth, a.expected, + `Field ${a.type} should have a fixed width of ${a.expected} pixels`); + }); + assert.strictEqual(list.$('th[data-name="foo"]')[0].style.width, '100%', "Char field should occupy the remaining space"); + + list.destroy(); + }); + QUnit.test('row height should not change when switching mode', async function (assert) { // Warning: this test is css dependant assert.expect(3); @@ -1414,7 +1485,7 @@ QUnit.module('Views', { '<field name="int_field" readonly="1"/>' + '<field name="boolean"/>' + '<field name="date"/>' + - '<field name="text" width_factor="2"/>' + + '<field name="text" width_factor="1"/>' + '<field name="amount"/>' + '<field name="currency_id" invisible="1"/>' + '<field name="m2o"/>' + @@ -1424,6 +1495,11 @@ QUnit.module('Views', { currencies: currencies, }, }); + + // the width is hardcoded to make sure we have the same condition + // between debug mode and non debug mode (#qunit-fixture is limited to + // 1000px) + list.$el.width('1000px'); var startHeight = list.$('.o_data_row:first').height(); // start edition of first row @@ -2098,8 +2174,8 @@ QUnit.module('Views', { await testUtils.dom.click(list.$('tbody:eq(1) .o_group_header:eq(0)')); - assert.containsNone(list, '.o_group_header button', - "there should be no button in the header"); + assert.containsOnce(list, '.o_group_header button', + "there should be one button in the header"); list.destroy(); }); @@ -2763,8 +2839,8 @@ QUnit.module('Views', { await testUtils.dom.click(list.$('.o_group_header')); assert.strictEqual(list.$('.o_group_header').css('height'), headerHeight, "height of group header shouldn't have changed"); - assert.hasClass(list.$('.o_group_header th'), 'o_group_pager', - "last cell of open group header should have classname 'o_group_header'"); + assert.hasClass(list.$('.o_group_header th:eq(1) > div'), 'o_pager', + "last cell of open group header should have classname 'o_pager'"); assert.strictEqual(list.$('.o_group_header .o_pager_value').text(), '1-3', "pager's value should be correct"); assert.containsN(list, '.o_data_row', 3, @@ -4334,6 +4410,43 @@ QUnit.module('Views', { list.destroy(); }); + QUnit.test('editable list view: contexts with multiple edit', async function (assert) { + assert.expect(4); + + var list = await createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '<tree editable="top">' + + '<field name="foo"/>' + + '</tree>', + mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/foo/write' || + route === '/web/dataset/call_kw/foo/read') { + var context = args.kwargs.context; + assert.strictEqual(context.active_field, 2, "context should be correct"); + assert.strictEqual(context.someKey, 'some value', "context should be correct"); + } + return this._super.apply(this, arguments); + }, + session: { + user_context: {someKey: 'some value'}, + }, + viewOptions: { + context: {active_field: 2}, + }, + }); + + // Uses the main selector to select all lines. + await testUtils.dom.click(list.$('.o_content input:first')); + await testUtils.dom.click(list.$('.o_data_cell:first')); + // Edits first record then confirms changes. + await testUtils.fields.editInput(list.$('.o_field_widget[name=foo]'), 'legion'); + await testUtils.dom.click($('.modal-dialog button.btn-primary')); + + list.destroy(); + }); + QUnit.test('editable list view: multi edition', async function (assert) { assert.expect(19); diff --git a/addons/web/static/tests/views/pivot_tests.js b/addons/web/static/tests/views/pivot_tests.js index 20abde4a4bb..f82bcdbca42 100644 --- a/addons/web/static/tests/views/pivot_tests.js +++ b/addons/web/static/tests/views/pivot_tests.js @@ -4,6 +4,7 @@ odoo.define('web.pivot_tests', function (require) { var core = require('web.core'); var PivotView = require('web.PivotView'); var testUtils = require('web.test_utils'); +var testUtilsDom = require('web.test_utils_dom'); var createActionManager = testUtils.createActionManager; var patchDate = testUtils.mock.patchDate; @@ -16,7 +17,7 @@ QUnit.module('Views', { this.data = { partner: { fields: { - foo: {string: "Foo", type: "integer"}, + foo: {string: "Foo", type: "integer", searchable: true}, bar: {string: "bar", type: "boolean"}, date: {string: "Date", type: "date", store: true, sortable: true}, product_id: {string: "Product", type: "many2one", relation: 'product', store: true}, @@ -1879,6 +1880,71 @@ QUnit.module('Views', { actionManager.destroy(); }); + QUnit.test('Navigation list view for a group and back with breadcrumbs', async function (assert) { + assert.expect(16); + // create an action manager to test the interactions with the search view + var readGroupCount = 0; + var actionManager = await createActionManager({ + data: this.data, + archs: { + 'partner,false,pivot': '<pivot>' + + '<field name="customer" type="row"/>' + + '</pivot>', + 'partner,false,search': '<search><filter name="bayou" string="Bayou" domain="[(\'foo\',\'=\', 12)]"/></search>', + 'partner,false,list': '<tree><field name="foo"/></tree>', + 'partner,false,form': '<form><field name="foo"/></form>', + }, + intercepts: { + do_action: function (event) { + var action = event.data.action; + actionManager.doAction(action); + } + }, + mockRPC: function (route, args) { + if (args.method === 'read_group') { + assert.step('read_group'); + var domain = args.kwargs.domain; + if ([0,1].indexOf(readGroupCount) !== -1) { + assert.deepEqual(domain, [], 'initial domain empty'); + } else if ([2,3,4,5].indexOf(readGroupCount) !== -1) { + assert.deepEqual(domain, [['foo', '=', 12]], + 'domain conserved when back with breadcrumbs'); + } + readGroupCount++; + } + if (route === '/web/dataset/search_read') { + assert.step('search_read'); + var domain = args.domain; + assert.deepEqual(domain, [['foo', '=', 12], ['customer', '=', 1], ['foo', '=', 12]], + 'list domain is correct'); + } + return this._super.apply(this, arguments); + } + }); + + await actionManager.doAction({ + res_model: 'partner', + type: 'ir.actions.act_window', + views: [[false, 'pivot']], + }); + + await testUtilsDom.click(actionManager.$('.o_filters_menu_button')); + await testUtilsDom.click(actionManager.$('.o_menu_item:contains("Bayou")')); + + await testUtilsDom.click(actionManager.$('.o_pivot_cell_value:nth(1)')) + + assert.containsOnce(actionManager, '.o_list_view'); + + await testUtilsDom.click(actionManager.$('.o_control_panel .breadcrumb-item:first() a')); + + assert.verifySteps([ + 'read_group', 'read_group', + 'read_group', 'read_group', + 'search_read', + 'read_group', 'read_group']) + actionManager.destroy(); + }); + QUnit.module('Sort in comparison mode', { beforeEach: async function () { this.data.partner.records[0].date = '2016-12-15'; |
