summaryrefslogtreecommitdiff
path: root/addons/web
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-07-26 15:12:29 +0200
committerChristophe Simonis <chs@odoo.com>2019-07-26 15:12:29 +0200
commitbfd34e14b1be97465e135d85c76b9e626a219e7a (patch)
tree8de54ec8f504158e965ab92e21658cab539024ee /addons/web
parent26b5b446b3dab3d66b7db054ca77e7b1a811c28a (diff)
parent40e8b67179f212551a176d72b3851671febc1fba (diff)
[MERGE] forward port branch saas-12.3 up to 40e8b67179f
Diffstat (limited to 'addons/web')
-rw-r--r--addons/web/i18n/km.po10
-rw-r--r--addons/web/static/src/js/chrome/action_manager_act_window.js10
-rw-r--r--addons/web/static/src/js/views/form/form_controller.js9
-rw-r--r--addons/web/static/src/js/views/form/form_renderer.js7
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_column_progressbar.js2
-rw-r--r--addons/web/static/src/js/views/list/list_editable_renderer.js66
-rw-r--r--addons/web/static/src/js/views/list/list_model.js13
-rw-r--r--addons/web/static/src/js/views/list/list_renderer.js20
-rw-r--r--addons/web/static/src/js/views/pivot/pivot_model.js1
-rw-r--r--addons/web/static/src/js/views/view_dialogs.js18
-rw-r--r--addons/web/static/src/scss/layout_standard.scss3
-rw-r--r--addons/web/static/src/scss/list_view.scss18
-rw-r--r--addons/web/static/src/xml/kanban.xml2
-rw-r--r--addons/web/static/src/xml/menu.xml2
-rw-r--r--addons/web/static/tests/chrome/action_manager_tests.js35
-rw-r--r--addons/web/static/tests/fields/relational_fields_tests.js12
-rw-r--r--addons/web/static/tests/views/form_tests.js59
-rw-r--r--addons/web/static/tests/views/list_tests.js135
-rw-r--r--addons/web/static/tests/views/pivot_tests.js68
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 c19364fa7a9..325a8d1e11e 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 660c7dc81e1..5aef3d54bfa 100644
--- a/addons/web/static/src/js/views/form/form_controller.js
+++ b/addons/web/static/src/js/views/form/form_controller.js
@@ -623,9 +623,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 9efadaabe30..cee79966a51 100644
--- a/addons/web/static/src/js/views/form/form_renderer.js
+++ b/addons/web/static/src/js/views/form/form_renderer.js
@@ -194,8 +194,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 6469b78347a..3c36720081e 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 df6345d398f..f7efaab1798 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 e35f09a6cc9..f665bf49791 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;
},
/**
@@ -425,8 +430,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 acc0e07bf1c..bd0b4934259 100644
--- a/addons/web/static/src/scss/layout_standard.scss
+++ b/addons/web/static/src/scss/layout_standard.scss
@@ -1,3 +1,6 @@
.o_standard_footer {
margin-top: 200px;
+ .list-inline-item {
+ white-space: nowrap;
+ }
}
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 3a01370443f..e54ad36b8b8 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 5724da4b2f8..b98a973695f 100644
--- a/addons/web/static/tests/chrome/action_manager_tests.js
+++ b/addons/web/static/tests/chrome/action_manager_tests.js
@@ -880,6 +880,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 b5e098d230c..9c649cc0173 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 cd9659aceed..1fa82e3f238 100644
--- a/addons/web/static/tests/views/form_tests.js
+++ b/addons/web/static/tests/views/form_tests.js
@@ -4881,6 +4881,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 4e324762372..5dd64d6a074 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
@@ -2094,8 +2170,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();
});
@@ -2759,8 +2835,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,
@@ -4299,6 +4375,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 333f51f75ba..3a83a23b64e 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},
@@ -1880,6 +1881,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';