summaryrefslogtreecommitdiff
path: root/addons/web/static
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-01-09 15:44:05 +0000
committerChristophe Simonis <chs@odoo.com>2019-01-09 15:56:53 +0000
commit8aa8548d8ab68feb13f3bde51c557db84877e809 (patch)
tree930790c1c84a4412db777986f6b2077808560d95 /addons/web/static
parent55a48e341580a6a400c36b6ba0d7b12ff9997f12 (diff)
parent3e4138deaa23b3631dc3fce33cd6d1b3be28ffbd (diff)
[MERGE] forward port branch 12.0 up to 3e4138deaa2
closes odoo/odoo#30045
Diffstat (limited to 'addons/web/static')
-rw-r--r--addons/web/static/src/js/chrome/action_manager.js6
-rw-r--r--addons/web/static/src/js/core/dialog.js6
-rw-r--r--addons/web/static/src/js/core/dom.js2
-rw-r--r--addons/web/static/src/js/tools/debug_manager.js4
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js29
-rw-r--r--addons/web/static/src/js/views/basic/basic_view.js15
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_model.js15
-rw-r--r--addons/web/static/src/js/views/form/form_view.js7
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_column.js10
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_record.js6
-rw-r--r--addons/web/static/src/js/views/pivot/pivot_controller.js3
-rw-r--r--addons/web/static/src/js/views/pivot/pivot_model.js33
-rw-r--r--addons/web/static/src/js/views/pivot/pivot_renderer.js29
-rw-r--r--addons/web/static/src/js/widgets/attach_document.js5
-rw-r--r--addons/web/static/src/js/widgets/switch_company_menu.js2
-rw-r--r--addons/web/static/src/scss/bootstrap_overridden.scss4
-rw-r--r--addons/web/static/src/scss/primary_variables.scss3
-rw-r--r--addons/web/static/src/xml/base.xml26
-rw-r--r--addons/web/static/src/xml/kanban.xml4
-rw-r--r--addons/web/static/src/xml/menu.xml2
-rw-r--r--addons/web/static/tests/chrome/action_manager_tests.js73
-rw-r--r--addons/web/static/tests/fields/relational_fields_tests.js166
-rw-r--r--addons/web/static/tests/helpers/test_utils_dom.js17
-rw-r--r--addons/web/static/tests/tools/debug_manager_tests.js4
-rw-r--r--addons/web/static/tests/views/calendar_tests.js92
-rw-r--r--addons/web/static/tests/views/form_tests.js72
-rw-r--r--addons/web/static/tests/views/kanban_tests.js14
-rw-r--r--addons/web/static/tests/views/pivot_tests.js144
28 files changed, 733 insertions, 60 deletions
diff --git a/addons/web/static/src/js/chrome/action_manager.js b/addons/web/static/src/js/chrome/action_manager.js
index c798189c0a0..107c6e314eb 100644
--- a/addons/web/static/src/js/chrome/action_manager.js
+++ b/addons/web/static/src/js/chrome/action_manager.js
@@ -332,9 +332,6 @@ var ActionManager = Widget.extend({
// update the internal state and the DOM
self._pushController(controller);
- // toggle the fullscreen mode for actions in target='fullscreen'
- self._toggleFullscreen();
-
// store the action into the sessionStorage so that it can be
// fully restored on F5
self.call('session_storage', 'setItem', 'current_action', action._originalAction);
@@ -720,6 +717,9 @@ var ActionManager = Widget.extend({
action: this.getCurrentAction(),
controller: controller,
});
+
+ // toggle the fullscreen mode for actions in target='fullscreen'
+ this._toggleFullscreen();
},
/**
* Pushes the given state, with additional information about the given
diff --git a/addons/web/static/src/js/core/dialog.js b/addons/web/static/src/js/core/dialog.js
index 492ea06c7bd..83eded92c8a 100644
--- a/addons/web/static/src/js/core/dialog.js
+++ b/addons/web/static/src/js/core/dialog.js
@@ -143,7 +143,11 @@ var Dialog = Widget.extend({
$.when(def).always(self.close.bind(self));
}
});
- self.$footer.append($button);
+ if (self.technical) {
+ self.$footer.append($button);
+ } else {
+ self.$footer.prepend($button);
+ }
});
},
diff --git a/addons/web/static/src/js/core/dom.js b/addons/web/static/src/js/core/dom.js
index 848e406c4b2..b8a5b8be3a4 100644
--- a/addons/web/static/src/js/core/dom.js
+++ b/addons/web/static/src/js/core/dom.js
@@ -506,7 +506,7 @@ var dom = {
var $dropdownMenu = $('<ul/>', {class: 'dropdown-menu'});
$extraItemsToggle = $('<li/>', {class: 'nav-item dropdown o_extra_menu_items'})
- .append($('<a/>', {href: '#', class: 'nav-link dropdown-toggle o-no-caret', 'data-toggle': 'dropdown'})
+ .append($('<a/>', {role: 'button', href: '#', class: 'nav-link dropdown-toggle o-no-caret', 'data-toggle': 'dropdown', 'aria-expanded': false})
.append($('<i/>', {class: 'fa fa-plus'})))
.append($dropdownMenu);
$extraItemsToggle.insertAfter($items.last());
diff --git a/addons/web/static/src/js/tools/debug_manager.js b/addons/web/static/src/js/tools/debug_manager.js
index a348f54acd9..f09d8745f04 100644
--- a/addons/web/static/src/js/tools/debug_manager.js
+++ b/addons/web/static/src/js/tools/debug_manager.js
@@ -365,6 +365,10 @@ DebugManager.include({
views: [[false, 'list'], [false, 'form']],
type: 'ir.actions.act_window',
domain: [['res_model', '=', this._action.res_model], ['res_id', '=', selectedIDs[0]]],
+ context: {
+ default_res_model: this._action.res_model,
+ default_res_id: selectedIDs[0],
+ },
});
},
get_metadata: function() {
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 fecbaa5b90c..9706251ed07 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -1758,7 +1758,7 @@ var BasicModel = AbstractModel.extend({
model: list.model,
method: 'read',
args: [_.pluck(data, 'id'), fieldNames],
- context: record.context,
+ context: _.extend({}, record.context, field.context),
}).then(function (records) {
_.each(records, function (record) {
list_records[record.id].data = record;
@@ -1833,7 +1833,7 @@ var BasicModel = AbstractModel.extend({
addDef = this._applyX2ManyChange(record, fieldName, {
operation: 'ADD_M2M',
ids: values
- });
+ }, viewType);
}
if (removedIds.length) {
var listData = _.map(list.data, function (localId) {
@@ -1847,7 +1847,7 @@ var BasicModel = AbstractModel.extend({
}
return _.findWhere(listData, {res_id: resID}).id;
}),
- });
+ }, viewType);
}
return $.when(addDef, removedDef);
}
@@ -2827,7 +2827,7 @@ var BasicModel = AbstractModel.extend({
var ids = record.data[fieldName] || [];
var list = self._makeDataPoint({
count: ids.length,
- context: record.context,
+ context: _.extend({}, record.context, field.context),
fieldsInfo: fieldsInfo,
fields: view ? view.fields : fieldInfo.relatedFields,
limit: fieldInfo.limit,
@@ -4325,11 +4325,24 @@ var BasicModel = AbstractModel.extend({
}
return def.then(function () {
var resIDs = [];
- // generate the current count and res_ids list by applying the changes
- var currentCount = list.count;
var currentResIDs = list.res_ids;
- var effectiveLimit = (list.limit || 0) - (list.tempLimitIncrement || 0);
- var upperBound = effectiveLimit ? Math.min(list.offset + effectiveLimit, currentCount) : currentCount;
+ // if new records have been added to the list, their virtual ids have
+ // been pushed at the end of res_ids (or at the beginning, depending
+ // on the editable property), ignoring completely the current page
+ // where the records have actually been created ; for that reason,
+ // we use orderedResIDs which is a freezed order with the virtual ids
+ // at the correct position where they were actually inserted ; however,
+ // when we use orderedResIDs, we must filter out ids that are not in
+ // res_ids, which correspond to records that have been removed from
+ // the relation (this information being taken into account in res_ids
+ // but not in orderedResIDs)
+ if (list.orderedResIDs) {
+ currentResIDs = list.orderedResIDs.filter(function (resID) {
+ return list.res_ids.indexOf(resID) >= 0;
+ });
+ }
+ var currentCount = currentResIDs.length;
+ var upperBound = list.limit ? Math.min(list.offset + list.limit, currentCount) : currentCount;
var fieldNames = list.getFieldNames();
for (var i = list.offset; i < upperBound; i++) {
var resId = currentResIDs[i];
diff --git a/addons/web/static/src/js/views/basic/basic_view.js b/addons/web/static/src/js/views/basic/basic_view.js
index d5d6cfa18d5..39444fdfd75 100644
--- a/addons/web/static/src/js/views/basic/basic_view.js
+++ b/addons/web/static/src/js/views/basic/basic_view.js
@@ -145,6 +145,21 @@ var BasicView = AbstractView.extend({
if (_.difference(fieldViewTypes, recordViewTypes).length) {
fieldNames.push(name);
}
+
+ if (record.data[name].viewType === 'default') {
+ // Use case: x2many (tags) in x2many list views
+ // When opening the x2many record form view, the
+ // x2many will be reloaded but it may not have
+ // the same fields (ex: tags in list and list in
+ // form) so we need to merge the fieldsInfo to
+ // avoid losing the initial fields (display_name)
+ var defaultFieldInfo = record.data[name].fieldsInfo.default;
+ _.each(fieldViews, function (fieldView) {
+ _.each(fieldView.fieldsInfo, function (x2mFieldInfo) {
+ _.defaults(x2mFieldInfo, defaultFieldInfo);
+ });
+ });
+ }
}
}
// Many2one: context is not the same between the different views
diff --git a/addons/web/static/src/js/views/calendar/calendar_model.js b/addons/web/static/src/js/views/calendar/calendar_model.js
index 1f8c9423cac..7c06a7d818d 100644
--- a/addons/web/static/src/js/views/calendar/calendar_model.js
+++ b/addons/web/static/src/js/views/calendar/calendar_model.js
@@ -244,18 +244,18 @@ return AbstractModel.extend({
this.setDate(this.data.target_date.clone().add(-1, this.data.scale));
},
/**
- * @todo: this should not work. it ignores the domain/context
- *
* @override
- * @param {any} _handle ignored
- * @param {Object} params
+ * @param {Object} [params.context]
* @param {Array} [params.domain]
* @returns {Deferred}
*/
- reload: function (_handle, params) {
+ reload: function (handle, params) {
if (params.domain) {
this.data.domain = params.domain;
}
+ if (params.context) {
+ this.data.context = params.context;
+ }
return this._loadCalendar();
},
/**
@@ -385,6 +385,9 @@ return AbstractModel.extend({
* @returns {Object}
*/
_getFullCalendarOptions: function () {
+ var week_start = _t.database.parameters.week_start || 0;
+ // calendar uses index 0 for Sunday but Odoo stores it as 7
+ week_start = week_start % 7;
return {
defaultView: (this.mode === "month")? "month" : ((this.mode === "week")? "agendaWeek" : ((this.mode === "day")? "agendaDay" : "agendaWeek")),
header: false,
@@ -404,7 +407,7 @@ return AbstractModel.extend({
monthNamesShort: moment.monthsShort(),
dayNames: moment.weekdays(),
dayNamesShort: moment.weekdaysShort(),
- firstDay: _t.database.parameters.week_start,
+ firstDay: week_start,
slotLabelFormat: _t.database.parameters.time_format.search("%H") != -1 ? 'H:mm': 'h(:mm)a',
};
},
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 249f4ec6019..5a00350c68f 100644
--- a/addons/web/static/src/js/views/form/form_view.js
+++ b/addons/web/static/src/js/views/form/form_view.js
@@ -122,9 +122,14 @@ var FormView = BasicView.extend({
while (matches = regex.exec(attrs.context)) {
context[matches[1]] = matches[2];
}
+
+ // Remove *_view_ref coming from parent view
+ var refinedContext = _.pick(self.loadParams.context, function (value, key) {
+ return key.indexOf('_view_ref') === -1;
+ });
defs.push(parent.loadViews(
field.relation,
- new Context(context, self.userContext, self.loadParams.context).eval(),
+ new Context(context, self.userContext, refinedContext).eval(),
[[null, attrs.mode === 'tree' ? 'list' : attrs.mode]])
.then(function (views) {
for (var viewName in views) {
diff --git a/addons/web/static/src/js/views/kanban/kanban_column.js b/addons/web/static/src/js/views/kanban/kanban_column.js
index 57ecc6f4b4e..b526b80a971 100644
--- a/addons/web/static/src/js/views/kanban/kanban_column.js
+++ b/addons/web/static/src/js/views/kanban/kanban_column.js
@@ -114,7 +114,6 @@ var KanbanColumn = Widget.extend({
revert: 0,
delay: 0,
items: '> .o_kanban_record:not(.o_updating)',
- helper: 'clone',
cursor: 'move',
over: function () {
self.$el.addClass('o_kanban_hover');
@@ -122,6 +121,15 @@ var KanbanColumn = Widget.extend({
out: function () {
self.$el.removeClass('o_kanban_hover');
},
+ start: function (event, ui) {
+ ui.item.addClass('o_currently_dragged');
+ },
+ stop: function (event, ui) {
+ var item = ui.item;
+ setTimeout(function () {
+ item.removeClass('o_currently_dragged');
+ });
+ },
update: function (event, ui) {
var record = ui.item.data('record');
var index = self.records.indexOf(record);
diff --git a/addons/web/static/src/js/views/kanban/kanban_record.js b/addons/web/static/src/js/views/kanban/kanban_record.js
index d9d7ac77fe2..46f6964dd1c 100644
--- a/addons/web/static/src/js/views/kanban/kanban_record.js
+++ b/addons/web/static/src/js/views/kanban/kanban_record.js
@@ -5,7 +5,6 @@ odoo.define('web.KanbanRecord', function (require) {
* This file defines the KanbanRecord widget, which corresponds to a card in
* a Kanban view.
*/
-
var config = require('web.config');
var core = require('web.core');
var Domain = require('web.Domain');
@@ -210,6 +209,11 @@ var KanbanRecord = Widget.extend({
* @private
*/
_openRecord: function () {
+ if (this.$el.hasClass('o_currently_dragged')) {
+ // this record is currently being dragged and dropped, so we do not
+ // want to open it.
+ return;
+ }
var editMode = this.$el.hasClass('oe_kanban_global_click_edit');
this.trigger_up('open_record', {
id: this.db_id,
diff --git a/addons/web/static/src/js/views/pivot/pivot_controller.js b/addons/web/static/src/js/views/pivot/pivot_controller.js
index a4833b40d81..ac48ab769ff 100644
--- a/addons/web/static/src/js/views/pivot/pivot_controller.js
+++ b/addons/web/static/src/js/views/pivot/pivot_controller.js
@@ -345,7 +345,8 @@ var PivotController = AbstractController.extend({
var col_id = $target.data('id');
var measure = $target.data('measure');
var isAscending = $target.hasClass('o_pivot_measure_row_sorted_asc');
- this.model.sortRows(col_id, measure, isAscending);
+ var dataType = $target.data('data_type');
+ this.model.sortRows(col_id, measure, isAscending, dataType);
this.update({}, {reload: false});
},
/**
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 1a0d3625440..5a6423fbe34 100644
--- a/addons/web/static/src/js/views/pivot/pivot_model.js
+++ b/addons/web/static/src/js/views/pivot/pivot_model.js
@@ -457,16 +457,23 @@ var PivotModel = AbstractModel.extend({
* @param {any} col_id
* @param {any} measure
* @param {any} descending
+ * @param {'data'|'comparisonData'|'variation'} [dataType]
*/
- sortRows: function (col_id, measure, descending) {
+ sortRows: function (col_id, measure, descending, dataType) {
var cells = this.data.cells;
+ var comparisonFunction = compare;
+ if (this.data.compare) {
+ dataType = dataType || 'data';
+ comparisonFunction = specialCompare;
+ }
this._traverseTree(this.data.main_row.root, function (header) {
- header.children.sort(compare);
+ header.children.sort(comparisonFunction);
});
this.data.sorted_column = {
id: col_id,
measure: measure,
order: descending ? 'desc' : 'asc',
+ dataType: dataType,
};
function _getValue (id1, id2) {
if ((id1 in cells) && (id2 in cells[id1])) {
@@ -476,12 +483,26 @@ var PivotModel = AbstractModel.extend({
}
function compare (row1, row2) {
- var values1 = _getValue(row1.id, col_id),
- values2 = _getValue(row2.id, col_id),
- value1 = values1 ? values1[measure] : 0,
- value2 = values2 ? values2[measure] : 0;
+ var values1 = _getValue(row1.id, col_id);
+ var values2 = _getValue(row2.id, col_id);
+ var value1 = values1 ? values1[measure] : 0;
+ var value2 = values2 ? values2[measure] : 0;
return descending ? value1 - value2 : value2 - value1;
}
+ function specialCompare (row1, row2) {
+ var values1 = _getValue(row1.id, col_id);
+ var values2 = _getValue(row2.id, col_id);
+ var value1 = values1 ? values1[measure] : {data: 0, comparisonData: 0, variation: {magnitude: 0}};
+ var value2 = values2 ? values2[measure] : {data: 0, comparisonData: 0, variation: {magnitude: 0}};
+ if (dataType === 'variation') {
+ return descending ?
+ value1[dataType].magnitude - value2[dataType].magnitude:
+ value2[dataType].magnitude - value1[dataType].magnitude;
+ }
+ return descending ?
+ value1[dataType] - value2[dataType]:
+ value2[dataType] - value1[dataType];
+ }
},
/**
* Toggle the active state for a given measure, then reload the data.
diff --git a/addons/web/static/src/js/views/pivot/pivot_renderer.js b/addons/web/static/src/js/views/pivot/pivot_renderer.js
index a68d91330a9..787e28e38a9 100644
--- a/addons/web/static/src/js/views/pivot/pivot_renderer.js
+++ b/addons/web/static/src/js/views/pivot/pivot_renderer.js
@@ -116,6 +116,8 @@ var PivotRenderer = AbstractRenderer.extend({
_renderHeaders: function ($thead, headers, nbrCols) {
var self = this;
var i, j, cell, $row, $cell;
+ var measure, dataType;
+ var id, measureCellIds = [];
var groupbyLabels = _.map(this.state.colGroupBys, function (gb) {
return self.state.fields[gb.split(':')[0]].string;
@@ -144,10 +146,13 @@ var PivotRenderer = AbstractRenderer.extend({
$cell.data('id', cell.id);
}
if (cell.measure) {
+ measure = cell.measure;
+ measureCellIds.push(cell.id);
$cell.addClass('o_pivot_measure_row text-muted')
- .text(this.state.fields[cell.measure].string);
- $cell.data('id', cell.id).data('measure', cell.measure);
- if (!this.compare && cell.id === this.state.sortedColumn.id && cell.measure === this.state.sortedColumn.measure) {
+ .text(this.state.fields[measure].string);
+ $cell.data('id', cell.id).data('measure', measure);
+ if (cell.id === this.state.sortedColumn.id &&
+ measure === this.state.sortedColumn.measure) {
$cell.addClass('o_pivot_measure_row_sorted_' + this.state.sortedColumn.order);
if (this.state.sortedColumn.order == 'asc') {
$cell.attr('aria-sorted', 'ascending');
@@ -167,12 +172,30 @@ var PivotRenderer = AbstractRenderer.extend({
}
if (this.compare) {
var colLabels = [this.timeRangeDescription, this.comparisonTimeRangeDescription, 'Variation'];
+ var dataTypes = ['data', 'comparisonData', 'variation'];
$row = $('<tr>');
for (i = 0; i < 3 * nbrCols; i++) {
+ id = measureCellIds[~~(i / 3)];
+ measure = this.state.measures[(~~(i / 3)) % this.state.measures.length];
+ dataType = dataTypes[i % 3];
$cell = $('<th>')
+ .addClass('o_pivot_measure_row text-muted')
+ .data('data_type', dataType)
+ .data('id', id)
+ .data('measure', measure)
.text(colLabels[i % 3])
.attr('rowspan', 1)
.attr('colspan', 1);
+ if (dataType === this.state.sortedColumn.dataType &&
+ id === this.state.sortedColumn.id &&
+ measure === this.state.sortedColumn.measure) {
+ $cell.addClass('o_pivot_measure_row_sorted_' + this.state.sortedColumn.order);
+ if (this.state.sortedColumn.order === 'asc') {
+ $cell.attr('aria-sorted', 'ascending');
+ } else {
+ $cell.attr('aria-sorted', 'descending');
+ }
+ }
$row.append($cell);
}
$thead.append($row);
diff --git a/addons/web/static/src/js/widgets/attach_document.js b/addons/web/static/src/js/widgets/attach_document.js
index 041b021d60b..13496f3aea7 100644
--- a/addons/web/static/src/js/widgets/attach_document.js
+++ b/addons/web/static/src/js/widgets/attach_document.js
@@ -11,7 +11,7 @@ var _t = core._t;
var AttachDocument = Widget.extend({
template: 'AttachDocument',
events: {
- 'click span.o_attach_document': '_onClickAttachDocument',
+ 'click': '_onClickAttachDocument',
'change input.o_input_file': '_onFileChanged',
},
/**
@@ -76,6 +76,9 @@ var AttachDocument = Widget.extend({
* @param {Event} ev
*/
_onClickAttachDocument: function (ev) {
+ if ($(ev.target).is('input.o_input_file')) {
+ return;
+ }
var fieldNames = this.getParent().canBeSaved(this.state.id);
if (fieldNames.length) {
return this._notifyInvalidFields(fieldNames);
diff --git a/addons/web/static/src/js/widgets/switch_company_menu.js b/addons/web/static/src/js/widgets/switch_company_menu.js
index c9498950a04..cc0f8825643 100644
--- a/addons/web/static/src/js/widgets/switch_company_menu.js
+++ b/addons/web/static/src/js/widgets/switch_company_menu.js
@@ -53,7 +53,7 @@ var SwitchCompanyMenu = Widget.extend({
} else {
a = '<span style="margin-right: 24px;"/>';
}
- companiesList += '<a href="#" class="dropdown-item" data-menu="company" data-company-id="' +
+ companiesList += '<a role="menuitem" href="#" class="dropdown-item" data-menu="company" data-company-id="' +
company[0] + '">' + a + company[1] + '</a>';
});
this.$('.dropdown-menu').html(companiesList);
diff --git a/addons/web/static/src/scss/bootstrap_overridden.scss b/addons/web/static/src/scss/bootstrap_overridden.scss
index 09dd5ddbfd4..e8406c948e9 100644
--- a/addons/web/static/src/scss/bootstrap_overridden.scss
+++ b/addons/web/static/src/scss/bootstrap_overridden.scss
@@ -133,8 +133,8 @@ $modal-inner-padding: $o-horizontal-padding;
// FIXME $modal-title-padding ?
-$modal-lg: 980px;
-$modal-md: 650px;
+$modal-lg: $o-modal-lg;
+$modal-md: $o-modal-md;
// Breadcrumbs
diff --git a/addons/web/static/src/scss/primary_variables.scss b/addons/web/static/src/scss/primary_variables.scss
index 45d1b7ac807..2bdc03a132b 100644
--- a/addons/web/static/src/scss/primary_variables.scss
+++ b/addons/web/static/src/scss/primary_variables.scss
@@ -65,6 +65,9 @@ $o-sheet-vpadding: 24px;
$o-notification-error-bg-color: #F16567;
$o-notification-info-bg-color: #FCFBEA;
+$o-modal-lg: 980px;
+$o-modal-md: 650px;
+
// Needed for having no spacing between sheet and mail body in mass_mailing:
// Different required cancel paddings between web and web_enterprise
$o-sheet-cancel-tpadding: 0px;
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index 102e70efa1e..55d744d3a60 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -235,7 +235,7 @@
<t t-name="WebClient.DebugManager">
<li class="o_debug_manager" role="menuitem">
- <a href="#" title="Open Developer Tools" aria-label="Open Developer Tools" data-toggle="dropdown" aria-expanded="false" tabindex="-1" data-display="static">
+ <a role="button" href="#" title="Open Developer Tools" aria-label="Open Developer Tools" data-toggle="dropdown" aria-expanded="false" tabindex="-1" data-display="static">
<span class="fa fa-bug"/>
</a>
<div class="dropdown-menu dropdown-menu-right o_debug_dropdown" role="menu"/>
@@ -853,7 +853,7 @@
<span class="o_badge_text" t-att-title="el.display_name"><span role="img" t-attf-aria-label="Tag color: #{colornames[color]}"/><t t-esc="el.display_name"/></span>
</t>
<t t-if="colorField">
- <a href="#" class="dropdown-toggle o-no-caret" data-toggle="dropdown">
+ <a role="button" href="#" class="dropdown-toggle o-no-caret" data-toggle="dropdown" aria-expanded="false">
<t t-raw="_badge_text"/>
</a>
</t>
@@ -1080,10 +1080,10 @@
<t t-if="widget.items.length !== 0">
<t t-set="group" t-value="0"/>
<t t-foreach="widget.items" t-as="item">
- <div t-if="group !== item.groupNumber" class="dropdown-divider" data-removable="1"/>
+ <div t-if="group !== item.groupNumber" role="separator" class="dropdown-divider" data-removable="1"/>
<div class="o_menu_item"
t-att-data-id="item.id">
- <a href="#" t-attf-class="dropdown-item#{item.isActive ? ' selected ' : ''}">
+ <a role="menuitem" href="#" t-attf-class="dropdown-item#{item.isActive ? ' selected ' : ''}">
<t t-esc="item.description"/>
<span t-if="item.isRemovable" t-att-data-id="item.id" class="o_trash_button" > <span class="fa fa-trash-o"></span> </span>
<span t-if="item.hasOptions" t-att-data-id="item.id" class="o_submenu_switcher">
@@ -1093,10 +1093,10 @@
<ul t-if="item.hasOptions &amp;&amp; widget.openItems[item.id]" class="list-unstyled">
<t t-set="subgroup" t-value="0"/>
<t t-foreach="item.options" t-as="option">
- <li t-if="subgroup !== option.groupNumber" class="dropdown-divider" data-removable="1"/>
+ <li t-if="subgroup !== option.groupNumber" role="separator" class="dropdown-divider" data-removable="1"/>
<li class="o_item_option"
t-att-data-item_id="item.id" t-att-data-option_id="option.optionId">
- <a href="#" t-attf-class="dropdown-item#{item.currentOptionId === option.optionId ? ' selected': ''} pl-5"><t t-esc="option.description"/></a>
+ <a role="menuitem" href="#" t-attf-class="dropdown-item#{item.currentOptionId === option.optionId ? ' selected': ''} pl-5"><t t-esc="option.description"/></a>
</li>
<t t-set="subgroup" t-value="option.groupNumber"/>
</t>
@@ -1117,8 +1117,8 @@
</div>
<t t-name="GroupByMenuGenerator">
- <div t-if="widget.presentedFields.length !== 0 &amp;&amp; widget.items.length !== 0" class="dropdown-divider o_generator_menu"/>
- <button t-if="widget.presentedFields.length !== 0" type="button" class="dropdown-item o_generator_menu o_add_custom_group">Add Custom Group</button>
+ <div t-if="widget.presentedFields.length !== 0 &amp;&amp; widget.items.length !== 0" role="separator" class="dropdown-divider o_generator_menu"/>
+ <button t-if="widget.presentedFields.length !== 0" type="button" class="dropdown-item o_generator_menu o_add_custom_group" aria-expanded="false">Add Custom Group</button>
<div t-if="widget.generatorMenuIsOpen" class="dropdown-item-text o_generator_menu">
<select class="w-auto o_input o_group_selector o_add_group">
<t t-foreach="widget.presentedFields" t-as="field">
@@ -1132,8 +1132,8 @@
</t>
<t t-name="FilterMenuGenerator">
- <div t-if="Object.keys(widget.fields).length !== 0 &amp;&amp; widget.items.length !== 0" class="dropdown-divider o_generator_menu"/>
- <button t-if="Object.keys(widget.fields).length !== 0" type="button" class="dropdown-item o_generator_menu o_add_custom_filter">Add Custom Filter</button>
+ <div t-if="Object.keys(widget.fields).length !== 0 &amp;&amp; widget.items.length !== 0" role="separator" class="dropdown-divider o_generator_menu"/>
+ <button t-if="Object.keys(widget.fields).length !== 0" type="button" class="dropdown-item o_generator_menu o_add_custom_filter" aria-expanded="false">Add Custom Filter</button>
<div t-if="widget.generatorMenuIsOpen" class="dropdown-item-text o_generator_menu o_add_filter_menu">
<button class="btn btn-primary o_apply_filter" type="button">Apply</button>
<button class="btn btn-secondary o_add_condition" type="button">
@@ -1335,7 +1335,7 @@
<label for="o_favorite_share_all_users" class="custom-control-label">Share with all users <span class="fa fa-users" role="img" aria-label="Users" title="Users"/></label>
</div>
<div class="dropdown-item-text o_save_favorite">
- <button role="menuitem" type="button" class="btn btn-primary">Save</button>
+ <button type="button" class="btn btn-primary">Save</button>
</div>
</div>
</div>
@@ -1495,7 +1495,7 @@
<t t-name="UserMenu">
<li class="o_user_menu">
- <a class="dropdown-toggle" data-toggle="dropdown" data-display="static" aria-expanded="false" href="#">
+ <a role="button" class="dropdown-toggle" data-toggle="dropdown" data-display="static" aria-expanded="false" href="#">
<img class="rounded-circle oe_topbar_avatar" t-att-src="_s + '/web/static/src/img/user_menu_avatar.png'" alt="Avatar"/>
<span class="oe_topbar_name"/>
</a>
@@ -1516,7 +1516,7 @@
<t t-name="SwitchCompanyMenu">
<li class="o_switch_company_menu">
- <a class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false" href="#" aria-label="Dropdown menu" title="Dropdown menu">
+ <a role="button" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false" href="#" aria-label="Dropdown menu" title="Dropdown menu">
<span t-attf-class="#{widget.isMobile ? 'fa fa-building-o' : 'oe_topbar_name'}"/>
</a>
<div class="dropdown-menu dropdown-menu-right" role="menu"/>
diff --git a/addons/web/static/src/xml/kanban.xml b/addons/web/static/src/xml/kanban.xml
index b91b8844752..220395fef02 100644
--- a/addons/web/static/src/xml/kanban.xml
+++ b/addons/web/static/src/xml/kanban.xml
@@ -24,8 +24,8 @@
<a t-if="widget.deletable and widget.id" role="menuitem" class="dropdown-item o_column_delete" href="#">Delete</a>
</t>
<t t-if="widget.has_active_field and widget.archivable">
- <a href="#" class="dropdown-item o_column_archive_records">Archive All</a>
- <a href="#" class="dropdown-item o_column_unarchive_records">Unarchive All</a>
+ <a role="menuitem" href="#" class="dropdown-item o_column_archive_records">Archive All</a>
+ <a role="menuitem" href="#" class="dropdown-item o_column_unarchive_records">Unarchive All</a>
</t>
</div>
</span>
diff --git a/addons/web/static/src/xml/menu.xml b/addons/web/static/src/xml/menu.xml
index 4cf905674d7..3838cc6c226 100644
--- a/addons/web/static/src/xml/menu.xml
+++ b/addons/web/static/src/xml/menu.xml
@@ -97,7 +97,7 @@
<t t-extend="UserMenu.Actions">
<t t-jquery=".dropdown-divider" t-operation="before">
- <a href="#" data-menu="shortcuts" class="dropdown-item d-none d-md-inline-block">Shortcuts</a>
+ <a role="menuitem" href="#" data-menu="shortcuts" class="dropdown-item d-none d-md-inline-block">Shortcuts</a>
</t>
</t>
diff --git a/addons/web/static/tests/chrome/action_manager_tests.js b/addons/web/static/tests/chrome/action_manager_tests.js
index cf7df5376c3..990c166a419 100644
--- a/addons/web/static/tests/chrome/action_manager_tests.js
+++ b/addons/web/static/tests/chrome/action_manager_tests.js
@@ -3434,6 +3434,79 @@ QUnit.module('ActionManager', {
actionManager.destroy();
});
+ QUnit.test('fullscreen on action change: back to a "current" action', function (assert) {
+ assert.expect(3);
+
+ this.actions[0].target = 'fullscreen';
+ this.archs['partner,false,form'] = '<form>' +
+ '<button name="1" type="action" class="oe_stat_button" />' +
+ '</form>';
+
+ var actionManager = createActionManager({
+ actions: this.actions,
+ archs: this.archs,
+ data: this.data,
+ intercepts: {
+ toggle_fullscreen: function (ev) {
+ var fullscreen = ev.data.fullscreen;
+
+ switch (toggleFullscreenCalls) {
+ case 0:
+ assert.strictEqual(fullscreen, false);
+ break;
+ case 1:
+ assert.strictEqual(fullscreen, true);
+ break;
+ case 2:
+ assert.strictEqual(fullscreen, false);
+ break;
+ }
+ },
+ },
+
+ });
+
+ var toggleFullscreenCalls = 0;
+ actionManager.doAction(6);
+
+ toggleFullscreenCalls = 1;
+ actionManager.$('button[name=1]').click();
+
+ toggleFullscreenCalls = 2;
+ $('.breadcrumb li a:first').click();
+
+ actionManager.destroy();
+ });
+
+ QUnit.test('fullscreen on action change: all "fullscreen" actions', function (assert) {
+ assert.expect(3);
+
+ this.actions[5].target = 'fullscreen';
+ this.archs['partner,false,form'] = '<form>' +
+ '<button name="1" type="action" class="oe_stat_button" />' +
+ '</form>';
+
+ var actionManager = createActionManager({
+ actions: this.actions,
+ archs: this.archs,
+ data: this.data,
+ intercepts: {
+ toggle_fullscreen: function (ev) {
+ var fullscreen = ev.data.fullscreen;
+ assert.strictEqual(fullscreen, true);
+ },
+ },
+ });
+
+ actionManager.doAction(6);
+
+ actionManager.$('button[name=1]').click();
+
+ $('.breadcrumb li a:first').click();
+
+ actionManager.destroy();
+ });
+
QUnit.module('"ir.actions.act_window_close" actions');
QUnit.test('close the currently opened dialog', function (assert) {
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index 6c3493828ad..15999ca952e 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -228,6 +228,90 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('widget many2many_checkboxes in a subview', function (assert) {
+ assert.expect(2);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<sheet>' +
+ '<notebook>' +
+ '<page string="Turtles">' +
+ '<field name="turtles" mode="tree">' +
+ '<tree>' +
+ '<field name="id"/>' +
+ '</tree>' +
+ '</field>' +
+ '</page>' +
+ '</notebook>' +
+ '</sheet>' +
+ '</form>',
+ archs: {
+ 'turtle,false,form': '<form>' +
+ '<field name="partner_ids" widget="many2many_checkboxes"/>' +
+ '</form>',
+ },
+ res_id: 1,
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_data_cell').click();
+ // edit the partner_ids field by (un)checking boxes on the widget
+ var $firstCheckbox = $('.modal .custom-control-input').first();
+ $firstCheckbox.click();
+ assert.ok($firstCheckbox.prop('checked'), "the checkbox should be ticked");
+ var $secondCheckbox = $('.modal .custom-control-input').eq(1);
+ $secondCheckbox.click();
+ assert.notOk($secondCheckbox.prop('checked'), "the checkbox should be unticked");
+ form.destroy();
+ });
+
+ QUnit.test('delete a record while adding another one in a multipage', function (assert) {
+ // in a many2one with at least 2 pages, add a new line. Delete the line above it.
+ // (the onchange makes it so that the virtualID is inserted in the middle of the currentResIDs.)
+ // it should load the next line to display it on the page.
+ assert.expect(2);
+
+ this.data.partner.records[0].turtles = [2, 3];
+ this.data.partner.onchanges.turtles = function (obj) {
+ obj.turtles = [[5]].concat(obj.turtles);
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="turtles">' +
+ '<tree editable="bottom" limit="1" decoration-muted="turtle_bar == False">' +
+ '<field name="turtle_foo"/>' +
+ '<field name="turtle_bar"/>' +
+ '</tree>' +
+ '</field>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+ // add a line (virtual record)
+ form.$('.o_field_x2many_list_row_add a').click();
+ form.$('.o_input').val('pi').trigger('input');
+ // delete the line above it
+ form.$('.o_list_record_remove').first().click();
+ // the next line should be displayed below the newly added one
+ assert.strictEqual(form.$('.o_data_row').length, 2, "should have 2 records");
+ assert.strictEqual(form.$('.o_data_row .o_data_cell:first-child').text(), 'pikawa',
+ "should display the correct records on page 1");
+
+ form.destroy();
+ });
+
QUnit.test('one2many, onchange, edition and multipage...', function (assert) {
assert.expect(7);
@@ -252,7 +336,7 @@ QUnit.module('relational_fields', {
'</form>',
res_id: 1,
mockRPC: function (route, args) {
- assert.step(args.method + ' ' + args.model)
+ assert.step(args.method + ' ' + args.model);
return this._super(route, args);
},
viewOptions: {
@@ -362,6 +446,39 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('many2many read, field context is properly sent', function (assert) {
+ assert.expect(4);
+
+ this.data.partner.fields.timmy.context = {hello: 'world'};
+ this.data.partner.records[0].timmy = [12];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="timmy" widget="many2many_tags"/>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (args.method === 'read' && args.model === 'partner_type') {
+ assert.step(args.kwargs.context.hello);
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ assert.verifySteps(['world']);
+
+ form.$buttons.find('.o_form_button_edit').click();
+ var $m2mInput = form.$('.o_field_many2manytags input');
+ $m2mInput.click();
+ $m2mInput.autocomplete('widget').find('li:first()').click();
+ assert.verifySteps(['world', 'world']);
+
+ form.destroy();
+ });
+
QUnit.module('FieldStatus');
QUnit.test('static statusbar widget on many2one field', function (assert) {
@@ -1323,6 +1440,53 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('widget many2many_tags in one2many with display_name', function (assert) {
+ assert.expect(4);
+ this.data.turtle.records[0].partner_ids = [2];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<sheet>' +
+ '<field name="turtles">' +
+ '<tree>' +
+ '<field name="partner_ids" widget="many2many_tags"/>' + // will use display_name
+ '</tree>' +
+ '<form>' +
+ '<sheet>' +
+ '<field name="partner_ids"/>' +
+ '</sheet>' +
+ '</form>' +
+ '</field>' +
+ '</sheet>' +
+ '</form>',
+ archs: {
+ 'partner,false,list': '<tree><field name="foo"/></tree>',
+ },
+ res_id: 1,
+ });
+
+ assert.strictEqual(form.$('.o_field_one2many[name="turtles"] .o_list_view .o_field_many2manytags[name="partner_ids"]').text().replace(/\s/g, ''),
+ "secondrecordaaa", "the tags should be correctly rendered");
+
+ // open the x2m form view
+ form.$('.o_field_one2many[name="turtles"] .o_list_view td.o_data_cell:first').click();
+ assert.strictEqual($('.modal .o_form_view .o_field_many2many[name="partner_ids"] .o_list_view .o_data_cell').text(),
+ "blipMy little Foo Value", "the list view should be correctly rendered with foo");
+
+ $('.modal button.o_form_button_cancel').click();
+ assert.strictEqual(form.$('.o_field_one2many[name="turtles"] .o_list_view .o_field_many2manytags[name="partner_ids"]').text().replace(/\s/g, ''),
+ "secondrecordaaa", "the tags should still be correctly rendered");
+
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('.o_field_one2many[name="turtles"] .o_list_view .o_field_many2manytags[name="partner_ids"]').text().replace(/\s/g, ''),
+ "secondrecordaaa", "the tags should still be correctly rendered");
+
+ form.destroy();
+ });
+
QUnit.test('widget many2many_tags: tags title attribute', function (assert) {
assert.expect(1);
this.data.turtle.records[0].partner_ids = [2];
diff --git a/addons/web/static/tests/helpers/test_utils_dom.js b/addons/web/static/tests/helpers/test_utils_dom.js
index b89f5a8eb5f..dcf9eab6e15 100644
--- a/addons/web/static/tests/helpers/test_utils_dom.js
+++ b/addons/web/static/tests/helpers/test_utils_dom.js
@@ -17,6 +17,10 @@ odoo.define('web.test_utils_dom', function () {
* events, but it is enough to help test drag and drop operations with jqueryUI
* sortable.
*
+ * @todo: remove the withTrailingClick option when the jquery update branch is
+ * merged. This is not the default as of now, because handlers are triggered
+ * synchronously, which is not the same as the 'reality'.
+ *
* @param {jqueryElement} $el
* @param {jqueryElement} $to
* @param {Object} [options]
@@ -27,9 +31,13 @@ odoo.define('web.test_utils_dom', function () {
* @param {boolean} [options.continueMove=false] whether to trigger the
* mousedown action (will only work after another call of this function with
* without this option)
+ * @param {boolean} [options.withTrailingClick=false] if true, this utility
+ * function will also trigger a click on the target after the mouseup event
+ * (this is actually what happens when a drag and drop operation is done)
*/
function dragAndDrop($el, $to, options) {
- var position = (options && options.position) || 'center';
+ options = options || {};
+ var position = options.position || 'center';
var elementCenter = $el.offset();
var toOffset = $to.offset();
@@ -58,7 +66,7 @@ function dragAndDrop($el, $to, options) {
toOffset.top += bound.top;
}
$el.trigger($.Event("mouseenter"));
- if (!(options && options.continueMove)) {
+ if (!(options.continueMove)) {
elementCenter.left += $el.outerWidth() / 2;
elementCenter.top += $el.outerHeight() / 2;
@@ -75,12 +83,15 @@ function dragAndDrop($el, $to, options) {
pageY: toOffset.top
}));
- if (!(options && options.disableDrop)) {
+ if (!options.disableDrop) {
$el.trigger($.Event("mouseup", {
which: 1,
pageX: toOffset.left,
pageY: toOffset.top
}));
+ if (options.withTrailingClick) {
+ $el.click();
+ }
} else {
// It's impossible to drag another element when one is already
// being dragged. So it's necessary to finish the drop when the test is
diff --git a/addons/web/static/tests/tools/debug_manager_tests.js b/addons/web/static/tests/tools/debug_manager_tests.js
index fde90105bfb..fb454aae0d1 100644
--- a/addons/web/static/tests/tools/debug_manager_tests.js
+++ b/addons/web/static/tests/tools/debug_manager_tests.js
@@ -46,6 +46,10 @@ QUnit.module('DebugManager', {}, function () {
intercepts: {
do_action: function (event) {
assert.deepEqual(event.data.action, {
+ context: {
+ default_res_model: "test.model",
+ default_res_id: 5,
+ },
domain: [["res_model", "=", "test.model"],["res_id", "=", 5]],
name: "Manage Attachments",
res_model: "ir.attachment",
diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js
index 3f4c6376d5b..264a00cc52a 100644
--- a/addons/web/static/tests/views/calendar_tests.js
+++ b/addons/web/static/tests/views/calendar_tests.js
@@ -557,6 +557,98 @@ QUnit.module('Views', {
$view.remove();
});
+ QUnit.test('default week start (US)', function (assert) {
+ // if not given any option, default week start is on Sunday
+ assert.expect(1);
+ var done = assert.async();
+
+ createAsyncView({
+ View: CalendarView,
+ model: 'event',
+ data: this.data,
+ arch:
+ '<calendar class="o_calendar_test" '+
+ 'date_start="start" '+
+ 'date_stop="stop" '+
+ 'mode="week">'+
+ '</calendar>',
+ archs: archs,
+
+ viewOptions: {
+ initialDate: initialDate,
+ },
+ }).then(function (calendar) {
+ assert.strictEqual(calendar.$('.fc-day-header').first().text(), "Sun 12/11",
+ "The first day of the week should be Sunday");
+ calendar.destroy();
+ done();
+ });
+ });
+
+ QUnit.test('European week start', function (assert) {
+ // the week start depends on the locale
+ assert.expect(1);
+ var done = assert.async();
+
+ createAsyncView({
+ View: CalendarView,
+ model: 'event',
+ data: this.data,
+ arch:
+ '<calendar class="o_calendar_test" '+
+ 'date_start="start" '+
+ 'date_stop="stop" '+
+ 'mode="week">'+
+ '</calendar>',
+ archs: archs,
+
+ viewOptions: {
+ initialDate: initialDate,
+ },
+ translateParameters: {
+ week_start: 1,
+ },
+ }).then(function (calendar) {
+ assert.strictEqual(calendar.$('.fc-day-header').first().text(), "Mon 12/12",
+ "The first day of the week should be Monday");
+ calendar.destroy();
+ done();
+ });
+ });
+
+ QUnit.test('week numbering', function (assert) {
+ // week number depends on the week start, which depends on the locale
+ // the calendar library uses numbers [0 .. 6], while Odoo uses [1 .. 7]
+ // so if the modulo is not done, the week number is incorrect
+ assert.expect(1);
+ var done = assert.async();
+
+ createAsyncView({
+ View: CalendarView,
+ model: 'event',
+ data: this.data,
+ arch:
+ '<calendar class="o_calendar_test" '+
+ 'date_start="start" '+
+ 'date_stop="stop" '+
+ 'mode="week">'+
+ '</calendar>',
+ archs: archs,
+
+ viewOptions: {
+ initialDate: initialDate,
+ },
+ translateParameters: {
+ week_start: 7,
+ },
+ }).then(function (calendar) {
+ assert.strictEqual(calendar.$('.fc-week-number').text(), "W51",
+ "We should be on the 51st week");
+ calendar.destroy();
+ done();
+ });
+ });
+
QUnit.test('create event with timezone in week mode with formViewDialog European locale', function (assert) {
assert.expect(8);
diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js
index a120a6a7547..e1cb3277dba 100644
--- a/addons/web/static/tests/views/form_tests.js
+++ b/addons/web/static/tests/views/form_tests.js
@@ -17,6 +17,7 @@ var Widget = require('web.Widget');
var _t = core._t;
var createView = testUtils.createView;
var createAsyncView = testUtils.createAsyncView;
+var createActionManager = testUtils.createActionManager;
QUnit.module('Views', {
beforeEach: function () {
@@ -299,7 +300,78 @@ QUnit.module('Views', {
form.destroy();
});
+ QUnit.test('Form and subview with _view_ref contexts', function (assert) {
+ assert.expect(2);
+
+ this.data.product.fields.partner_type_ids = {string: "one2many field", type: "one2many", relation: "partner_type"},
+ this.data.product.records = [{id: 1, name: 'Tromblon', partner_type_ids: [12,14]}];
+ this.data.partner.records[0].product_id = 1;
+
+ var actionManager = createActionManager({
+ data: this.data,
+ archs: {
+ 'product,false,form': '<form>'+
+ '<field name="name"/>'+
+ '<field name="partner_type_ids" context="{\'tree_view_ref\': \'some_other_tree_view\'}"/>' +
+ '</form>',
+ 'partner_type,false,list': '<tree>'+
+ '<field name="color"/>'+
+ '</tree>',
+ 'product,false,search': '<search></search>',
+ },
+ mockRPC: function (route, args) {
+ if (args.method === 'load_views') {
+ var context = args.kwargs.context;
+ if (args.model === 'product') {
+ assert.deepEqual(context, {tree_view_ref: 'some_tree_view'},
+ 'The correct _view_ref should have been sent to the server, first time');
+ }
+ if (args.model === 'partner_type') {
+ assert.deepEqual(context, {tree_view_ref: 'some_other_tree_view'},
+ 'The correct _view_ref should have been sent to the server for the subview');
+ }
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="name"/>' +
+ '<field name="product_id" context="{\'tree_view_ref\': \'some_tree_view\'}"/>' +
+ '</form>',
+ res_id: 1,
+
+ mockRPC: function(route, args) {
+ if (args.method === 'get_formview_action') {
+ return $.when({
+ res_id: 1,
+ type: 'ir.actions.act_window',
+ target: 'current',
+ res_model: args.model,
+ context: args.kwargs.context,
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ 'views': [[false, 'form']],
+ });
+ }
+ return this._super(route, args);
+ },
+
+ interceptsPropagate: {
+ do_action: function (ev) {
+ actionManager.doAction(ev.data.action);
+ },
+ },
+ });
+ form.$('.o_field_widget[name="product_id"]').click();
+ form.destroy();
+ actionManager.destroy();
+ });
QUnit.test('invisible fields are properly hidden', function (assert) {
assert.expect(4);
diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js
index c8ac8890da1..6702a9bee37 100644
--- a/addons/web/static/tests/views/kanban_tests.js
+++ b/addons/web/static/tests/views/kanban_tests.js
@@ -2426,6 +2426,12 @@ QUnit.module('Views', {
QUnit.test('can drag and drop a record from one column to the next', function (assert) {
assert.expect(9);
+ // @todo: remove this resequenceDef whenever the jquery upgrade branch
+ // is merged. This is currently necessary to simulate the reality: we
+ // need the click handlers to be executed after the end of the drag and
+ // drop operation, not before.
+ var resequenceDef = $.Deferred();
+
var envIDs = [1, 3, 2, 4]; // the ids that should be in the environment during this test
this.data.partner.fields.sequence = {type: 'number', string: "Sequence"};
var kanban = createView({
@@ -2435,7 +2441,7 @@ QUnit.module('Views', {
arch: '<kanban class="o_kanban_test" on_create="quick_create">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
- '<div><field name="foo"/>' +
+ '<div class="oe_kanban_global_click"><field name="foo"/>' +
'<t t-if="widget.editable"><span class="thisiseditable">edit</span></t>' +
'</div>' +
'</t></templates>' +
@@ -2444,7 +2450,7 @@ QUnit.module('Views', {
mockRPC: function (route, args) {
if (route === '/web/dataset/resequence') {
assert.ok(true, "should call resequence");
- return $.when(true);
+ return resequenceDef.then(_.constant(true));
}
return this._super(route, args);
},
@@ -2457,13 +2463,15 @@ QUnit.module('Views', {
var $record = kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record:first');
var $group = kanban.$('.o_kanban_group:nth-child(2)');
envIDs = [3, 2, 4, 1]; // first record of first column moved to the bottom of second column
- testUtils.dom.dragAndDrop($record, $group);
+ testUtils.dom.dragAndDrop($record, $group, {withTrailingClick: true});
+ resequenceDef.resolve();
assert.containsOnce(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record');
assert.containsN(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record', 3);
assert.containsN(kanban, '.thisiseditable', 4);
assert.deepEqual(kanban.exportState().resIds, envIDs);
+ resequenceDef.resolve();
kanban.destroy();
});
diff --git a/addons/web/static/tests/views/pivot_tests.js b/addons/web/static/tests/views/pivot_tests.js
index b2d71523e74..930fadbbb7b 100644
--- a/addons/web/static/tests/views/pivot_tests.js
+++ b/addons/web/static/tests/views/pivot_tests.js
@@ -1432,7 +1432,7 @@ QUnit.module('Views', {
actionManager.destroy();
});
- QUnit.test('export data in excel with comparison', function (assert) {
+ QUnit.test('export data in excel with comparison', function (assert) {
assert.expect(10);
this.data.partner.records[0].date = '2016-12-15';
@@ -1587,5 +1587,147 @@ QUnit.module('Views', {
unpatchDate();
actionManager.destroy();
});
+
+ QUnit.module('Sort in comparison mode', {
+ beforeEach: function () {
+ this.data.partner.records[0].date = '2016-12-15';
+ this.data.partner.records[1].date = '2016-12-17';
+ this.data.partner.records[2].date = '2016-11-22';
+ this.data.partner.records[3].date = '2016-11-03';
+
+ this.data.partner.fields.company_type = {string: "Company Type", type: "selection", selection: [["company", "Company"], ["individual", "individual"]], searchable: true, store: true, sortable: true};
+
+ this.data.partner.records[0].company_type = 'company';
+ this.data.partner.records[1].company_type = 'individual';
+ this.data.partner.records[2].company_type = 'company';
+ this.data.partner.records[3].company_type = 'individual';
+
+
+ this.unpatchDate = patchDate(2016, 11, 20, 1, 0, 0);
+
+ this.actualResult = function () {
+ var actualResult = $('.o_pivot .o_pivot_cell_value div').map(function() {
+ return $( this ).text();
+ }).get().join();
+ return actualResult;
+ };
+
+ // create an action manager to test the interactions with the search view
+ this.actionManager = createActionManager({
+ data: this.data,
+ archs: {
+ 'partner,false,pivot': '<pivot>' +
+ '<field name="date" interval="day" type="row"/>' +
+ '<field name="company_type" type="col"/>' +
+ '<field name="foo" type="measure"/>' +
+ '</pivot>',
+ 'partner,false,search': '<search></search>',
+ },
+ debug: 1,
+ });
+
+ this.actionManager.doAction({
+ res_model: 'partner',
+ type: 'ir.actions.act_window',
+ views: [[false, 'pivot']],
+ flags: {
+ pivot: {
+ additionalMeasures: ['product_id'],
+ }
+ }
+ });
+
+ // open time range menu
+ testUtils.dom.click($('.o_control_panel .o_time_range_menu_button'));
+ // select 'Today' as range
+ testUtils.fields.editInput($('.o_control_panel .o_time_range_selector'), 'this_month');
+ // check checkbox 'Compare To'
+ testUtils.dom.click($('.o_control_panel .o_time_range_menu .o_comparison_checkbox'));
+ // Click on 'Apply' button
+ testUtils.dom.click($('.o_control_panel .o_time_range_menu .o_apply_range'));
+ // We are in comparison mode
+ },
+ afterEach: function () {
+ this.unpatchDate();
+ this.actionManager.destroy();
+ },
+ }, function () {
+ QUnit.test('when clicking on measure, sort by "This Month" (period of interest)', function (assert) {
+ assert.expect(1);
+
+ // click on 'Foo' in column Total/Company
+ testUtils.dom.click($('.o_pivot_measure_row').eq(0));
+ var results = [
+ "12", "17", "-29.41%", "1", "2", "-50%", "13", "19", "-31.58%",
+ "12", "0", "100%", "12", "0" , "100%",
+ "1", "0", "100%", "1" , "0", "100%",
+ "0", "17", "-100%", "0", "17", "-100%",
+ "0", "2", "-100%", "0" , "2" , "-100%"
+ ];
+ assert.strictEqual(this.actualResult(), results.join());
+ });
+
+ QUnit.test(
+ 'click on a period of interest header sort according to the appropriate column ' +
+ 'first in ascending order then in descending order',
+ function (assert) {
+ assert.expect(2);
+
+ // click on 'This Month' in column Total/Individual/Foo
+ testUtils.dom.click($('.o_pivot_measure_row').eq(6));
+ var results = [
+ "12", "17", "-29.41%", "1", "2", "-50%", "13", "19", "-31.58%",
+ "1", "0", "100%", "1" , "0", "100%",
+ "12", "0", "100%", "12", "0" , "100%",
+ "0", "17", "-100%", "0", "17", "-100%",
+ "0", "2", "-100%", "0" , "2" , "-100%"
+ ];
+ var actualResult = $('.o_pivot .o_pivot_cell_value div').text();
+ assert.strictEqual(actualResult, results.join(''));
+
+ // click once again on 'This Month' in column Total/Individual/Foo
+ testUtils.dom.click($('.o_pivot_measure_row').eq(6));
+ results = [
+ "12", "17", "-29.41%", "1", "2", "-50%", "13", "19", "-31.58%",
+ "12", "0", "100%", "12", "0" , "100%",
+ "0", "17", "-100%", "0", "17", "-100%",
+ "0", "2", "-100%", "0" , "2" , "-100%",
+ "1", "0", "100%", "1" , "0", "100%"
+ ];
+ assert.strictEqual(this.actualResult(), results.join());
+ });
+
+ QUnit.test('click on a period of comparison header sort according to appropriate column',
+ function (assert) {
+ assert.expect(1);
+
+ // click on 'Previous Period' in column Total/Individual/Foo
+ testUtils.dom.click($('.o_pivot_measure_row').eq(7));
+ var results = [
+ "12", "17", "-29.41%", "1", "2", "-50%", "13", "19", "-31.58%",
+ "0", "2", "-100%", "0" , "2" , "-100%",
+ "12", "0", "100%", "12", "0" , "100%",
+ "1", "0", "100%", "1" , "0", "100%",
+ "0", "17", "-100%", "0", "17", "-100%"
+ ];
+ assert.strictEqual(this.actualResult(), results.join());
+ });
+
+ QUnit.test('click on a variation header sort according to appropriate column',
+ function (assert) {
+ assert.expect(1);
+
+ // click on 'Variation' in column Total/Individual/Foo
+ testUtils.dom.click($('.o_pivot_measure_row').eq(11));
+ var results = [
+ "12", "17", "-29.41%", "1", "2", "-50%", "13", "19", "-31.58%",
+ "12", "0", "100%", "12", "0" , "100%",
+ "1", "0", "100%", "1" , "0", "100%",
+ "0", "17", "-100%", "0", "17", "-100%",
+ "0", "2", "-100%", "0" , "2" , "-100%"
+ ];
+ assert.strictEqual(this.actualResult(), results.join());
+ });
+ });
});
});