summaryrefslogtreecommitdiff
path: root/addons/web/static
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2017-08-31 12:55:41 +0200
committerChristophe Simonis <chs@odoo.com>2017-08-31 12:55:41 +0200
commit4879ce040797fd4a47b6b8326c3aacd365c8ab91 (patch)
tree45479b47c5dcfba0d0ced4f05d770b0852f31263 /addons/web/static
parentdae6178a0a5629ce678b20fcc9226caa44a5b0ce (diff)
parentfe77df3ba480d40b3d6b25736397bd52c684ce03 (diff)
[MERGE] forward port branch saas-17 up to fe77df3ba48
Diffstat (limited to 'addons/web/static')
-rw-r--r--addons/web/static/src/js/chrome/pager.js8
-rw-r--r--addons/web/static/src/js/chrome/view_manager.js2
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js9
-rw-r--r--addons/web/static/src/js/fields/relational_fields.js6
-rw-r--r--addons/web/static/src/js/services/data_manager.js5
-rw-r--r--addons/web/static/src/js/views/basic/basic_controller.js6
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js24
-rw-r--r--addons/web/static/src/js/views/field_manager_mixin.js1
-rw-r--r--addons/web/static/src/js/views/form/form_controller.js7
-rw-r--r--addons/web/static/src/js/views/form/form_renderer.js7
-rw-r--r--addons/web/static/src/js/views/form/form_view.js1
-rw-r--r--addons/web/static/src/js/views/graph/graph_controller.js10
-rw-r--r--addons/web/static/src/js/views/graph/graph_model.js15
-rw-r--r--addons/web/static/src/js/views/graph/graph_view.js8
-rw-r--r--addons/web/static/src/js/views/list/list_controller.js6
-rw-r--r--addons/web/static/src/js/views/list/list_editable_renderer.js7
-rw-r--r--addons/web/static/src/js/views/pivot/pivot_model.js37
-rw-r--r--addons/web/static/src/js/views/pivot/pivot_view.js6
-rw-r--r--addons/web/static/src/less/list_view.less4
-rw-r--r--addons/web/static/tests/fields/basic_fields_tests.js58
-rw-r--r--addons/web/static/tests/fields/relational_fields_tests.js128
-rw-r--r--addons/web/static/tests/helpers/test_utils.js15
-rw-r--r--addons/web/static/tests/views/form_tests.js177
-rw-r--r--addons/web/static/tests/views/graph_tests.js46
-rw-r--r--addons/web/static/tests/views/list_tests.js75
-rw-r--r--addons/web/static/tests/views/pivot_tests.js76
26 files changed, 691 insertions, 53 deletions
diff --git a/addons/web/static/src/js/chrome/pager.js b/addons/web/static/src/js/chrome/pager.js
index bbe69c6d86a..c7f22a6f2c9 100644
--- a/addons/web/static/src/js/chrome/pager.js
+++ b/addons/web/static/src/js/chrome/pager.js
@@ -97,10 +97,16 @@ var Pager = Widget.extend({
/**
* Sets the state of the pager and renders it
* @param {Object} [state] the values to update (size, current_min and limit)
+ * @param {Object} [options]
+ * @param {boolean} [options.notifyChange] set to true to make the pager
+ * notify the environment that its state changed
*/
- updateState: function (state) {
+ updateState: function (state, options) {
_.extend(this.state, state);
this._render();
+ if (options && options.notifyChange) {
+ this.trigger('pager_changed', _.clone(this.state));
+ }
},
//--------------------------------------------------------------------------
diff --git a/addons/web/static/src/js/chrome/view_manager.js b/addons/web/static/src/js/chrome/view_manager.js
index 2dae906c6db..d89f035d34d 100644
--- a/addons/web/static/src/js/chrome/view_manager.js
+++ b/addons/web/static/src/js/chrome/view_manager.js
@@ -363,7 +363,7 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
var View = this.registry.get(arch.attrs.js_class || view_descr.type);
var params = _.extend({}, view_options, {userContext: this.getSession().user_context});
if (view_descr.type === "form" && ((this.action.target === 'new' || this.action.target === 'inline') ||
- (view_options && view_options.mode === 'edit'))) {
+ (view_options && (view_options.mode === 'edit' || view_options.context.form_view_initial_mode)))) {
params.mode = params.initial_mode || 'edit';
}
view_descr.searchview_hidden = View.prototype.searchview_hidden;
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index a1f87a38fd2..4c4671275f2 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -846,6 +846,13 @@ var HandleWidget = AbstractField.extend({
tagName: 'span',
description: "",
supportedFieldTypes: ['integer'],
+
+ /*
+ * @override
+ */
+ isSet: function () {
+ return true;
+ },
});
var FieldEmail = InputField.extend({
@@ -1787,7 +1794,7 @@ var FieldProgressBar = AbstractField.extend({
} else {
this.$el.on('click', function () {
if (!self.write_mode) {
- var $input = $('<input>', {type: 'text', class: 'o_progressbar_value'});
+ var $input = $('<input>', {type: 'text', class: 'o_progressbar_value o_input'});
$input.on('blur', _.bind(self.on_change_input, self));
self.$('.o_progressbar_value').replaceWith($input);
self.write_mode = true;
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js
index ba9b947d6a3..f396699bc3d 100644
--- a/addons/web/static/src/js/fields/relational_fields.js
+++ b/addons/web/static/src/js/fields/relational_fields.js
@@ -1087,7 +1087,11 @@ var FieldOne2Many = FieldX2Many.extend({
ev.stopPropagation();
if (this.editable) {
- if (!this.creatingRecord) {
+ if (!this.activeActions.create) {
+ if (ev.data.onFail) {
+ ev.data.onFail();
+ }
+ } else if (!this.creatingRecord) {
this.creatingRecord = true;
this._setValue({
operation: 'CREATE',
diff --git a/addons/web/static/src/js/services/data_manager.js b/addons/web/static/src/js/services/data_manager.js
index 999a9d8df01..77857ef2d31 100644
--- a/addons/web/static/src/js/services/data_manager.js
+++ b/addons/web/static/src/js/services/data_manager.js
@@ -398,11 +398,6 @@ return core.Class.extend({
*/
_processFieldsView: function (viewInfo) {
var viewFields = this._processFields(viewInfo.type, viewInfo.arch, viewInfo.fields);
- // by default fetch display_name and id
- if (!viewInfo.fields.display_name) {
- viewInfo.fields.display_name = {type: 'char'};
- viewFields.display_name = {};
- }
viewInfo.fieldsInfo = {};
viewInfo.fieldsInfo[viewInfo.type] = viewFields;
utils.deepFreeze(viewInfo.fields);
diff --git a/addons/web/static/src/js/views/basic/basic_controller.js b/addons/web/static/src/js/views/basic/basic_controller.js
index cfdee6793dd..67de3ec8ad9 100644
--- a/addons/web/static/src/js/views/basic/basic_controller.js
+++ b/addons/web/static/src/js/views/basic/basic_controller.js
@@ -539,6 +539,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
* reload
*/
_onReload: function (event) {
+ event.stopPropagation(); // prevent other controllers from handling this request
var data = event && event.data || {};
var handle = data.db_id;
if (handle) {
@@ -546,7 +547,10 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
this.model.reload(handle).then(this._confirmSave.bind(this, handle));
} else {
// no db_id given, so reload the main record
- this.reload({fieldNames: data.fieldNames});
+ this.reload({
+ fieldNames: data.fieldNames,
+ keepChanges: data.keepChanges || false,
+ });
}
},
/**
diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js
index 537fa11fdb9..5eca6c5374f 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -1631,8 +1631,8 @@ var BasicModel = AbstractModel.extend({
*/
_fetchRecord: function (record, options) {
var self = this;
- var fieldNames = options && options.fieldNames ||
- _.uniq(record.getFieldNames().concat(['display_name']));
+ var fieldNames = options && options.fieldNames || record.getFieldNames();
+ fieldNames = _.uniq(fieldNames.concat(['display_name']));
return this._rpc({
model: record.model,
method: 'read',
@@ -2045,7 +2045,7 @@ var BasicModel = AbstractModel.extend({
if (record.data.hasOwnProperty(domainModel)) {
domainModel = record._changes && record._changes[domainModel] || record.data[domainModel];
}
- var domainValue = record._changes && record._changes[fieldName] || record.data[fieldName];
+ var domainValue = record._changes && record._changes[fieldName] || record.data[fieldName] || [];
// avoid rpc if not necessary
var hasChanged = this._saveSpecialDataCache(record, fieldName, {
@@ -2339,6 +2339,16 @@ var BasicModel = AbstractModel.extend({
_generateOnChangeData: function (record) {
var commands = this._generateX2ManyCommands(record, {withReadonly: true});
var data = _.extend(this.get(record.id, {raw: true}).data, commands);
+ // 'display_name' is automatically added to the list of fields to fetch,
+ // when fetching a record, even if it doesn't appear in the view. However,
+ // only the fields in the view must be passed to the onchange RPC, so we
+ // remove it from the data sent by RPC if it isn't in the view.
+ var hasDisplayName = _.some(record.fieldsInfo, function (fieldsInfo) {
+ return 'display_name' in fieldsInfo;
+ });
+ if (!hasDisplayName) {
+ delete data.display_name;
+ }
// one2many records have a parentID
if (record.parentID) {
@@ -2399,7 +2409,10 @@ var BasicModel = AbstractModel.extend({
_.each(list._changes, function (change) {
if (change.operation === 'ADD') {
relRecordAdded.push(self.localData[change.id]);
- } else if (change.operation === 'UPDATE') {
+ } else if (change.operation === 'UPDATE' && !self.isNew(change.id)) {
+ // ignore new records that would have been updated
+ // afterwards, as all their changes would already
+ // be aggregated in the CREATE command
relRecordUpdated.push(self.localData[change.id]);
}
});
@@ -2740,8 +2753,7 @@ var BasicModel = AbstractModel.extend({
var fieldInfo = element.fieldsInfo[element.viewType][fieldName];
var rawModifiers = JSON.parse(fieldInfo.modifiers || "{}");
var modifiers = self._evalModifiers(record, rawModifiers);
- var required = 'required' in modifiers ? modifiers.required : field.required;
- if (required && !self._isFieldSet(recordData[fieldName], field.type)) {
+ if (modifiers.required && !self._isFieldSet(recordData[fieldName], field.type)) {
isValid = false;
}
});
diff --git a/addons/web/static/src/js/views/field_manager_mixin.js b/addons/web/static/src/js/views/field_manager_mixin.js
index 1ee7bf4a501..aaf7e4a5859 100644
--- a/addons/web/static/src/js/views/field_manager_mixin.js
+++ b/addons/web/static/src/js/views/field_manager_mixin.js
@@ -120,6 +120,7 @@ var FieldManagerMixin = {
*/
_onLoad: function (event) {
var self = this;
+ event.stopPropagation(); // prevent other field managers from handling this request
var data = event.data;
if (!data.on_success) { return; }
var params = {};
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 0718b4e202e..0bd3c263666 100644
--- a/addons/web/static/src/js/views/form/form_controller.js
+++ b/addons/web/static/src/js/views/form/form_controller.js
@@ -28,6 +28,7 @@ var FormController = BasicController.extend({
this._super.apply(this, arguments);
this.actionButtons = params.actionButtons;
+ this.disableAutofocus = params.disableAutofocus;
this.footerToButtons = params.footerToButtons;
this.defaultButtons = params.defaultButtons;
this.hasSidebar = params.hasSidebar;
@@ -42,7 +43,9 @@ var FormController = BasicController.extend({
* Calls autofocus on the renderer
*/
autofocus: function () {
- this.renderer.autofocus();
+ if (!this.disableAutofocus) {
+ this.renderer.autofocus();
+ }
},
/**
* This method switches the form view in edit mode, with a new record.
@@ -305,7 +308,7 @@ var FormController = BasicController.extend({
this.set('title', title);
this._updateButtons();
this._updateSidebar();
- return this._super.apply(this, arguments);
+ return this._super.apply(this, arguments).then(this.autofocus.bind(this));
},
/**
* @private
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 1d788ac2c05..63d7fd44917 100644
--- a/addons/web/static/src/js/views/form/form_renderer.js
+++ b/addons/web/static/src/js/views/form/form_renderer.js
@@ -246,10 +246,6 @@ var FormRenderer = BasicRenderer.extend({
_renderButtonBox: function (node) {
var self = this;
var $result = $('<' + node.tag + '>', { 'class': 'o_not_full' });
- // Avoid to show buttons if we are in create mode (edit mode without res_id)
- if (this.mode === 'edit' && !this.state.res_id) {
- return $result;
- }
var buttons = _.map(node.children, function (child) {
if (child.tag === 'button') {
return self._renderStatButton(child);
@@ -816,9 +812,6 @@ var FormRenderer = BasicRenderer.extend({
self._addFieldTooltip(widget, $label);
}
});
-
- // Focus the default field (in edit mode)
- self.autofocus();
},
/**
* Sets id attribute of given widget to idForLabel
diff --git a/addons/web/static/src/js/views/form/form_view.js b/addons/web/static/src/js/views/form/form_view.js
index f0264b67307..b48a5488848 100644
--- a/addons/web/static/src/js/views/form/form_view.js
+++ b/addons/web/static/src/js/views/form/form_view.js
@@ -29,6 +29,7 @@ var FormView = BasicView.extend({
this.loadParams.type = 'record';
this.loadParams.parentID = params.parentID;
+ this.controllerParams.disableAutofocus = params.disable_autofocus;
this.controllerParams.hasSidebar = params.sidebar;
this.controllerParams.toolbarActions = viewInfo.toolbar;
this.controllerParams.footerToButtons = params.footer_to_buttons;
diff --git a/addons/web/static/src/js/views/graph/graph_controller.js b/addons/web/static/src/js/views/graph/graph_controller.js
index d68808bac1d..77e3f1e7a40 100644
--- a/addons/web/static/src/js/views/graph/graph_controller.js
+++ b/addons/web/static/src/js/views/graph/graph_controller.js
@@ -102,10 +102,20 @@ var GraphController = AbstractController.extend({
});
},
/**
+ * @private
+ */
+ _update: function () {
+ this._updateButtons();
+ return this._super.apply(this, arguments);
+ },
+ /**
* makes sure that the buttons in the control panel matches the current
* state (so, correct active buttons and stuff like that)
*/
_updateButtons: function () {
+ if (!this.$buttons) {
+ return;
+ }
var state = this.model.get();
this.$buttons.find('.o_graph_button').removeClass('active');
this.$buttons
diff --git a/addons/web/static/src/js/views/graph/graph_model.js b/addons/web/static/src/js/views/graph/graph_model.js
index 50e9f224697..a6d2b057d0e 100644
--- a/addons/web/static/src/js/views/graph/graph_model.js
+++ b/addons/web/static/src/js/views/graph/graph_model.js
@@ -55,14 +55,15 @@ return AbstractModel.extend({
* to keep track of various entities.
*/
load: function (params) {
- this.initialGroupBys = params.groupBys;
+ var groupBys = params.context.graph_groupbys || params.groupBys;
+ this.initialGroupBys = groupBys;
this.fields = params.fields;
this.modelName = params.modelName;
this.chart = {
data: [],
- groupedBy: params.groupedBy.length ? params.groupedBy : params.groupBys,
- measure: params.measure,
- mode: params.mode,
+ groupedBy: params.groupedBy.length ? params.groupedBy : groupBys,
+ measure: params.context.graph_measure || params.measure,
+ mode: params.context.graph_mode || params.mode,
domain: params.domain,
context: params.context,
};
@@ -85,6 +86,12 @@ return AbstractModel.extend({
* @returns {Deferred}
*/
reload: function (handle, params) {
+ if ('context' in params) {
+ this.chart.context = params.context;
+ this.chart.groupedBy = params.context.graph_groupbys || this.chart.groupedBy;
+ this.chart.measure = params.context.graph_measure || this.chart.measure;
+ this.chart.mode = params.context.graph_mode || this.chart.mode;
+ }
if ('domain' in params) {
this.chart.domain = params.domain;
}
diff --git a/addons/web/static/src/js/views/graph/graph_view.js b/addons/web/static/src/js/views/graph/graph_view.js
index 08b5ce1968f..423d52ca64c 100644
--- a/addons/web/static/src/js/views/graph/graph_view.js
+++ b/addons/web/static/src/js/views/graph/graph_view.js
@@ -27,7 +27,7 @@ var GraphView = AbstractView.extend({
/**
* @override
*/
- init: function (viewInfo, params) {
+ init: function (viewInfo) {
this._super.apply(this, arguments);
var measure;
@@ -57,9 +57,9 @@ var GraphView = AbstractView.extend({
this.controllerParams.measures = measures;
this.rendererParams.stacked = viewInfo.arch.attrs.stacked !== "False";
- this.loadParams.mode = params.context.graph_mode || viewInfo.arch.attrs.type || 'bar';
- this.loadParams.measure = params.context.graph_measure || measure || '__count__';
- this.loadParams.groupBys = params.context.graph_groupbys || groupBys || [];
+ this.loadParams.mode = viewInfo.arch.attrs.type || 'bar';
+ this.loadParams.measure = measure || '__count__';
+ this.loadParams.groupBys = groupBys || [];
this.loadParams.fields = viewInfo.fields;
},
});
diff --git a/addons/web/static/src/js/views/list/list_controller.js b/addons/web/static/src/js/views/list/list_controller.js
index 3b74f140e4d..33637e53f52 100644
--- a/addons/web/static/src/js/views/list/list_controller.js
+++ b/addons/web/static/src/js/views/list/list_controller.js
@@ -313,7 +313,11 @@ var ListController = BasicController.extend({
*/
_onAddRecord: function (event) {
event.stopPropagation();
- this._addRecord();
+ if (this.activeActions.create) {
+ this._addRecord();
+ } else if (event.data.onFail) {
+ event.data.onFail();
+ }
},
/**
* Handles a click on a button by performing its action.
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 3cd93bb6e8b..c5f65ecc6e2 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
@@ -341,7 +341,12 @@ ListRenderer.include({
if (this.currentRow < this.state.data.length - 1) {
this._selectCell(this.currentRow + 1, 0);
} else {
- this.unselectRow().then(this.trigger_up.bind(this, 'add_record'));
+ var self = this;
+ this.unselectRow().then(function () {
+ self.trigger_up('add_record', {
+ onFail: self._selectCell.bind(self, 0, 0, {}),
+ });
+ });
}
},
/**
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 d74af7fee22..7eb2ba4bc6b 100644
--- a/addons/web/static/src/js/views/pivot/pivot_model.js
+++ b/addons/web/static/src/js/views/pivot/pivot_model.js
@@ -223,15 +223,15 @@ var PivotModel = AbstractModel.extend({
*/
load: function (params) {
this.initialDomain = params.domain;
- this.initialRowGroupBys = params.rowGroupBys;
+ this.initialRowGroupBys = params.context.pivot_row_groupby || params.rowGroupBys;
this.fields = params.fields;
this.modelName = params.modelName;
this.data = {
domain: params.domain,
context: _.extend({}, session.user_context, params.context),
groupedBy: params.groupedBy,
- colGroupBys: params.colGroupBys,
- measures: params.measures,
+ colGroupBys: params.context.pivot_column_groupby || params.colGroupBys,
+ measures: this._processMeasures(params.context.pivot_measures) || params.measures,
sorted_column: {},
};
this.defaultGroupedBy = params.groupedBy;
@@ -245,6 +245,12 @@ var PivotModel = AbstractModel.extend({
*/
reload: function (handle, params) {
var self = this;
+ if ('context' in params) {
+ this.data.context = params.context;
+ this.data.colGroupBys = params.context.pivot_column_groupby || this.data.colGroupBys;
+ this.data.groupedBy = params.context.pivot_row_groupby || this.data.groupedBy;
+ this.data.measures = this._processMeasures(params.context.pivot_measures) || this.data.measures;
+ }
if ('domain' in params) {
this.data.domain = params.domain;
} else {
@@ -261,7 +267,7 @@ var PivotModel = AbstractModel.extend({
var old_col_root = this.data.main_col.root;
return this._loadData().then(function () {
var new_groupby_length;
- if (!('groupBy' in params)) {
+ if (!('groupBy' in params) && !('pivot_row_groupby' in (params.context || {}))) {
// we only update the row groupbys according to the old groupbys
// if we don't have the key 'groupBy' in params. In that case,
// we want to have the full open state for the groupbys.
@@ -649,6 +655,29 @@ var PivotModel = AbstractModel.extend({
this.data.main_col.root = main_col_header;
},
/**
+ * In the preview implementation of the pivot view (a.k.a. version 2),
+ * the virtual field used to display the number of records was named
+ * __count__, whereas __count is actually the one used in xml. So
+ * basically, activating a filter specifying __count as measures crashed.
+ * Unfortunately, as __count__ was used in the JS, all filters saved as
+ * favorite at that time were saved with __count__, and not __count.
+ * So in order the make them still work with the new implementation, we
+ * handle both __count__ and __count.
+ *
+ * This function replaces in the given array of measures occurences of
+ * '__count__' by '__count'.
+ *
+ * @param {Array[string] || undefined} measures
+ * @return {Array[string] || undefined}
+ */
+ _processMeasures: function (measures) {
+ if (measures) {
+ return _.map(measures, function (measure) {
+ return measure === '__count__' ? '__count' : measure;
+ });
+ }
+ },
+ /**
* Format a value to a usable string, for the renderer to display.
*
* @param {any} value
diff --git a/addons/web/static/src/js/views/pivot/pivot_view.js b/addons/web/static/src/js/views/pivot/pivot_view.js
index 356672081d9..7e4dddc4a52 100644
--- a/addons/web/static/src/js/views/pivot/pivot_view.js
+++ b/addons/web/static/src/js/views/pivot/pivot_view.js
@@ -87,9 +87,9 @@ var PivotView = AbstractView.extend({
activeMeasures.push('__count');
}
- this.loadParams.measures = params.context.pivot_measures || activeMeasures;
- this.loadParams.colGroupBys = params.context.pivot_column_groupby || colGroupBys;
- this.loadParams.rowGroupBys = params.context.pivot_row_groupby || rowGroupBys;
+ this.loadParams.measures = activeMeasures;
+ this.loadParams.colGroupBys = colGroupBys;
+ this.loadParams.rowGroupBys = rowGroupBys;
this.loadParams.fields = fields;
this.controllerParams.title = params.title || arch.attrs.string || _t("Untitled");
diff --git a/addons/web/static/src/less/list_view.less b/addons/web/static/src/less/list_view.less
index b7e362cc57f..f509e82345c 100644
--- a/addons/web/static/src/less/list_view.less
+++ b/addons/web/static/src/less/list_view.less
@@ -118,7 +118,9 @@
}
.o_data_cell.o_invisible_modifier {
- display: table-cell!important; // Override invisible to keep the cells aligned
+ // Override display: none to keep table cells aligned. Note: cannot use
+ // 'initial' as it will force them to 'inline', not 'table-cell'.
+ display: table-cell!important;
}
//--------------------------------------------------------------------------
diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js
index b533f5056d2..6347ed63432 100644
--- a/addons/web/static/tests/fields/basic_fields_tests.js
+++ b/addons/web/static/tests/fields/basic_fields_tests.js
@@ -1259,6 +1259,8 @@ QUnit.module('basic_fields', {
// Copying from a div tag with white-space:pre-wrap doesn't work in Firefox
assert.strictEqual(form.$('[name="txt"]').prop("tagName").toLowerCase(), 'span',
"the field contents should be surrounded by a span tag");
+
+ form.destroy();
});
QUnit.module('FieldBinary');
@@ -1594,6 +1596,24 @@ QUnit.module('basic_fields', {
form.destroy();
});
+ QUnit.test('handle widget with falsy values', function (assert) {
+ assert.expect(1);
+
+ var list = createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '<tree>' +
+ '<field name="sequence" widget="handle"/>' +
+ '<field name="display_name"/>' +
+ '</tree>',
+ });
+
+ assert.strictEqual(list.$('.o_row_handle:visible').length, this.data.partner.records.length,
+ 'there should be a visible handle for each record');
+ list.destroy();
+ });
+
QUnit.module('FieldDate');
@@ -3878,6 +3898,44 @@ QUnit.module('basic_fields', {
"field selector popover should contain 'Color index' field");
form.destroy();
});
+
+ QUnit.test('domain field: handle false domain as []', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.records[0].foo = false;
+ this.data.partner.fields.bar.type = "char";
+ this.data.partner.records[0].bar = "product";
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:
+ '<form>' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="bar"/>' +
+ '<field name="foo" widget="domain" options="{\'model\': \'bar\'}"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ mockRPC: function (route, args) {
+ if (args.method === 'search_count') {
+ assert.deepEqual(args.args[0], [], "should send a valid domain");
+ }
+ return this._super.apply(this, arguments);
+ },
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ var $warning = form.$('.o_field_widget[name=foo] .text-warning');
+ assert.strictEqual($warning.length, 0, "should not display that the domain is invalid");
+
+ form.destroy();
+ });
});
});
});
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index 82b668a11fb..b6b463fdc32 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -998,14 +998,15 @@ QUnit.module('relational_fields', {
});
QUnit.test('list in form: default_get with x2many create', function (assert) {
- assert.expect(3);
+ assert.expect(5);
+ var displayName = 'brandon is the new timmy';
this.data.partner.onchanges.timmy = function (obj) {
assert.deepEqual(
obj.timmy,
[
[6, false, []],
- [0, obj.timmy[1][1], {display_name: 'brandon is the new timmy', name: 'brandon'}]
+ [0, obj.timmy[1][1], {display_name: displayName, name: 'brandon'}]
],
"should have properly created the x2many command list");
obj.int_field = obj.timmy.length;
@@ -1029,6 +1030,15 @@ QUnit.module('relational_fields', {
if (args.method === 'default_get') {
return $.when({timmy: [[0, 0, {display_name: 'brandon is the new timmy', name: 'brandon'}]]});
}
+ if (args.method === 'create') {
+ assert.deepEqual(args.args[0], {
+ int_field: 2,
+ timmy: [
+ [6, false, []],
+ [0, false, {display_name: displayName, name: 'brandon'}],
+ ],
+ }, "should send the correct values to create");
+ }
return this._super.apply(this, arguments);
},
});
@@ -1038,6 +1048,12 @@ QUnit.module('relational_fields', {
assert.strictEqual($('input.o_field_integer').val(), '2',
"should have called and executed the onchange properly");
+ // edit the subrecord and save
+ displayName = 'new value';
+ form.$('.o_data_cell').click();
+ form.$('.o_data_cell input').val(displayName).trigger('input');
+ form.$buttons.find('.o_form_button_save').click();
+
form.destroy();
});
@@ -2521,6 +2537,7 @@ QUnit.module('relational_fields', {
arch:'<form string="Partners">' +
'<field name="p">' +
'<kanban create="0">' +
+ '<field name="display_name"/>' +
'<templates>' +
'<t t-name="kanban-box">' +
'<div class="oe_kanban_global_click">' +
@@ -3499,7 +3516,7 @@ QUnit.module('relational_fields', {
rpcCount++;
if (args.method === 'write') {
assert.deepEqual(args.args[1].p, [[0, args.args[1].p[0][1], {
- display_name: false, int_field: 123, product_id: 41
+ int_field: 123, product_id: 41
}]]);
}
return this._super(route, args);
@@ -3736,7 +3753,7 @@ QUnit.module('relational_fields', {
if (args.method === 'default_get') {
var expected = counter === 0 ?
[[4, 2, false]] :
- [[4, 2, false], [0, args.kwargs.context.turtles[1][1], {display_name: false, turtle_foo: 'hammer'}]];
+ [[4, 2, false], [0, args.kwargs.context.turtles[1][1], {turtle_foo: 'hammer'}]];
assert.deepEqual(args.kwargs.context.turtles, expected,
"should have properly evaluated turtles key in context");
counter++;
@@ -5483,6 +5500,43 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('onchange and required fields with override in arch', function (assert) {
+ assert.expect(4);
+
+ this.data.partner.onchanges = {
+ turtles: function (obj) {}
+ };
+ this.data.turtle.fields.turtle_foo.required = true;
+ this.data.partner.records[0].turtles = [];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<field name="turtles">' +
+ '<tree editable="bottom">' +
+ '<field name="turtle_int"/>' +
+ '<field name="turtle_foo" required="0"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ assert.step(args.method);
+ return this._super.apply(this, arguments);
+ },
+ });
+ form.$buttons.find('.o_form_button_edit').click();
+
+ // triggers an onchange on partner, because the new record is valid
+ form.$('.o_field_x2many_list_row_add a').click();
+
+ assert.verifySteps(['read', 'default_get', 'onchange']);
+ form.destroy();
+ });
+
+
QUnit.module('FieldMany2Many');
@@ -5638,6 +5692,7 @@ QUnit.module('relational_fields', {
arch:'<form string="Partners">' +
'<field name="timmy">' +
'<kanban create="0">' +
+ '<field name="display_name"/>' +
'<templates>' +
'<t t-name="kanban-box">' +
'<div class="oe_kanban_global_click">' +
@@ -6977,6 +7032,71 @@ QUnit.module('relational_fields', {
list.destroy();
});
+ QUnit.test('search more in many2one: group and use the pager', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.records.push({
+ id: 5,
+ display_name: "Partner 4",
+ }, {
+ id: 6,
+ display_name: "Partner 5",
+ }, {
+ id: 7,
+ display_name: "Partner 6",
+ }, {
+ id: 8,
+ display_name: "Partner 7",
+ }, {
+ id: 9,
+ display_name: "Partner 8",
+ }, {
+ id: 10,
+ display_name: "Partner 9",
+ });
+
+ this.data.partner.fields.datetime.searchable = true;
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="trululu"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+
+ res_id: 1,
+ archs: {
+ 'partner,false,list': '<tree limit="7"><field name="display_name"/></tree>',
+ 'partner,false,search': '<search><group>' +
+ ' <filter name="bar" string="Bar" context="{\'group_by\': \'bar\'}"/>' +
+ '</group></search>',
+ },
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ var $dropdown = form.$('.o_field_many2one input').autocomplete('widget');
+ form.$('.o_field_many2one input').click();
+ $dropdown.find('.o_m2o_dropdown_option:contains(Search)').mouseenter().click(); // Open Search More
+
+ $('.modal .o_searchview_more').click(); // Magnifying class for more filters
+ $('.modal .o_search_options .o_group_by_menu a:contains(Bar)').click(); // group by 'Bar'
+
+ $('.modal .o_group_header:first').click();
+ assert.strictEqual($('.modal tbody:nth(1) .o_data_row').length, 7,
+ "should display 7 records in the first page");
+ $('.modal .o_group_header:first .o_pager_next').click();
+ assert.strictEqual($('.modal tbody:nth(1) .o_data_row').length, 1,
+ "should display 1 record in the second page");
+
+ form.destroy();
+ });
+
QUnit.test('field many2many_tags keeps focus when being edited', function (assert) {
assert.expect(7);
diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js
index fdc70507a12..65cbc16fda0 100644
--- a/addons/web/static/tests/helpers/test_utils.js
+++ b/addons/web/static/tests/helpers/test_utils.js
@@ -470,6 +470,20 @@ function triggerPositionalMouseEvent(x, y, type){
}
/**
+ * simulate a keypress event for a given character
+ * @param {string} the character
+ */
+function triggerKeypressEvent(char) {
+ var keycode;
+ if (char === "Enter") {
+ keycode = $.ui.keyCode.ENTER;
+ } else {
+ keycode = char.charCodeAt(0);
+ }
+ return $('body').trigger($.Event('keypress', {which: keycode, keyCode: keycode}));
+}
+
+/**
* Removes the src attribute on images and iframes to prevent not found errors,
* and optionally triggers an rpc with the src url as route on a widget.
*
@@ -518,6 +532,7 @@ return $.when(
dragAndDrop: dragAndDrop,
triggerMouseEvent: triggerMouseEvent,
triggerPositionalMouseEvent: triggerPositionalMouseEvent,
+ triggerKeypressEvent: triggerKeypressEvent,
removeSrcAttribute: removeSrcAttribute,
};
});
diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js
index dcb422b00ca..f87eea2d154 100644
--- a/addons/web/static/tests/views/form_tests.js
+++ b/addons/web/static/tests/views/form_tests.js
@@ -1136,7 +1136,7 @@ QUnit.module('Views', {
mockRPC: function (route, args) {
if (args.method === "onchange") {
assert.deepEqual(args.args[3],
- {"foo": "1", "p": "", "p.bar": "", "p.display_name": "", "p.product_id": "", "timmy": "", "timmy.name": ""},
+ {"foo": "1", "p": "", "p.bar": "", "p.product_id": "", "timmy": "", "timmy.name": ""},
"should send only the fields used in the views");
}
return this._super(route, args);
@@ -1284,7 +1284,6 @@ QUnit.module('Views', {
if (args.method === 'onchange') {
assert.deepEqual(args.args[3], {
p: '',
- 'p.display_name': '',
'p.foo': '1'
}, "onchangeSpec should be correct (with sub fields)");
}
@@ -2678,7 +2677,7 @@ QUnit.module('Views', {
form.destroy();
});
- QUnit.test('button box is not rendered in create mode', function (assert) {
+ QUnit.test('button box is rendered in create mode', function (assert) {
assert.expect(3);
var form = createView({
@@ -2708,8 +2707,8 @@ QUnit.module('Views', {
// create mode (leave edition first!)
form.$buttons.find('.o_form_button_cancel').click();
form.$buttons.find('.o_form_button_create').click();
- assert.strictEqual(form.$('.oe_stat_button').length, 0,
- "button box should not be displayed when creating a new record");
+ assert.strictEqual(form.$('.oe_stat_button').length, 1,
+ "button box should be displayed when creating a new record as well");
form.destroy();
});
@@ -2863,6 +2862,92 @@ QUnit.module('Views', {
form.destroy();
});
+ QUnit.test('display_name not sent for onchanges if not in view', function (assert) {
+ assert.expect(7);
+
+ this.data.partner.records[0].timmy = [12];
+ this.data.partner.onchanges = {
+ foo: function () {},
+ };
+ this.data.partner_type.onchanges = {
+ name: function () {},
+ };
+ var readInModal = false;
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<group>' +
+ '<field name="foo"/>' +
+ '<field name="timmy">' +
+ '<tree>' +
+ '<field name="name"/>' +
+ '</tree>' +
+ '<form>' +
+ '<field name="name"/>' +
+ '<field name="color"/>' +
+ '</form>' +
+ '</field>' +
+ '</group>' +
+ '</form>',
+ mockRPC: function (route, args) {
+ if (args.method === 'read' && args.model === 'partner') {
+ assert.deepEqual(args.args[1], ['foo', 'timmy', 'display_name'],
+ "should read display_name even if not in the view");
+ }
+ if (args.method === 'read' && args.model === 'partner_type') {
+ if (!readInModal) {
+ assert.deepEqual(args.args[1], ['name'],
+ "should not read display_name for records in the list");
+ } else {
+ assert.deepEqual(args.args[1], ['name', 'color', 'display_name'],
+ "should read display_name when opening the subrecord");
+ }
+ }
+ if (args.method === 'onchange' && args.model === 'partner') {
+ assert.deepEqual(args.args[1], {
+ id: 1,
+ foo: 'coucou',
+ timmy: [[6, false, [12]]],
+ }, "should only send the value of fields in the view (+ id)");
+ assert.deepEqual(args.args[3], {
+ foo: '1',
+ timmy: '',
+ 'timmy.name': '1',
+ 'timmy.color': '',
+ }, "only the fields in the view should be in the onchange spec");
+ }
+ if (args.method === 'onchange' && args.model === 'partner_type') {
+ assert.deepEqual(args.args[1], {
+ id: 12,
+ name: 'new name',
+ color: 2,
+ }, "should only send the value of fields in the view (+ id)");
+ assert.deepEqual(args.args[3], {
+ name: '1',
+ color: '',
+ }, "only the fields in the view should be in the onchange spec");
+ }
+ return this._super.apply(this, arguments);
+ },
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ // trigger the onchange
+ form.$('.o_field_widget[name=foo]').val("coucou").trigger('input');
+
+ // open a subrecord and trigger an onchange
+ readInModal = true;
+ form.$('.o_data_row .o_data_cell:first').click();
+ $('.modal .o_field_widget[name=name]').val("new name").trigger('input');
+
+ form.destroy();
+ });
+
QUnit.test('onchanges on date(time) fields', function (assert) {
assert.expect(6);
@@ -3761,7 +3846,6 @@ QUnit.module('Views', {
if (args.method === 'create') {
var command = args.args[0].p;
assert.deepEqual(command, [[0, command[0][1], {
- display_name: false,
timmy: [[6, false, [12]]],
}]], "the default partner_type_id should be equal to 12");
}
@@ -4233,6 +4317,32 @@ QUnit.module('Views', {
form.destroy();
});
+ QUnit.test('no autofocus with disable_autofocus option [REQUIRE FOCUS]', function (assert) {
+ assert.expect(2);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="int_field"/>' +
+ '<field name="foo"/>' +
+ '</form>',
+ viewOptions: {
+ disable_autofocus: true,
+ },
+ });
+ assert.notStrictEqual(document.activeElement, form.$('input[name="int_field"]')[0],
+ "int_field field should not have focus");
+
+ form.update({});
+
+ assert.notStrictEqual(document.activeElement, form.$('input[name="int_field"]')[0],
+ "int_field field should not have focus");
+
+ form.destroy();
+ });
+
QUnit.test('open one2many form containing many2many_tags', function (assert) {
assert.expect(4);
@@ -5625,6 +5735,61 @@ QUnit.module('Views', {
form.destroy();
});
+ QUnit.test('reload event is handled only once', function (assert) {
+ // In this test, several form controllers are nested (two of them are
+ // opened in dialogs). When the users clicks on save in the last
+ // opened dialog, a 'reload' event is triggered up to reload the (direct)
+ // parent view. If this event isn't stopPropagated by the first controller
+ // catching it, it will crash when the other one will try to handle it,
+ // as this one doesn't know at all the dataPointID to reload.
+ assert.expect(9);
+
+ var arch = '<form>' +
+ '<field name="display_name"/>' +
+ '<field name="trululu"/>' +
+ '</form>';
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: arch,
+ archs: {
+ 'partner,false,form': arch,
+ },
+ res_id: 2,
+ mockRPC: function (route, args) {
+ assert.step(args.method);
+ if (args.method === 'get_formview_id') {
+ return $.when(false);
+ }
+ return this._super.apply(this, arguments);
+ },
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ form.$('.o_external_button').click(); // open the many2one record in a modal
+ $('.modal .o_external_button').click(); // in the modal, open the many2one record in another modal
+
+ $('.modal:nth(1) .o_field_widget[name=display_name]').val('new name').trigger('input');
+ $('.modal:nth(1) .modal-footer .btn-primary').first().click(); // save changes
+
+ assert.strictEqual($('.modal .o_field_widget[name=trululu] input').val(), 'new name',
+ "record should have been reloaded");
+ assert.verifySteps([
+ "read", // main record
+ "get_formview_id", // id of first form view opened in a dialog
+ "read", // first dialog
+ "get_formview_id", // id of second form view opened in a dialog
+ "read", // second dialog
+ "write", // save second dialog
+ "read", // reload first dialog
+ ]);
+
+ form.destroy();
+ });
+
QUnit.test('rainbowman attributes correctly passed on button click', function (assert) {
assert.expect(1);
diff --git a/addons/web/static/tests/views/graph_tests.js b/addons/web/static/tests/views/graph_tests.js
index c7d69b194ff..07f7d060347 100644
--- a/addons/web/static/tests/views/graph_tests.js
+++ b/addons/web/static/tests/views/graph_tests.js
@@ -372,6 +372,52 @@ QUnit.module('Views', {
});
});
+ QUnit.test('correctly uses graph_ keys from the context (at reload)', function (assert) {
+ var done = assert.async();
+ assert.expect(8);
+
+ var graph = createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: '<graph><field name="product_id"/></graph>',
+ });
+
+ assert.strictEqual(graph.renderer.state.mode, "bar", "should be in bar chart mode");
+ assert.ok(graph.$buttons.find('button[data-mode="bar"]').hasClass('active'),
+ 'bar chart button should be active');
+
+ var reloadParams = {
+ context: {
+ graph_measure: 'foo',
+ graph_mode: 'line',
+ graph_groupbys: ['color_id'],
+ },
+ };
+ graph.reload(reloadParams);
+ return concurrency.delay(0).then(function () {
+ // check measure
+ assert.strictEqual(graph.$('text.nv-legend-text:contains(Foo)').length, 1,
+ "should now use the 'foo' measure");
+
+ // check mode
+ assert.strictEqual(graph.renderer.state.mode, "line", "should be in line chart mode");
+ assert.notOk(graph.$buttons.find('button[data-mode="bar"]').hasClass('active'),
+ 'bar chart button should not be active');
+ assert.ok(graph.$buttons.find('button[data-mode="line"]').hasClass('active'),
+ 'line chart button should be active');
+
+ // check groupbys
+ assert.strictEqual(graph.$('text:contains(xphone)').length, 0,
+ "should not contain a text element with product in legend");
+ assert.strictEqual(graph.$('text:contains(red)').length, 1,
+ "should contain a text element with color in legend");
+
+ graph.destroy();
+ done();
+ });
+ });
+
QUnit.test('reload graph with correct fields', function (assert) {
assert.expect(2);
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index 4b33af9f0a6..2ac373dbcd0 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -19,6 +19,7 @@ QUnit.module('Views', {
int_field: {string: "int_field", type: "integer", sortable: true},
qux: {string: "my float", type: "float"},
m2o: {string: "M2O field", type: "many2one", relation: "bar"},
+ o2m: {string: "O2M field", type: "one2many", relation: "bar"},
m2m: {string: "M2M field", type: "many2many", relation: "bar"},
amount: {string: "Monetary field", type: "monetary"},
currency_id: {string: "Currency", type: "many2one",
@@ -1994,6 +1995,80 @@ QUnit.module('Views', {
list.destroy();
});
+ QUnit.test('navigation with tab on a list with create="0"', function (assert) {
+ assert.expect(4);
+
+ var list = createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '<tree editable="bottom" create="0">' +
+ '<field name="display_name"/>' +
+ '</tree>',
+ });
+
+ assert.strictEqual(list.$('.o_data_row').length, 4,
+ "the list should contain 4 rows");
+
+ list.$('.o_data_row:nth(2) .o_data_cell:first').click();
+ assert.ok(list.$('.o_data_row:nth(2)').hasClass('o_selected_row'),
+ "third row should be in edition");
+
+ // Press 'Tab' -> should go to next line
+ 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");
+
+ // Press 'Tab' -> should go back to first line as the create action isn't available
+ list.$('.o_selected_row input').trigger({type: 'keydown', which: 9});
+ assert.ok(list.$('.o_data_row:first').hasClass('o_selected_row'),
+ "first row should be in edition");
+
+ list.destroy();
+ });
+
+
+ QUnit.test('navigation with tab on a one2many list with create="0"', function (assert) {
+ assert.expect(4);
+
+ this.data.foo.records[0].o2m = [1, 2];
+ var form = createView({
+ View: FormView,
+ model: 'foo',
+ data: this.data,
+ arch: '<form><sheet>' +
+ '<field name="o2m">' +
+ '<tree editable="bottom" create="0">' +
+ '<field name="display_name"/>' +
+ '</tree>' +
+ '</field>' +
+ '</sheet></form>',
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ assert.strictEqual(form.$('.o_field_widget[name=o2m] .o_data_row').length, 2,
+ "there should be two records in the many2many");
+
+ form.$('.o_field_widget[name=o2m] .o_data_cell:first').click();
+ assert.ok(form.$('.o_field_widget[name=o2m] .o_data_row:first').hasClass('o_selected_row'),
+ "first row should be in edition");
+
+ // Press 'Tab' -> should go to next line
+ form.$('.o_field_widget[name=o2m] .o_selected_row input').trigger({type: 'keydown', which: 9});
+ assert.ok(form.$('.o_field_widget[name=o2m] .o_data_row:nth(1)').hasClass('o_selected_row'),
+ "second row should be in edition");
+
+ // Press 'Tab' -> should go back to first line as the create action isn't available
+ form.$('.o_field_widget[name=o2m] .o_selected_row input').trigger({type: 'keydown', which: 9});
+ assert.ok(form.$('.o_field_widget[name=o2m] .o_data_row:first').hasClass('o_selected_row'),
+ "first row should be in edition");
+
+ form.destroy();
+ });
+
QUnit.test('edition, then navigation with tab (with a readonly field)', function (assert) {
// This test makes sure that if we have 2 cells in a row, the first in
// edit mode, and the second one readonly, then if we edit and press TAB,
diff --git a/addons/web/static/tests/views/pivot_tests.js b/addons/web/static/tests/views/pivot_tests.js
index 267437845b0..fe8c4c5b3f9 100644
--- a/addons/web/static/tests/views/pivot_tests.js
+++ b/addons/web/static/tests/views/pivot_tests.js
@@ -825,6 +825,53 @@ QUnit.module('Views', {
pivot.destroy();
});
+ QUnit.test('correctly uses pivot_ keys from the context (at reload)', function (assert) {
+ assert.expect(8);
+
+ this.data.partner.fields.amount = {string: "Amount", type: "float"};
+
+ var pivot = createView({
+ View: PivotView,
+ model: "partner",
+ data: this.data,
+ arch: '<pivot>' +
+ '<field name="date" interval="day" type="col"/>' +
+ '<field name="amount" type="measure"/>' +
+ '</pivot>',
+ });
+
+ assert.strictEqual(pivot.$('tbody tr:first td.o_pivot_cell_value:last').text(), '0.00',
+ "the active measure should be amount");
+
+ var reloadParams = {
+ context: {
+ pivot_measures: ['foo'],
+ pivot_column_groupby: ['customer'],
+ pivot_row_groupby: ['product_id'],
+ },
+ };
+ pivot.reload(reloadParams);
+
+ assert.strictEqual(pivot.$('thead .o_pivot_header_cell_opened').length, 1,
+ "column: should have one opened header");
+ assert.strictEqual(pivot.$('thead .o_pivot_header_cell_closed:contains(First)').length, 1,
+ "column: should display one closed header with 'First'");
+ assert.strictEqual(pivot.$('thead .o_pivot_header_cell_closed:contains(Second)').length, 1,
+ "column: should display one closed header with 'Second'");
+
+ assert.strictEqual(pivot.$('tbody .o_pivot_header_cell_opened').length, 1,
+ "row: should have one opened header");
+ assert.strictEqual(pivot.$('tbody .o_pivot_header_cell_closed:contains(xphone)').length, 1,
+ "row: should display one closed header with 'xphone'");
+ assert.strictEqual(pivot.$('tbody .o_pivot_header_cell_closed:contains(xpad)').length, 1,
+ "row: should display one closed header with 'xpad'");
+
+ assert.strictEqual(pivot.$('tbody tr:first td:nth(3)').text(), '32',
+ "selected measure should be foo, with total 32");
+
+ pivot.destroy();
+ });
+
QUnit.test('correctly use group_by key from the context', function (assert) {
assert.expect(7);
@@ -859,4 +906,33 @@ QUnit.module('Views', {
pivot.destroy();
});
+ QUnit.test('pivot still handles __count__ measure', function (assert) {
+ // for retro-compatibility reasons, the pivot view still handles
+ // '__count__' measure.
+ assert.expect(2);
+
+ var pivot = createView({
+ View: PivotView,
+ model: "partner",
+ data: this.data,
+ arch: '<pivot></pivot>',
+ mockRPC: function (route, args) {
+ if (args.method === 'read_group') {
+ assert.deepEqual(args.kwargs.fields, ['__count'],
+ "should make a read_group with field __count");
+ }
+ return this._super(route, args);
+ },
+ viewOptions: {
+ context: {
+ pivot_measures: ['__count__'],
+ },
+ },
+ });
+
+ var $countMeasure = pivot.$buttons.find('li[data-field=__count]');
+ assert.ok($countMeasure.hasClass('selected'), "The count measure should be activated");
+
+ pivot.destroy();
+ });
});});