summaryrefslogtreecommitdiff
path: root/addons/web/static
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-02-22 16:25:02 +0100
committerChristophe Simonis <chs@odoo.com>2019-02-22 16:25:02 +0100
commit0e7675847f05a63cf013210dabac82c94845217f (patch)
treeae87ad0d0b4d792cb7a17ea1b5428b5d96ce8c56 /addons/web/static
parent1af67ffdfa370ade4ea12f227d7c4fae5bc02170 (diff)
parent0f4abc5c228747c7eee79f820fa2ea9c23d73e5f (diff)
[MERGE] forward port branch saas-12.1 up to 0f4abc5c228
Diffstat (limited to 'addons/web/static')
-rw-r--r--addons/web/static/lib/moment/locale/es.js20
-rw-r--r--addons/web/static/lib/py.js/lib/py.js9
-rw-r--r--addons/web/static/src/js/core/abstract_storage_service.js11
-rw-r--r--addons/web/static/src/js/core/py_utils.js10
-rw-r--r--addons/web/static/src/js/core/utils.js12
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js1
-rw-r--r--addons/web/static/src/js/views/control_panel/control_panel_model.js17
-rw-r--r--addons/web/static/src/js/views/control_panel/search/filter_menu.js2
-rw-r--r--addons/web/static/src/js/views/control_panel/search/search_filters.js11
-rw-r--r--addons/web/static/src/js/views/graph/graph_renderer.js7
-rw-r--r--addons/web/static/src/js/views/pivot/pivot_model.js8
-rw-r--r--addons/web/static/src/js/views/pivot/pivot_renderer.js3
-rw-r--r--addons/web/static/src/scss/form_view.scss4
-rw-r--r--addons/web/static/src/scss/graph_view.scss29
-rw-r--r--addons/web/static/src/xml/base.xml2
-rw-r--r--addons/web/static/tests/chrome/action_manager_tests.js2
-rw-r--r--addons/web/static/tests/chrome/user_menu_tests.js1
-rw-r--r--addons/web/static/tests/core/py_utils_tests.js35
-rw-r--r--addons/web/static/tests/core/util_tests.js20
-rw-r--r--addons/web/static/tests/core/widget_tests.js24
-rw-r--r--addons/web/static/tests/helpers/test_utils_mock.js5
-rw-r--r--addons/web/static/tests/views/abstract_controller_tests.js2
-rw-r--r--addons/web/static/tests/views/control_panel_tests.js81
-rw-r--r--addons/web/static/tests/views/pivot_tests.js97
-rw-r--r--addons/web/static/tests/views/search_filter_menu_tests.js54
-rw-r--r--addons/web/static/tests/views/search_view_tests.js10
-rw-r--r--addons/web/static/tests/views/view_dialogs_tests.js2
27 files changed, 403 insertions, 76 deletions
diff --git a/addons/web/static/lib/moment/locale/es.js b/addons/web/static/lib/moment/locale/es.js
index eb9e0809dfe..6d1f5907968 100644
--- a/addons/web/static/lib/moment/locale/es.js
+++ b/addons/web/static/lib/moment/locale/es.js
@@ -8,21 +8,30 @@
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
+var monthsShortDot = 'ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.'.split('_'),
+ monthsShort = 'ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic'.split('_');
-
-var monthsShortDot = 'ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.'.split('_');
-var monthsShort = 'ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic'.split('_');
+var monthsParse = [/^ene/i, /^feb/i, /^mar/i, /^abr/i, /^may/i, /^jun/i, /^jul/i, /^ago/i, /^sep/i, /^oct/i, /^nov/i, /^dic/i];
+var monthsRegex = /^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre|ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i;
var es = moment.defineLocale('es', {
months : 'enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre'.split('_'),
monthsShort : function (m, format) {
- if (/-MMM-/.test(format)) {
+ if (!m) {
+ return monthsShortDot;
+ } else if (/-MMM-/.test(format)) {
return monthsShort[m.month()];
} else {
return monthsShortDot[m.month()];
}
},
- monthsParseExact : true,
+ monthsRegex : monthsRegex,
+ monthsShortRegex : monthsRegex,
+ monthsStrictRegex : /^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre)/i,
+ monthsShortStrictRegex : /^(ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i,
+ monthsParse : monthsParse,
+ longMonthsParse : monthsParse,
+ shortMonthsParse : monthsParse,
weekdays : 'domingo_lunes_martes_miércoles_jueves_viernes_sábado'.split('_'),
weekdaysShort : 'dom._lun._mar._mié._jue._vie._sáb.'.split('_'),
weekdaysMin : 'do_lu_ma_mi_ju_vi_sá'.split('_'),
@@ -57,6 +66,7 @@ var es = moment.defineLocale('es', {
future : 'en %s',
past : 'hace %s',
s : 'unos segundos',
+ ss : '%d segundos',
m : 'un minuto',
mm : '%d minutos',
h : 'una hora',
diff --git a/addons/web/static/lib/py.js/lib/py.js b/addons/web/static/lib/py.js/lib/py.js
index bd794323995..60b80a7f439 100644
--- a/addons/web/static/lib/py.js/lib/py.js
+++ b/addons/web/static/lib/py.js/lib/py.js
@@ -273,8 +273,7 @@ var py = {};
var Special = '[:;.,`@]';
var Funny = group(Operator, Bracket, Special);
- var ContStr = group("([uU])?'([^']*)'", '([uU])?"([^"]*)"');
-
+ var ContStr = group("([uU])?'([^\n'\\\\]*(?:\\\\.[^\n'\\\\]*)*)'", '([uU])?"([^\n"\\\\]*(?:\\\\.[^\n"\\\\]*)*)"');
var PseudoToken = Whitespace + group(Number, Funny, ContStr, Name);
var number_pattern = new RegExp('^' + Number + '$');
@@ -282,9 +281,6 @@ var py = {};
var name_pattern = new RegExp('^' + Name + '$');
var strip = new RegExp('^' + Whitespace);
return function tokenize(s) {
- s = s
- .replace(/\\\"/g, '\\u0022') // for double quote
- .replace(/\\\'/g, '\\u0027'); // for single quote
var max=s.length, tokens = [], start, end;
// /g flag makes repeated exec() have memory
var pseudoprog = new RegExp(PseudoToken, 'g');
@@ -313,9 +309,6 @@ var py = {};
} else if (string_pattern.test(token)) {
var m = string_pattern.exec(token);
var value = (m[3] !== undefined ? m[3] : m[5]);
- value
- .replace(/\\u0022/g, '"')
- .replace(/\\u0027/g, "'");
tokens.push(create(symbols['(string)'], {
unicode: !!(m[2] || m[4]),
value: value
diff --git a/addons/web/static/src/js/core/abstract_storage_service.js b/addons/web/static/src/js/core/abstract_storage_service.js
index 34dd24b2be6..ef828b5dd5c 100644
--- a/addons/web/static/src/js/core/abstract_storage_service.js
+++ b/addons/web/static/src/js/core/abstract_storage_service.js
@@ -13,6 +13,17 @@ var AbstractStorageService = AbstractService.extend({
// this abstraction
storage: null,
+ /**
+ * @override
+ */
+ destroy: function () {
+ // storage can be permanent or transient, destroy transient ones
+ if ((this.storage || {}).destroy) {
+ this.storage.destroy();
+ }
+ this._super.apply(this, arguments);
+ },
+
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
diff --git a/addons/web/static/src/js/core/py_utils.js b/addons/web/static/src/js/core/py_utils.js
index 9bcd66864e5..296ab5c3089 100644
--- a/addons/web/static/src/js/core/py_utils.js
+++ b/addons/web/static/src/js/core/py_utils.js
@@ -415,7 +415,15 @@ function _formatAST(ast, lbp) {
case "(number)":
return String(ast.value);
case "(string)":
- return "'" + ast.value + "'";
+ // ast.value is a string that may contain a mix of single and double quotes.
+ // But we need to return a string that represents the string ast.value.
+ // We use for that JSON.stringify. It will return a string with quotes correctly
+ // escaped. For instance, for x = `""` JSON.stringify(x) is `"\"\""`.
+ // But this means that if we pass several times here, escapes will be
+ // done again and again, leading to a profusion of backslashes that we don't want.
+ // For fun consider y = `\\` and y = JSON.stringify(y).
+ // This is why we first deescape particular characters in ast.value.
+ return JSON.stringify(ast.value.replace(/(\\(['"\\]))/g,"\$2"));
case "(constant)":
return ast.value;
case "(name)":
diff --git a/addons/web/static/src/js/core/utils.js b/addons/web/static/src/js/core/utils.js
index cf64de72eef..44e09933a25 100644
--- a/addons/web/static/src/js/core/utils.js
+++ b/addons/web/static/src/js/core/utils.js
@@ -303,7 +303,17 @@ var utils = {
var d2 = Math.pow(10, decimals);
var val = _t('kMGTPE');
var symbol = '';
- for (var i = val.length - 1 ; i > 0 ; i--) {
+ var numberMagnitude = number.toExponential().split('e')[1];
+ // the case numberMagnitude >= 21 corresponds to a number
+ // better expressed in the scientific format.
+ if (numberMagnitude >= 21) {
+ // we do not use number.toExponential(decimals) because we want to
+ // avoid the possible useless O decimals: 1e.+24 prefered to 1.0e+24
+ number = Math.round(number * Math.pow(10, decimals - numberMagnitude)) / d2;
+ // formatterCallback seems useless here.
+ return number + 'e' + numberMagnitude;
+ }
+ for (var i = val.length; i > 0 ; i--) {
var s = Math.pow(10, i * 3);
if (s <= number / Math.pow(10, minDigits - 1)) {
number = Math.round(number * d2 / s) / d2;
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index dcfdc982b7d..c6df3885a86 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -2914,6 +2914,7 @@ var AceEditor = DebouncedField.extend({
if (this.aceEditor) {
this.aceEditor.destroy();
}
+ this._super.apply(this, arguments);
},
//--------------------------------------------------------------------------
diff --git a/addons/web/static/src/js/views/control_panel/control_panel_model.js b/addons/web/static/src/js/views/control_panel/control_panel_model.js
index f58fbc93c4f..e59dbfb1036 100644
--- a/addons/web/static/src/js/views/control_panel/control_panel_model.js
+++ b/addons/web/static/src/js/views/control_panel/control_panel_model.js
@@ -927,10 +927,21 @@ var ControlPanelModel = mvc.Model.extend({
}
var sort = JSON.parse(favorite.sort);
var orderedBy = sort.map(function (order) {
- var orderTerms = order.split(' ');
+ var fieldName;
+ var asc;
+ var sqlNotation = order.split(' ');
+ if (sqlNotation.length > 1) {
+ // regex: \fieldName (asc|desc)?\
+ fieldName = sqlNotation[0];
+ asc = sqlNotation[1] === 'asc';
+ } else {
+ // legacy notation -- regex: \-?fieldName\
+ fieldName = order[0] === '-' ? order.slice(1) : order;
+ asc = order[0] === '-' ? false : true;
+ }
return {
- name: orderTerms[0],
- asc: orderTerms.length === 2 && orderTerms[1] === 'asc',
+ name: fieldName,
+ asc: asc,
};
});
return {
diff --git a/addons/web/static/src/js/views/control_panel/search/filter_menu.js b/addons/web/static/src/js/views/control_panel/search/filter_menu.js
index 70a242dd0f8..04f333b37c4 100644
--- a/addons/web/static/src/js/views/control_panel/search/filter_menu.js
+++ b/addons/web/static/src/js/views/control_panel/search/filter_menu.js
@@ -89,8 +89,6 @@ var FilterMenu = DropdownMenu.extend({
domain: Domain.prototype.arrayToString(preFilter.attrs.domain),
};
});
- // TO DO intercepts 'new_filters' and decide what to do whith filters
- // rewrite web.search_filters?
this.trigger_up('new_filters', {filters: filters});
_.invoke(this.propositions, 'destroy');
this.propositions = [];
diff --git a/addons/web/static/src/js/views/control_panel/search/search_filters.js b/addons/web/static/src/js/views/control_panel/search/search_filters.js
index 4703995ac7c..80a9019c02d 100644
--- a/addons/web/static/src/js/views/control_panel/search/search_filters.js
+++ b/addons/web/static/src/js/views/control_panel/search/search_filters.js
@@ -171,15 +171,6 @@ var Char = Field.extend({
{value: "∃", text: _lt("is set")},
{value: "∄", text: _lt("is not set")}
],
- get_domain: function (field, operator) {
- switch (operator.value) {
- case '∃': return [[field.name, '!=', false]];
- case '∄': return [[field.name, '=', false]];
- // does not work in all cases
- // default: return "[('" + field.name + "', '" + operator.value + "', '" + this.get_value() + "')]";
- default: return [[field.name, operator.value, this.get_value()]];
- }
- },
get_value: function () {
return this.$el.val();
}
@@ -251,7 +242,7 @@ var DateTime = Field.extend({
_create_new_widget: function (name) {
this[name] = new (this._get_widget_class())(this);
return this[name].appendTo(this.$el).then((function () {
- this[name].setValue(moment(new Date()));
+ this[name].setValue(moment());
}).bind(this));
},
_get_widget_class: function () {
diff --git a/addons/web/static/src/js/views/graph/graph_renderer.js b/addons/web/static/src/js/views/graph/graph_renderer.js
index 09706695bbc..82204bf3bd5 100644
--- a/addons/web/static/src/js/views/graph/graph_renderer.js
+++ b/addons/web/static/src/js/views/graph/graph_renderer.js
@@ -21,6 +21,7 @@ var CHART_TYPES = ['pie', 'bar', 'line'];
// hide top legend when too many items for device size
var MAX_LEGEND_LENGTH = 25 * (Math.max(1, config.device.size_class));
+var SPLIT_THRESHOLD = config.device.isMobile ? Infinity : 20;
return AbstractRenderer.extend({
className: "o_graph_renderer",
@@ -417,7 +418,11 @@ return AbstractRenderer.extend({
}
}
- var $svgContainer = $('<div/>', {class: 'o_graph_svg_container'});
+ var $svgContainer = $('<div/>', { class: 'o_graph_svg_container'});
+ // Split the tooltip into columns for large data because some portion goes out off the screen.
+ if (data.length >= SPLIT_THRESHOLD) {
+ $svgContainer.addClass('o_tooltip_split_in_columns');
+ }
this.$el.append($svgContainer);
var svg = d3.select($svgContainer[0]).append('svg');
svg.datum(data);
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 4c37b823639..3f195c1ea24 100644
--- a/addons/web/static/src/js/views/pivot/pivot_model.js
+++ b/addons/web/static/src/js/views/pivot/pivot_model.js
@@ -237,7 +237,7 @@ var PivotModel = AbstractModel.extend({
[
this.data.timeRangeDescription.toString(),
this.data.comparisonTimeRangeDescription.toString(),
- 'Variation'
+ _t('Variation')
],
makeMeasure
)
@@ -443,8 +443,10 @@ var PivotModel = AbstractModel.extend({
self._updateMainGroupBys(old_row, self.data.main_row);
}
- self._updateTree(old_col.root, self.data.main_col.root);
- self._updateMainGroupBys(old_col, self.data.main_col);
+ if (!('pivot_column_groupby' in (params.context || {}))) {
+ self._updateTree(old_col.root, self.data.main_col.root);
+ self._updateMainGroupBys(old_col, self.data.main_col);
+ }
});
},
/**
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 813dc5d38b3..1a3c451f064 100644
--- a/addons/web/static/src/js/views/pivot/pivot_renderer.js
+++ b/addons/web/static/src/js/views/pivot/pivot_renderer.js
@@ -7,6 +7,7 @@ var field_utils = require('web.field_utils');
var config = require('web.config');
var QWeb = core.qweb;
+var _t = core._t;
var PivotRenderer = AbstractRenderer.extend({
tagName: 'table',
@@ -173,7 +174,7 @@ var PivotRenderer = AbstractRenderer.extend({
$thead.append($row);
}
if (this.compare) {
- var colLabels = [this.timeRangeDescription, this.comparisonTimeRangeDescription, 'Variation'];
+ var colLabels = [this.timeRangeDescription, this.comparisonTimeRangeDescription, _t('Variation')];
var dataTypes = ['data', 'comparisonData', 'variation'];
$row = $('<tr>');
for (i = 0; i < 3 * nbrCols; i++) {
diff --git a/addons/web/static/src/scss/form_view.scss b/addons/web/static/src/scss/form_view.scss
index 5642e656dea..0645e00544e 100644
--- a/addons/web/static/src/scss/form_view.scss
+++ b/addons/web/static/src/scss/form_view.scss
@@ -744,6 +744,10 @@
.o_field_widget {
width: 50%;
flex: 0 0 auto;
+
+ &.o_field_many2manytags > .o_field_widget {
+ flex: 1 0 50px;
+ }
}
button.btn-link:first-child {
padding-left: 0;
diff --git a/addons/web/static/src/scss/graph_view.scss b/addons/web/static/src/scss/graph_view.scss
index 71ec5fbca9c..e67edbd3cb5 100644
--- a/addons/web/static/src/scss/graph_view.scss
+++ b/addons/web/static/src/scss/graph_view.scss
@@ -24,6 +24,35 @@
// ligther area color
fill-opacity: 0.1;
}
+ .nv-lineChart .nv-x.nv-axis .nv-axisMaxMin-x {
+ &.nv-axisMin-x text {
+ text-anchor: start !important;
+ }
+ &.nv-axisMax-x text {
+ text-anchor: end !important;
+ }
+ }
+ }
+
+ /* Use less than 50% of the screen area for tooltip and show legends in 3 column of 33% each */
+ &.o_tooltip_split_in_columns .nvtooltip {
+ max-width: 48%;
+ tbody {
+ display: flex;
+ flex-flow: wrap;
+ tr {
+ width: 33%;
+ td {
+ display: inline-block;
+ &.key {
+ min-width: 160px;
+ max-width: 70%;
+ overflow-x: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+ }
+ }
}
}
}
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index 43d8f6900e8..8126fcf11c4 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -347,7 +347,7 @@
<t t-raw="item.label"/>
</a>
</t>
- <div class="dropdown-menu" role="menu">
+ <div class="dropdown-menu o_dropdown_menu" role="menu">
<t t-foreach="widget.items[section.name]" t-as="item">
<t t-if="section.name == 'files'">
<t t-set="item.title">
diff --git a/addons/web/static/tests/chrome/action_manager_tests.js b/addons/web/static/tests/chrome/action_manager_tests.js
index 291084402a9..4b8a3a79751 100644
--- a/addons/web/static/tests/chrome/action_manager_tests.js
+++ b/addons/web/static/tests/chrome/action_manager_tests.js
@@ -3133,7 +3133,7 @@ QUnit.module('ActionManager', {
intercepts: {
create_filter: function (event) {
var filter = event.data.filter;
- assert.deepEqual(filter.domain, "[('bar', '=', 1)]",
+ assert.deepEqual(filter.domain, `[("bar", "=", 1)]`,
"should save the correct domain");
assert.deepEqual(filter.context, {shouldBeInFilterContext: true},
"should save the correct context");
diff --git a/addons/web/static/tests/chrome/user_menu_tests.js b/addons/web/static/tests/chrome/user_menu_tests.js
index 600ac125dd4..0151afdf6d6 100644
--- a/addons/web/static/tests/chrome/user_menu_tests.js
+++ b/addons/web/static/tests/chrome/user_menu_tests.js
@@ -25,6 +25,7 @@ QUnit.module('chrome', {}, function () {
"should have a 'Shortcuts' item");
userMenu.destroy();
+ parent.destroy();
});
});
diff --git a/addons/web/static/tests/core/py_utils_tests.js b/addons/web/static/tests/core/py_utils_tests.js
index be5760fc6c7..fba8017cf01 100644
--- a/addons/web/static/tests/core/py_utils_tests.js
+++ b/addons/web/static/tests/core/py_utils_tests.js
@@ -1149,7 +1149,7 @@ QUnit.module('core', function () {
assert.checkAST("1.4", "float value");
assert.checkAST("-12", "negative integer value");
assert.checkAST("True", "boolean");
- assert.checkAST("'some string'", "a string");
+ assert.checkAST(`"some string"`, "a string");
assert.checkAST("None", "None");
});
@@ -1157,8 +1157,8 @@ QUnit.module('core', function () {
assert.expect(3);
assert.checkAST("{}", "empty dictionary");
- assert.checkAST("{'a': 1}", "dictionary with a single key");
- assert.checkAST("d['a']", "get a value in a dictionary");
+ assert.checkAST(`{"a": 1}`, "dictionary with a single key");
+ assert.checkAST(`d["a"]`, "get a value in a dictionary");
});
QUnit.test("list", function (assert) {
@@ -1225,14 +1225,14 @@ QUnit.module('core', function () {
QUnit.test("strftime", function (assert) {
assert.expect(3);
- assert.checkAST("time.strftime('%Y')", "strftime with year");
- assert.checkAST("time.strftime('%Y') + '-01-30'", "strftime with year");
- assert.checkAST("time.strftime('%Y-%m-%d %H:%M:%S')", "strftime with year");
+ assert.checkAST(`time.strftime("%Y")`, "strftime with year");
+ assert.checkAST(`time.strftime("%Y") + "-01-30"`, "strftime with year");
+ assert.checkAST(`time.strftime("%Y-%m-%d %H:%M:%S")`, "strftime with year");
});
QUnit.test("context_today", function (assert) {
assert.expect(1);
- assert.checkAST("context_today().strftime('%Y-%m-%d')", "context today call");
+ assert.checkAST(`context_today().strftime("%Y-%m-%d")`, "context today call");
});
@@ -1250,17 +1250,10 @@ QUnit.module('core', function () {
assert.checkAST('(a - b).days', "substraction and .days");
assert.checkAST('a + day == date(2002, 3, 3)');
- var expr = "[('type', '=', 'in'), ('day', '<=', time.strftime('%Y-%m-%d')), ('day', '>', (context_today() - datetime.timedelta(days = 15)).strftime('%Y-%m-%d'))]";
+ var expr = `[("type", "=", "in"), ("day", "<=", time.strftime("%Y-%m-%d")), ("day", ">", (context_today() - datetime.timedelta(days = 15)).strftime("%Y-%m-%d"))]`;
assert.checkAST(expr);
});
- QUnit.test("parse escaped quoted strings", function (assert) {
- assert.expect(2);
-
- assert.checkAST(`'\"'`, "a string containing \"");
- assert.checkAST(`'\'`, "a string containing \'");
- });
-
QUnit.module('pyutils (_normalizeDomain)');
QUnit.assert.checkNormalization = function (domain, normalizedDomain) {
@@ -1278,23 +1271,23 @@ QUnit.module('core', function () {
assert.expect(3);
assert.checkNormalization("[]");
- assert.checkNormalization("[('a', '=', 1)]");
- assert.checkNormalization("['!', ('a', '=', 1)]");
+ assert.checkNormalization(`[("a", "=", 1)]`);
+ assert.checkNormalization(`["!", ("a", "=", 1)]`);
});
QUnit.test("properly add the & in a non normalized domain", function (assert) {
assert.expect(1);
assert.checkNormalization(
- "[('a', '=', 1), ('b', '=', 2)]",
- "['&', ('a', '=', 1), ('b', '=', 2)]"
+ `[("a", "=", 1), ("b", "=", 2)]`,
+ `["&", ("a", "=", 1), ("b", "=", 2)]`
);
});
QUnit.test("normalize domain with ! operator", function (assert) {
assert.expect(1);
assert.checkNormalization(
- "['!', ('a', '=', 1), ('b', '=', 2)]",
- "['&', '!', ('a', '=', 1), ('b', '=', 2)]"
+ `["!", ("a", "=", 1), ("b", "=", 2)]`,
+ `["&", "!", ("a", "=", 1), ("b", "=", 2)]`
);
});
diff --git a/addons/web/static/tests/core/util_tests.js b/addons/web/static/tests/core/util_tests.js
index 3dd0a54b5e3..368f36c2fc6 100644
--- a/addons/web/static/tests/core/util_tests.js
+++ b/addons/web/static/tests/core/util_tests.js
@@ -67,6 +67,26 @@ QUnit.module('core', {}, function () {
assert.strictEqual("MzGzHz", caseSensetiveCharacters);
});
+ QUnit.test('human_number', function (assert) {
+ assert.expect(13);
+
+ var human_number = utils.human_number;
+
+ assert.strictEqual(human_number(1020, 2, 1), '1.02k');
+ assert.strictEqual(human_number(1020000, 2, 2), '1020k');
+ assert.strictEqual(human_number(10200000, 2, 2), '10.2M');
+ assert.strictEqual(human_number(1020, 2, 1), '1.02k');
+ assert.strictEqual(human_number(1002, 2, 1), '1k');
+ assert.strictEqual(human_number(101, 2, 1), '101');
+ assert.strictEqual(human_number(64.2, 2, 1), '64');
+ assert.strictEqual(human_number(1e+18), '1E');
+ assert.strictEqual(human_number(1e+21, 2, 1), '1e+21');
+ assert.strictEqual(human_number(1.0045e+22, 2, 1), '1e+22');
+ assert.strictEqual(human_number(1.0045e+22, 3, 1), '1.005e+22');
+ assert.strictEqual(human_number(1.012e+43, 2, 1), '1.01e+43');
+ assert.strictEqual(human_number(1.012e+43, 2, 2), '1.01e+43');
+ });
+
});
});
diff --git a/addons/web/static/tests/core/widget_tests.js b/addons/web/static/tests/core/widget_tests.js
index 834033a1769..0eb4192c4a1 100644
--- a/addons/web/static/tests/core/widget_tests.js
+++ b/addons/web/static/tests/core/widget_tests.js
@@ -24,6 +24,7 @@ QUnit.module('core', {}, function () {
var fn = w.proxy('exec');
fn();
assert.ok(w.executed, 'should execute the named method in the right context');
+ w.destroy();
});
QUnit.test('proxy (String)(*args)', function (assert) {
@@ -39,6 +40,7 @@ QUnit.module('core', {}, function () {
fn(42);
assert.ok(w.executed, "should execute the named method in the right context");
assert.strictEqual(w.executed, 42, "should be passed the proxy's arguments");
+ w.destroy();
});
QUnit.test('proxy (String), include', function (assert) {
@@ -59,6 +61,7 @@ QUnit.module('core', {}, function () {
fn();
assert.strictEqual(w.executed, 2, "should be lazily resolved");
+ w.destroy();
});
QUnit.test('proxy (Function)', function (assert) {
@@ -69,6 +72,7 @@ QUnit.module('core', {}, function () {
var fn = w.proxy(function () { this.executed = true; });
fn();
assert.ok(w.executed, "should set the function's context (like Function#bind)");
+ w.destroy();
});
QUnit.test('proxy (Function)(*args)', function (assert) {
@@ -79,6 +83,7 @@ QUnit.module('core', {}, function () {
var fn = w.proxy(function (arg) { this.executed = arg; });
fn(42);
assert.strictEqual(w.executed, 42, "should be passed the proxy's arguments");
+ w.destroy();
});
@@ -99,6 +104,7 @@ QUnit.module('core', {}, function () {
assert.strictEqual(widget.el.nodeName, 'DIV', "should have generated the default element");
assert.strictEqual(widget.el.attributes.length, 0, "should not have generated any attribute");
assert.ok(_.isEmpty(widget.$el.html(), "should not have generated any content"));
+ widget.destroy();
});
QUnit.test('no template, custom tag', function (assert) {
@@ -111,6 +117,7 @@ QUnit.module('core', {}, function () {
widget.renderElement();
assert.strictEqual(widget.el.nodeName, 'UL', "should have generated the custom element tag");
+ widget.destroy();
});
QUnit.test('no template, @id', function (assert) {
@@ -124,6 +131,7 @@ QUnit.module('core', {}, function () {
assert.strictEqual(widget.el.attributes.length, 1, "should have one attribute");
assert.hasAttrValue(widget.$el, 'id', 'foo', "should have generated the id attribute");
assert.strictEqual(widget.el.id, 'foo', "should also be available via property");
+ widget.destroy();
});
QUnit.test('no template, @className', function (assert) {
@@ -136,6 +144,7 @@ QUnit.module('core', {}, function () {
assert.strictEqual(widget.el.className, 'oe_some_class', "should have the right property");
assert.hasAttrValue(widget.$el, 'class', 'oe_some_class', "should have the right attribute");
+ widget.destroy();
});
QUnit.test('no template, bunch of attributes', function (assert) {
@@ -166,6 +175,7 @@ QUnit.module('core', {}, function () {
assert.hasAttrValue(widget.$el, 'clark', 'gable');
assert.hasAttrValue(widget.$el, 'spoiler', 'snape kills dumbledore');
+ widget.destroy();
});
QUnit.test('template', function (assert) {
@@ -193,6 +203,7 @@ QUnit.module('core', {}, function () {
assert.strictEqual(widget.el.nodeName, 'OL');
assert.strictEqual(widget.$el.children().length, 5);
assert.strictEqual(widget.el.textContent, '01234');
+ widget.destroy();
});
QUnit.test('repeated', function (assert) {
@@ -219,6 +230,8 @@ QUnit.module('core', {}, function () {
widget.renderElement();
assert.strictEqual($fix.find('p').text(), '36', "DOM fixture should use new value");
assert.strictEqual(widget.$el.text(), '36', "should set new value");
+ }).always(function () {
+ widget.destroy();
});
});
@@ -257,6 +270,7 @@ QUnit.module('core', {}, function () {
assert.ok(widget.$('li:eq(3)').is(widget.$el.find('li:eq(3)')),
"should do the same thing as calling find on the widget root");
+ widget.destroy();
});
@@ -285,6 +299,7 @@ QUnit.module('core', {}, function () {
for(var i=0; i<3; ++i) {
assert.ok(a[i], "should pass test " + i);
}
+ widget.destroy();
});
QUnit.test('undelegate', function (assert) {
@@ -310,6 +325,7 @@ QUnit.module('core', {}, function () {
widget.$('li').click();
assert.ok(!clicked, "undelegate should unbind events delegated");
assert.ok(newclicked, "undelegate should only unbind events it created");
+ widget.destroy();
});
QUnit.module('Widget, and async stuff');
@@ -321,7 +337,8 @@ QUnit.module('core', {}, function () {
return concurrency.asyncWhen(widget.start())
.then(function () { return widget.alive(concurrency.asyncWhen()); })
- .then(function () { assert.ok(true); });
+ .then(function () { assert.ok(true); })
+ .always(function () { widget.destroy(); });
});
QUnit.test("alive(dead)", function (assert) {
@@ -355,7 +372,8 @@ QUnit.module('core', {}, function () {
var widget = new (Widget.extend({}));
return concurrency.asyncWhen(widget.start())
.then(function () { return widget.alive(concurrency.asyncWhen(), true) })
- .then(function () { assert.ok(true); });
+ .then(function () { assert.ok(true); })
+ .always(function () { widget.destroy(); });
});
QUnit.test("alive(dead, true)", function (assert) {
@@ -445,4 +463,4 @@ QUnit.module('core', {}, function () {
});
-}); \ No newline at end of file
+});
diff --git a/addons/web/static/tests/helpers/test_utils_mock.js b/addons/web/static/tests/helpers/test_utils_mock.js
index 27dbaeeaa44..2ecc655db7e 100644
--- a/addons/web/static/tests/helpers/test_utils_mock.js
+++ b/addons/web/static/tests/helpers/test_utils_mock.js
@@ -199,6 +199,11 @@ function addMockEnvironment(widget, params) {
// widget is destroyed, at the end of each test to avoid collisions
core.bus.trigger('clear_cache');
+ _(services).chain()
+ .compact() // services can be defined but null (e.g. ajax)
+ .reject(function (s) { return s.isDestroyed(); })
+ .invoke('destroy');
+
DebouncedField.prototype.DEBOUNCE = initialDebounceValue;
dom.DEBOUNCE = initialDOMDebounceValue;
if (params.debounce === false) {
diff --git a/addons/web/static/tests/views/abstract_controller_tests.js b/addons/web/static/tests/views/abstract_controller_tests.js
index 73bc41d650b..f9045645bc6 100644
--- a/addons/web/static/tests/views/abstract_controller_tests.js
+++ b/addons/web/static/tests/views/abstract_controller_tests.js
@@ -80,6 +80,8 @@ odoo.define("base.abstract_controller_tests", function(require) {
testUtils.dom.click(view.$(".descr"));
testUtils.dom.click(view.$(".descr2"));
assert.verifySteps(["a1", "a2", "method", "method", "descr", "descr2"]);
+
+ view.destroy();
}
);
}
diff --git a/addons/web/static/tests/views/control_panel_tests.js b/addons/web/static/tests/views/control_panel_tests.js
index ee191e4d3a9..f2654e55e1b 100644
--- a/addons/web/static/tests/views/control_panel_tests.js
+++ b/addons/web/static/tests/views/control_panel_tests.js
@@ -415,5 +415,86 @@ QUnit.module('Views', {
controlPanel.destroy();
});
+
+ QUnit.test('load filter', async function (assert) {
+ assert.expect(1);
+
+ var controlPanel = await createControlPanel({
+ model: 'partner',
+ arch: "<search></search>",
+ data: this.data,
+ searchMenuTypes: ['filter'],
+ intercepts: {
+ load_filters: function (ev) {
+ ev.data.on_success([
+ {
+ user_id: [2,"Mitchell Admin"],
+ name: 'sorted filter',
+ id: 5,
+ context: {},
+ sort: "[\"foo\", \"-bar\"]",
+ domain: "[('user_id', '=', uid)]",
+ }
+ ]);
+ }
+ }
+ });
+
+ _.each(controlPanel.exportState().filters, function (filter) {
+ if (filter.type === 'favorite') {
+ assert.deepEqual(filter.orderedBy,
+ [{
+ name: 'foo',
+ asc: true,
+ }, {
+ name: 'bar',
+ asc: false,
+ }],
+ 'the filter should have the right orderedBy values');
+ }
+ });
+
+ controlPanel.destroy();
+ });
+
+ QUnit.test('save filter', async function (assert) {
+ assert.expect(1);
+
+ var controlPanel = await createControlPanel({
+ model: 'partner',
+ arch: "<search></search>",
+ data: this.data,
+ searchMenuTypes: ['filter'],
+ intercepts: {
+ create_filter: function (ev) {
+ assert.strictEqual(ev.data.filter.sort, "[\"foo\",\"bar desc\"]",
+ 'The right format for the string "sort" should be sent to the server');
+ },
+ get_controller_query_params: function (ev) {
+ ev.data.callback({
+ orderedBy: [
+ {
+ name: 'foo',
+ asc: true,
+ }, {
+ name: 'bar',
+ asc: false,
+ }
+ ]
+ });
+ }
+ }
+ });
+
+ controlPanel._onNewFavorite({
+ data: {
+ description: 'Morbier',
+ type: 'favorite',
+ },
+ stopPropagation: function () {return;}
+ });
+
+ controlPanel.destroy();
+ });
});
});
diff --git a/addons/web/static/tests/views/pivot_tests.js b/addons/web/static/tests/views/pivot_tests.js
index 7af2892ac33..0e7e7ff0a2d 100644
--- a/addons/web/static/tests/views/pivot_tests.js
+++ b/addons/web/static/tests/views/pivot_tests.js
@@ -1007,6 +1007,95 @@ QUnit.module('Views', {
pivot.destroy();
});
+ QUnit.test('Unload Filter, reset display, load another filter', function (assert) {
+ assert.expect(18);
+
+ var pivot = createView({
+ View: PivotView,
+ model: "partner",
+ data: this.data,
+ arch: '<pivot>' +
+ '<field name="foo" type="measure"/>' +
+ '</pivot>',
+ viewOptions: {
+ context: {
+ pivot_measures: ['foo'],
+ pivot_column_groupby: ['customer'],
+ pivot_row_groupby: ['product_id'],
+ },
+ },
+ });
+
+ // Check Columns
+ assert.strictEqual(pivot.$('thead .o_pivot_header_cell_opened').length, 1,
+ 'The column should be grouped');
+ assert.strictEqual(pivot.$('thead tr:contains("First")').length, 1,
+ 'There should be a column "First"');
+ assert.strictEqual(pivot.$('thead tr:contains("Second")').length, 1,
+ 'There should be a column "Second"');
+
+ // Check Rows
+ assert.strictEqual(pivot.$('tbody .o_pivot_header_cell_opened').length, 1,
+ 'The row should be grouped');
+ assert.strictEqual(pivot.$('tbody tr:contains("xphone")').length, 1,
+ 'There should be a row "xphone"');
+ assert.strictEqual(pivot.$('tbody tr:contains("xpad")').length, 1,
+ 'There should be a row "xpad"');
+
+ // Equivalent to unload the filter
+ var reloadParams = {
+ context: {},
+ };
+ pivot.reload(reloadParams);
+ // collapse all headers
+ pivot.$('.o_pivot_header_cell_opened').click();
+ pivot.$('.o_pivot_header_cell_opened').click();
+
+ // Check Columns
+ assert.strictEqual(pivot.$('thead .o_pivot_header_cell_closed').length, 1,
+ 'The column should not be grouped');
+ assert.strictEqual(pivot.$('thead tr:contains("First")').length, 0,
+ 'There should not be a column "First"');
+ assert.strictEqual(pivot.$('thead tr:contains("Second")').length, 0,
+ 'There should not be a column "Second"');
+
+ // Check Rows
+ assert.strictEqual(pivot.$('tbody .o_pivot_header_cell_closed').length, 1,
+ 'The row should not be grouped');
+ assert.strictEqual(pivot.$('tbody tr:contains("xphone")').length, 0,
+ 'There should not be a row "xphone"');
+ assert.strictEqual(pivot.$('tbody tr:contains("xpad")').length, 0,
+ 'There should not be a row "xpad"');
+
+ // Equivalent to load another filter
+ reloadParams = {
+ context: {
+ pivot_measures: ['foo'],
+ pivot_column_groupby: ['customer'],
+ pivot_row_groupby: ['product_id'],
+ },
+ };
+ pivot.reload(reloadParams);
+
+ // Check Columns
+ assert.strictEqual(pivot.$('thead .o_pivot_header_cell_opened').length, 1,
+ 'The column should be grouped');
+ assert.strictEqual(pivot.$('thead tr:contains("First")').length, 1,
+ 'There should be a column "First"');
+ assert.strictEqual(pivot.$('thead tr:contains("Second")').length, 1,
+ 'There should be a column "Second"');
+
+ // Check Rows
+ assert.strictEqual(pivot.$('tbody .o_pivot_header_cell_opened').length, 1,
+ 'The row should be grouped');
+ assert.strictEqual(pivot.$('tbody tr:contains("xphone")').length, 1,
+ 'There should be a row "xphone"');
+ assert.strictEqual(pivot.$('tbody tr:contains("xpad")').length, 1,
+ 'There should be a row "xpad"');
+
+ pivot.destroy();
+ });
+
QUnit.test('correctly uses pivot_ keys from the context', function (assert) {
assert.expect(7);
@@ -1441,7 +1530,7 @@ QUnit.module('Views', {
'<field name="foo" type="measure"/>' +
'</pivot>',
'partner,false,search': '<search></search>',
- },
+ }
});
actionManager.doAction({
@@ -1512,6 +1601,12 @@ QUnit.module('Views', {
];
checkCellValues(results);
+ $('.o_search_options button:contains("Favorites")').click();
+ var $favorites = $('.dropdown-menu.o_favorites_menu');
+ $favorites.find('a.o_save_search').click();
+ $favorites.find('.o_input').val('Fav').trigger('input');
+ $favorites.find('button').click();
+
unpatchDate();
actionManager.destroy();
});
diff --git a/addons/web/static/tests/views/search_filter_menu_tests.js b/addons/web/static/tests/views/search_filter_menu_tests.js
index e907d70111e..92d49cc23ae 100644
--- a/addons/web/static/tests/views/search_filter_menu_tests.js
+++ b/addons/web/static/tests/views/search_filter_menu_tests.js
@@ -1,9 +1,10 @@
odoo.define('web.search_filters_menu_tests', function (require) {
"use strict";
+var Domain = require('web.Domain');
var FilterMenu = require('web.FilterMenu');
+var pyUtils = require('web.py_utils');
var testUtils = require('web.test_utils');
-var Domain = require('web.Domain');
function createFilterMenu(filters, fields, params) {
params = params || {};
@@ -134,33 +135,70 @@ QUnit.module('FilterMenu', {
filterMenu.destroy();
});
- QUnit.skip('commit search with an extended proposition with field char does not cause a crash', function (assert) {
- assert.expect(0);
+ QUnit.test('commit search with an extended proposition with field char does not cause a crash', function (assert) {
+ assert.expect(6);
this.fields = {many2one_field: {string: "Trululu", type: "many2one", relation: 'partner', selectable: true, searchable: true}};
+ var expectedDomains = [
+ [['many2one_field','ilike', `a`]],
+ [['many2one_field','ilike', `"a"`]],
+ [['many2one_field','ilike', `'a'`]],
+ [['many2one_field','ilike', `'`]],
+ [['many2one_field','ilike', `"`]],
+ [['many2one_field','ilike', `\\`]],
+ ];
+
var filterMenu = createFilterMenu([], this.fields, {
intercepts: {
new_filters: function (ev) {
var filter = ev.data.filters[0];
- Domain.prototype.stringToArray(filter.domain);
+ // this step combine a tokenization/parsing followed by a string formatting
+ var domain = pyUtils.assembleDomains([filter.domain]);
+ domain = Domain.prototype.stringToArray(domain);
+ assert.deepEqual(domain, expectedDomains.shift());
},
}
});
- // open menu dropdown and custom filter submenu, select trululu field and enter string "a", then click apply
+ // open menu dropdown and custom filter submenu, select trululu field and enter string `a`, then click apply
testUtils.dom.click(filterMenu.$('span.fa-filter'));
testUtils.dom.click(filterMenu.$('.o_add_custom_filter'));
testUtils.fields.editSelect(filterMenu.$('.o_filter_condition select.o_input.o_searchview_extended_prop_field'), 'many2one_field');
- testUtils.fields.editInput(filterMenu.$('.o_filter_condition .o_searchview_extended_prop_value input'), "a");
+ testUtils.fields.editInput(filterMenu.$('.o_filter_condition .o_searchview_extended_prop_value input'), `a`);
+ testUtils.dom.click(filterMenu.$('.o_apply_filter'));
+
+
+ // open custom filter submenu, select trululu field and enter string `"a"`, then click apply
+ testUtils.dom.click(filterMenu.$('.o_add_custom_filter'));
+ testUtils.fields.editSelect(filterMenu.$('.o_filter_condition select.o_input.o_searchview_extended_prop_field'), 'many2one_field');
+ testUtils.fields.editInput(filterMenu.$('.o_filter_condition .o_searchview_extended_prop_value input'), `"a"`);
+ testUtils.dom.click(filterMenu.$('.o_apply_filter'));
+
+ // open custom filter submenu, select trululu field and enter string `'a'`, then click apply
+ testUtils.dom.click(filterMenu.$('.o_add_custom_filter'));
+ testUtils.fields.editSelect(filterMenu.$('.o_filter_condition select.o_input.o_searchview_extended_prop_field'), 'many2one_field');
+ testUtils.fields.editInput(filterMenu.$('.o_filter_condition .o_searchview_extended_prop_value input'), `'a'`);
testUtils.dom.click(filterMenu.$('.o_apply_filter'));
+ // open custom filter submenu, select trululu field and enter string `'`, then click apply
+ testUtils.dom.click(filterMenu.$('.o_add_custom_filter'));
+ testUtils.fields.editSelect(filterMenu.$('.o_filter_condition select.o_input.o_searchview_extended_prop_field'), 'many2one_field');
+ testUtils.fields.editInput(filterMenu.$('.o_filter_condition .o_searchview_extended_prop_value input'), `'`);
+ testUtils.dom.click(filterMenu.$('.o_apply_filter'));
- // open custom filter submenu, select trululu field and enter string "'a'", then click apply
+ // open custom filter submenu, select trululu field and enter string `"`, then click apply
testUtils.dom.click(filterMenu.$('.o_add_custom_filter'));
testUtils.fields.editSelect(filterMenu.$('.o_filter_condition select.o_input.o_searchview_extended_prop_field'), 'many2one_field');
- testUtils.fields.editInput(filterMenu.$('.o_filter_condition .o_searchview_extended_prop_value input'), '"a"');
+ testUtils.fields.editInput(filterMenu.$('.o_filter_condition .o_searchview_extended_prop_value input'), `"`);
testUtils.dom.click(filterMenu.$('.o_apply_filter'));
+
+ // open custom filter submenu, select trululu field and enter string `\`, then click apply
+ testUtils.dom.click(filterMenu.$('.o_add_custom_filter'));
+ testUtils.fields.editSelect(filterMenu.$('.o_filter_condition select.o_input.o_searchview_extended_prop_field'), 'many2one_field');
+ testUtils.fields.editInput(filterMenu.$('.o_filter_condition .o_searchview_extended_prop_value input'), `\\`);
+ testUtils.dom.click(filterMenu.$('.o_apply_filter'));
+
filterMenu.destroy();
});
});
diff --git a/addons/web/static/tests/views/search_view_tests.js b/addons/web/static/tests/views/search_view_tests.js
index 87484bf855d..4916834e1bc 100644
--- a/addons/web/static/tests/views/search_view_tests.js
+++ b/addons/web/static/tests/views/search_view_tests.js
@@ -750,10 +750,10 @@ QUnit.module('Search View', {
create_filter: function (ev) {
assert.equal(
ev.data.filter.domain,
- "['&', " +
- "('date_field', '>=', (context_today() + relativedelta()).strftime('%Y-%m-%d')), " +
- "('date_field', '<', (context_today() + relativedelta(days = 1)).strftime('%Y-%m-%d'))"+
- "]");
+ `["&", ` +
+ `("date_field", ">=", (context_today() + relativedelta()).strftime("%Y-%m-%d")), ` +
+ `("date_field", "<", (context_today() + relativedelta(days = 1)).strftime("%Y-%m-%d"))`+
+ `]`);
},
},
});
@@ -841,7 +841,7 @@ QUnit.module('Search View', {
data: this.data,
intercepts: {
create_filter: function (ev) {
- assert.ok(ev.data.filter.domain === "[['foo', 'ilike', 'a']]");
+ assert.ok(ev.data.filter.domain === `[["foo", "ilike", "a"]]`);
},
},
});
diff --git a/addons/web/static/tests/views/view_dialogs_tests.js b/addons/web/static/tests/views/view_dialogs_tests.js
index 1df70b03acc..27275e7fd80 100644
--- a/addons/web/static/tests/views/view_dialogs_tests.js
+++ b/addons/web/static/tests/views/view_dialogs_tests.js
@@ -443,7 +443,7 @@ QUnit.module('Views', {
intercepts: {
create_filter: function (event) {
var filter = event.data.filter;
- assert.deepEqual(filter.domain, "[('bar', '=', True)]",
+ assert.deepEqual(filter.domain, `[("bar", "=", True)]`,
"should save the correct domain");
assert.deepEqual(filter.context, {shouldBeInFilterContext: true},
"should save the correct context");