summaryrefslogtreecommitdiff
path: root/addons/web
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web')
-rw-r--r--addons/web/i18n/es.po7
-rw-r--r--addons/web/i18n/web.pot6
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js101
-rw-r--r--addons/web/static/src/js/views/abstract_controller.js3
-rw-r--r--addons/web/static/src/js/views/control_panel/search/search_bar.js20
-rw-r--r--addons/web/static/src/js/views/control_panel/search/search_facet.js20
-rw-r--r--addons/web/static/src/js/views/kanban/search_panel.js5
-rw-r--r--addons/web/static/src/js/widgets/auto_complete.js14
-rw-r--r--addons/web/static/src/scss/banner.scss3
-rw-r--r--addons/web/static/src/scss/modal.scss5
-rw-r--r--addons/web/static/tests/fields/basic_fields_tests.js441
-rw-r--r--addons/web/static/tests/views/search_panel_tests.js55
12 files changed, 637 insertions, 43 deletions
diff --git a/addons/web/i18n/es.po b/addons/web/i18n/es.po
index b18b803478c..f8682c1860f 100644
--- a/addons/web/i18n/es.po
+++ b/addons/web/i18n/es.po
@@ -2761,13 +2761,16 @@ msgid ""
" <span class=\"page\"/>\n"
" of\n"
" <span class=\"topage\"/>"
-msgstr "<span class=\"page\"/><span class=\"topage\"/>"
+msgstr "Página:\n"
+" <span class=\"page\"/>\n"
+" de\n"
+" <span class=\"topage\"/>"
#. module: web
#: model_terms:ir.ui.view,arch_db:web.external_layout_boxed
#: model_terms:ir.ui.view,arch_db:web.external_layout_standard
msgid "Page: <span class=\"page\"/> / <span class=\"topage\"/>"
-msgstr "<span class=\"page\"/><span class=\"topage\"/>"
+msgstr "Página: <span class=\"page\"/> / <span class=\"topage\"/>"
#. module: web
#: model_terms:ir.ui.view,arch_db:web.login
diff --git a/addons/web/i18n/web.pot b/addons/web/i18n/web.pot
index 4224c70aef9..6a3b4b9cf6e 100644
--- a/addons/web/i18n/web.pot
+++ b/addons/web/i18n/web.pot
@@ -2584,6 +2584,12 @@ msgid "Only Integer Value should be valid."
msgstr ""
#. module: web
+#: code:addons/web/static/src/js/fields/basic_fields.js:1982
+#, python-format
+msgid "Only Integer or Float Value should be valid."
+msgstr ""
+
+#. module: web
#: code:addons/web/controllers/main.py:537
#, python-format
msgid ""
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index 160f445f1c4..eeda3d220b6 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -2329,11 +2329,28 @@ var FieldProgressBar = AbstractField.extend({
if (this.recordData[this.nodeOptions.current_value]) {
this.value = this.recordData[this.nodeOptions.current_value];
}
- this.max_value = this.recordData[this.nodeOptions.max_value] || 100;
+
+ // The few next lines determine if the widget can write on the record or not
+ this.editable_readonly = !!this.nodeOptions.editable_readonly;
+ // "hard" readonly
this.readonly = this.nodeOptions.readonly || !this.nodeOptions.editable;
- this.edit_max_value = this.nodeOptions.edit_max_value || false;
+
+ this.canWrite = !this.readonly && (
+ this.mode === 'edit' ||
+ (this.editable_readonly && this.mode === 'readonly') ||
+ (this.viewType === 'kanban') // Keep behavior before commit
+ );
+
+ // Boolean to toggle if we edit the numerator (value) or the denominator (max_value)
+ this.edit_max_value = !!this.nodeOptions.edit_max_value;
+ this.max_value = this.recordData[this.nodeOptions.max_value] || 100;
+
this.title = _t(this.attrs.title || this.nodeOptions.title) || '';
- this.edit_on_click = !this.nodeOptions.edit_max_value || false;
+
+ // Ability to edit the field through the bar
+ // /!\ this feature is disabled
+ this.enableBarAsInput = false;
+ this.edit_on_click = this.enableBarAsInput && this.mode === 'readonly' && !this.edit_max_value;
this.write_mode = false;
},
@@ -2341,19 +2358,19 @@ var FieldProgressBar = AbstractField.extend({
var self = this;
this._render_value();
- if (!this.readonly) {
+ if (this.canWrite) {
if (this.edit_on_click) {
this.$el.on('click', '.o_progress', function (e) {
var $target = $(e.currentTarget);
- self.value = Math.floor((e.pageX - $target.offset().left) / $target.outerWidth() * self.max_value);
+ var numValue = Math.floor((e.pageX - $target.offset().left) / $target.outerWidth() * self.max_value);
+ self.on_update(numValue);
self._render_value();
- self.on_update(self.value);
});
} else {
this.$el.on('click', function () {
if (!self.write_mode) {
var $input = $('<input>', {type: 'text', class: 'o_progressbar_value o_input'});
- $input.on('blur', _.bind(self.on_change_input, self));
+ $input.on('blur', self.on_change_input.bind(self));
self.$('.o_progressbar_value').replaceWith($input);
self.write_mode = true;
self._render_value();
@@ -2363,24 +2380,25 @@ var FieldProgressBar = AbstractField.extend({
}
return this._super();
},
+ /**
+ * Updates the widget with value
+ *
+ * @param {Number} value
+ */
on_update: function (value) {
- if (!isNaN(value)) {
- if (this.edit_max_value) {
- try {
- this.max_value = this._parseValue(value);
- this._isValid = true;
- } catch (e) {
- this._isValid = false;
- }
- var changes = {};
- changes[this.nodeOptions.max_value] = this.max_value;
- this.trigger_up('field_changed', {
- dataPointID: this.dataPointID,
- changes: changes,
- });
- } else {
- this._setValue(value);
- }
+ if (this.edit_max_value) {
+ this.max_value = value;
+ this._isValid = true;
+ var changes = {};
+ changes[this.nodeOptions.max_value] = this.max_value;
+ this.trigger_up('field_changed', {
+ dataPointID: this.dataPointID,
+ changes: changes,
+ });
+ } else {
+ // _setValues accepts string and will parse it
+ var formattedValue = this._formatValue(value);
+ this._setValue(formattedValue);
}
},
on_change_input: function (e) {
@@ -2388,29 +2406,42 @@ var FieldProgressBar = AbstractField.extend({
if (e.type === 'change' && !$input.is(':focus')) {
return;
}
- if (isNaN($input.val())) {
- this.do_warn(_t("Wrong value entered!"), _t("Only Integer Value should be valid."));
- } else {
- if (e.type === 'input') {
- this._render_value($input.val());
- if (parseFloat($input.val()) === 0) {
+
+ var parsedValue;
+ try {
+ // Cover all numbers with parseFloat
+ parsedValue = field_utils.parse.float($input.val());
+ } catch (error) {
+ this.do_warn(_t("Wrong value entered!"), _t("Only Integer or Float Value should be valid."));
+ }
+
+ if (parsedValue !== undefined) {
+ if (e.type === 'input') { // ensure what has just been typed in the input is a number
+ // returns NaN if not a number
+ this._render_value(parsedValue);
+ if (parsedValue === 0) {
$input.select();
}
- } else {
+ } else { // Implicit type === 'blur': we commit the value
if (this.edit_max_value) {
- this.max_value = $(e.target).val();
- } else {
- this.value = $(e.target).val() || 0;
+ parsedValue = parsedValue || 100;
}
+
var $div = $('<div>', {class: 'o_progressbar_value'});
this.$('.o_progressbar_value').replaceWith($div);
this.write_mode = false;
+ this.on_update(parsedValue);
this._render_value();
- this.on_update(this.edit_max_value ? this.max_value : this.value);
}
}
},
+ /**
+ * Renders the value
+ *
+ * @private
+ * @param {Number} v
+ */
_render_value: function (v) {
var value = this.value;
var max_value = this.max_value;
diff --git a/addons/web/static/src/js/views/abstract_controller.js b/addons/web/static/src/js/views/abstract_controller.js
index 74e1a239790..68abdf9855a 100644
--- a/addons/web/static/src/js/views/abstract_controller.js
+++ b/addons/web/static/src/js/views/abstract_controller.js
@@ -19,6 +19,8 @@ var config = require('web.config');
var core = require('web.core');
var mvc = require('web.mvc');
+var session = require('web.session');
+
var QWeb = core.qweb;
var AbstractController = mvc.Controller.extend(ActionMixin, {
@@ -477,6 +479,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
this.dp.add(this._rpc({
model: data.model,
method: data.method,
+ context: session.user_context,
})).then(function (action) {
if (action !== undefined) {
self.do_action(action, options);
diff --git a/addons/web/static/src/js/views/control_panel/search/search_bar.js b/addons/web/static/src/js/views/control_panel/search/search_bar.js
index 6248489922c..9063b2aca50 100644
--- a/addons/web/static/src/js/views/control_panel/search/search_bar.js
+++ b/addons/web/static/src/js/views/control_panel/search/search_bar.js
@@ -9,6 +9,8 @@ var Widget = require('web.Widget');
var SearchBar = Widget.extend({
template: 'SearchView.SearchBar',
events: _.extend({}, Widget.prototype.events, {
+ 'compositionend .o_searchview_input': '_onCompositionendInput',
+ 'compositionstart .o_searchview_input': '_onCompositionstartInput',
'keydown': '_onKeydown',
}),
/**
@@ -34,6 +36,7 @@ var SearchBar = Widget.extend({
this.autoCompleteSources = [];
this.searchFacets = [];
+ this._isInputComposing = false;
},
/**
* @override
@@ -208,10 +211,27 @@ var SearchBar = Widget.extend({
}
},
/**
+ * @rivate
+ * @param {CompositionEvent} ev
+ */
+ _onCompositionendInput: function () {
+ this._isInputComposing = false;
+ },
+ /**
+ * @rivate
+ * @param {CompositionEvent} ev
+ */
+ _onCompositionstartInput: function () {
+ this._isInputComposing = true;
+ },
+ /**
* @private
* @param {KeyEvent} e
*/
_onKeydown: function (e) {
+ if (this._isInputComposing) {
+ return;
+ }
switch(e.which) {
case $.ui.keyCode.LEFT:
this._focusPreceding();
diff --git a/addons/web/static/src/js/views/control_panel/search/search_facet.js b/addons/web/static/src/js/views/control_panel/search/search_facet.js
index ca9a7ad6225..3f18597cccf 100644
--- a/addons/web/static/src/js/views/control_panel/search/search_facet.js
+++ b/addons/web/static/src/js/views/control_panel/search/search_facet.js
@@ -10,6 +10,8 @@ var SearchFacet = Widget.extend({
template: 'SearchView.SearchFacet',
events: _.extend({}, Widget.prototype.events, {
'click .o_facet_remove': '_onFacetRemove',
+ 'compositionend': '_onCompositionend',
+ 'compositionstart': '_onCompositionstart',
'keydown': '_onKeydown',
}),
/**
@@ -26,6 +28,7 @@ var SearchFacet = Widget.extend({
});
this.separator = this._getSeparator();
this.icon = this._getIcon();
+ this._isComposing = false;
},
//--------------------------------------------------------------------------
@@ -107,6 +110,20 @@ var SearchFacet = Widget.extend({
/**
* @private
+ * @param {CompositionEvent} ev
+ */
+ _onCompositionend: function (ev) {
+ this._isComposing = false;
+ },
+ /**
+ * @private
+ * @param {CompositionEvent} ev
+ */
+ _onCompositionstart: function (ev) {
+ this._isComposing = true;
+ },
+ /**
+ * @private
*/
_onFacetRemove: function () {
this.trigger_up('facet_removed', {group: this.facet});
@@ -116,6 +133,9 @@ var SearchFacet = Widget.extend({
* @param {KeyboardEvent} ev
*/
_onKeydown: function (ev) {
+ if (this._isComposing) {
+ return;
+ }
switch (ev.which) {
case $.ui.keyCode.BACKSPACE:
this.trigger_up('facet_removed', {group: this.facet});
diff --git a/addons/web/static/src/js/views/kanban/search_panel.js b/addons/web/static/src/js/views/kanban/search_panel.js
index 730c9b83ae2..788c8e5ed20 100644
--- a/addons/web/static/src/js/views/kanban/search_panel.js
+++ b/addons/web/static/src/js/views/kanban/search_panel.js
@@ -140,8 +140,9 @@ var SearchPanel = Widget.extend({
});
_.map(values, function (value) {
var value = category.values[value.id];
- if (value.parentId) {
- category.values[value.parentId].childrenIds.push(value.id);
+ var parentCategoryId = value.parentId;
+ if (parentCategoryId && parentCategoryId in category.values) {
+ category.values[parentCategoryId].childrenIds.push(value.id);
}
});
category.rootIds = _.filter(_.map(values, function (value) {
diff --git a/addons/web/static/src/js/widgets/auto_complete.js b/addons/web/static/src/js/widgets/auto_complete.js
index 1bb00be8d38..30c38690d17 100644
--- a/addons/web/static/src/js/widgets/auto_complete.js
+++ b/addons/web/static/src/js/widgets/auto_complete.js
@@ -29,16 +29,23 @@ return Widget.extend({
this.searching = true;
this.search_string = '';
this.current_search = null;
+ this._isInputComposing = false;
},
start: function () {
var self = this;
+ this.$input.on('compositionend', function (ev) {
+ self._isInputComposing = false;
+ });
+ this.$input.on('compositionstart', function (ev) {
+ self._isInputComposing = true;
+ });
this.$input.on('keyup', function (ev) {
- if (ev.which === $.ui.keyCode.RIGHT) {
+ if (ev.which === $.ui.keyCode.RIGHT && !self._isInputComposing) {
self.searching = true;
ev.preventDefault();
return;
}
- if (ev.which === $.ui.keyCode.ENTER) {
+ if (ev.which === $.ui.keyCode.ENTER && !self._isInputComposing) {
if (self.search_string.length) {
self.select_item(ev);
}
@@ -68,6 +75,9 @@ return Widget.extend({
}
});
this.$input.on('keydown', function (ev) {
+ if (self._isInputComposing) {
+ return;
+ }
switch (ev.which) {
case $.ui.keyCode.ENTER:
diff --git a/addons/web/static/src/scss/banner.scss b/addons/web/static/src/scss/banner.scss
index d7237203897..be011d70698 100644
--- a/addons/web/static/src/scss/banner.scss
+++ b/addons/web/static/src/scss/banner.scss
@@ -1,8 +1,9 @@
.o_has_banner {
.o_view_nocontent {
top: 30%;
- @media screen and (max-height: map-get($grid-breakpoints, 'md')) {
+ @include media-breakpoint-down(sm) {
position: relative;
+ margin: auto;
}
}
}
diff --git a/addons/web/static/src/scss/modal.scss b/addons/web/static/src/scss/modal.scss
index 52c3100df31..5b190240235 100644
--- a/addons/web/static/src/scss/modal.scss
+++ b/addons/web/static/src/scss/modal.scss
@@ -92,6 +92,11 @@
}
}
+body.modal-open {
+ // Allow vertical scrolling in modals on iOS
+ -webkit-overflow-scrolling: touch;
+}
+
// Temporary fix for modals which are not instantiated thanks to the Dialog
// JS classes (deprecated case) (see bootstrap_overridden.scss) + Frontend.
//
diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js
index a593505e9c2..c4869ce2e00 100644
--- a/addons/web/static/tests/fields/basic_fields_tests.js
+++ b/addons/web/static/tests/fields/basic_fields_tests.js
@@ -10,6 +10,7 @@ var KanbanView = require('web.KanbanView');
var ListView = require('web.ListView');
var session = require('web.session');
var testUtils = require('web.test_utils');
+var testUtilsDom = require('web.test_utils_dom');
var field_registry = require('web.field_registry');
var createView = testUtils.createView;
@@ -5510,7 +5511,7 @@ QUnit.module('basic_fields', {
QUnit.module('FieldProgressBar');
QUnit.test('Field ProgressBar: max_value should update', async function (assert) {
- assert.expect(2);
+ assert.expect(3);
this.data.partner.records = this.data.partner.records.slice(0,1);
this.data.partner.records[0].qux = 2;
@@ -5535,6 +5536,15 @@ QUnit.module('basic_fields', {
viewOptions: {
mode: 'edit',
},
+ mockRPC: function (route, args) {
+ if (args.method === 'write') {
+ assert.deepEqual(
+ args.args[1],
+ {int_field: 999, qux: 5, display_name: 'new name'},
+ 'New value of progress bar saved');
+ }
+ return this._super.apply(this, arguments);
+ }
});
assert.strictEqual(form.$('.o_progressbar_value').text(), '10 / 2',
@@ -5546,6 +5556,435 @@ QUnit.module('basic_fields', {
assert.strictEqual(form.$('.o_progressbar_value').text(), '999 / 5',
'The value of the progress bar should be correct after the update');
+ await testUtilsDom.click(form.$buttons.find('.o_form_button_save'));
+
+ form.destroy();
+ });
+
+ QUnit.test('Field ProgressBar: value should not update in readonly mode when sliding the bar', async function (assert) {
+ assert.expect(4);
+ this.data.partner.records[0].int_field = 99;
+
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="int_field" widget="progressbar" options="{\'editable\': true}" />' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ assert.step(route);
+ return this._super.apply(this, arguments);
+ }
+ });
+ var $view = $('#qunit-fixture').contents();
+ $view.prependTo('body'); // => select with click position
+
+ assert.strictEqual(form.$('.o_progressbar_value').text(), '99%',
+ 'Initial value should be correct')
+
+ var $progressBarEl = form.$('.o_progress');
+ var top = $progressBarEl.offset().top + 5;
+ var left = $progressBarEl.offset().left + 5;
+ try {
+ testUtils.triggerPositionalMouseEvent(left, top, "click");
+ } catch (e) {
+ form.destroy();
+ $view.remove();
+ throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.');
+ }
+ assert.strictEqual(form.$('.o_progressbar_value').text(), '99%',
+ 'New value should be different than initial after click');
+
+ assert.verifySteps(["/web/dataset/call_kw/partner/read"]);
+
+ form.destroy();
+ $view.remove();
+ });
+
+ QUnit.test('Field ProgressBar: value should not update in edit mode when sliding the bar', async function (assert) {
+ assert.expect(6);
+ this.data.partner.records[0].int_field = 99;
+
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="int_field" widget="progressbar" options="{\'editable\': true}" />' +
+ '</form>',
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ mockRPC: function (route, args) {
+ assert.step(route);
+ return this._super.apply(this, arguments);
+ }
+ });
+ var $view = $('#qunit-fixture').contents();
+ $view.prependTo('body'); // => select with click position
+
+ assert.ok(form.$('.o_form_view').hasClass('o_form_editable'), 'Form in edit mode');
+
+ assert.strictEqual(form.$('.o_progressbar_value').text(), '99%',
+ 'Initial value should be correct')
+
+ var $progressBarEl = form.$('.o_progress');
+ var top = $progressBarEl.offset().top + 5;
+ var left = $progressBarEl.offset().left + 5;
+ try {
+ testUtils.triggerPositionalMouseEvent(left, top, "click");
+ } catch (e) {
+ form.destroy();
+ $view.remove();
+ throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.');
+ }
+ assert.strictEqual(form.$('.o_progressbar_value.o_input').val(), "99",
+ 'Value of input is not changed');
+ await testUtilsDom.click(form.$buttons.find('.o_form_button_save'));
+
+ assert.strictEqual(form.$('.o_progressbar_value').text(), '99%',
+ 'New value should be different than initial after click');
+
+ assert.verifySteps(["/web/dataset/call_kw/partner/read"]);
+
+ form.destroy();
+ $view.remove();
+ });
+
+ QUnit.test('Field ProgressBar: value should update in edit mode when typing in input', async function (assert) {
+ assert.expect(5);
+ this.data.partner.records[0].int_field = 99;
+
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="int_field" widget="progressbar" options="{\'editable\': true}" />' +
+ '</form>',
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ mockRPC: function (route, args) {
+ if (args.method === 'write') {
+ assert.strictEqual(args.args[1].int_field, 69,
+ 'New value of progress bar saved');
+ }
+ return this._super.apply(this, arguments);
+ }
+ });
+
+ assert.ok(form.$('.o_form_view').hasClass('o_form_editable'), 'Form in edit mode');
+
+ assert.strictEqual(form.$('.o_progressbar_value').text(), '99%',
+ 'Initial value should be correct');
+
+ await testUtilsDom.click(form.$('.o_progress'));
+
+ var $valInput = form.$('.o_progressbar_value.o_input');
+ assert.strictEqual($valInput.val(), '99', 'Initial value in input is correct');
+
+ await testUtils.fields.editAndTrigger($valInput, '69', ['input', 'blur']);
+
+ await testUtilsDom.click(form.$buttons.find('.o_form_button_save'));
+
+ assert.strictEqual(form.$('.o_progressbar_value').text(), '69%',
+ 'New value should be different than initial after click');
+
+ form.destroy();
+ });
+
+ QUnit.test('Field ProgressBar: value should update in edit mode when typing in input with field max value', async function (assert) {
+ assert.expect(5);
+ this.data.partner.records[0].int_field = 99;
+
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="qux" invisible="1" />' +
+ '<field name="int_field" widget="progressbar" options="{\'editable\': true, \'max_value\': \'qux\'}" />' +
+ '</form>',
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ mockRPC: function (route, args) {
+ if (args.method === 'write') {
+ assert.strictEqual(args.args[1].int_field, 69,
+ 'New value of progress bar saved');
+ }
+ return this._super.apply(this, arguments);
+ }
+ });
+
+ assert.ok(form.$('.o_form_view').hasClass('o_form_editable'), 'Form in edit mode');
+
+ assert.strictEqual(form.$('.o_progressbar_value').text(), '99 / 0',
+ 'Initial value should be correct');
+
+ await testUtilsDom.click(form.$('.o_progress'));
+
+ var $valInput = form.$('.o_progressbar_value.o_input');
+ assert.strictEqual($valInput.val(), '99', 'Initial value in input is correct');
+
+ await testUtils.fields.editAndTrigger($valInput, '69', ['input', 'blur']);
+
+ await testUtilsDom.click(form.$buttons.find('.o_form_button_save'));
+
+ assert.strictEqual(form.$('.o_progressbar_value').text(), '69 / 0',
+ 'New value should be different than initial after click');
+
+ form.destroy();
+ });
+
+ QUnit.test('Field ProgressBar: max value should update in edit mode when typing in input with field max value', async function (assert) {
+ assert.expect(5);
+ this.data.partner.records[0].int_field = 99;
+
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="qux" invisible="1" />' +
+ '<field name="int_field" widget="progressbar" options="{\'editable\': true, \'max_value\': \'qux\', \'edit_max_value\': true}" />' +
+ '</form>',
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ mockRPC: function (route, args) {
+ if (args.method === 'write') {
+ assert.strictEqual(args.args[1].qux, 69,
+ 'New value of progress bar saved');
+ }
+ return this._super.apply(this, arguments);
+ }
+ });
+
+ assert.ok(form.$('.o_form_view').hasClass('o_form_editable'), 'Form in edit mode');
+
+ assert.strictEqual(form.$('.o_progressbar_value').text(), '99 / 0',
+ 'Initial value should be correct');
+
+ await testUtilsDom.click(form.$('.o_progress'));
+
+ var $valInput = form.$('.o_progressbar_value.o_input');
+ assert.strictEqual($valInput.val(), "0.44444", 'Initial value in input is correct');
+
+ await testUtils.fields.editAndTrigger($valInput, '69', ['input', 'blur']);
+
+ await testUtilsDom.click(form.$buttons.find('.o_form_button_save'));
+
+ assert.strictEqual(form.$('.o_progressbar_value').text(), '99 / 69',
+ 'New value should be different than initial after click');
+
+ form.destroy();
+ });
+
+ QUnit.test('Field ProgressBar: Standard readonly mode is readonly', async function (assert) {
+ assert.expect(5);
+ this.data.partner.records[0].int_field = 99;
+
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="qux" invisible="1" />' +
+ '<field name="int_field" widget="progressbar" options="{\'editable\': true, \'max_value\': \'qux\', \'edit_max_value\': true}" />' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ assert.step(route);
+ return this._super.apply(this, arguments);
+ }
+ });
+
+ assert.ok(form.$('.o_form_view').hasClass('o_form_readonly'), 'Form in readonly mode');
+
+ assert.strictEqual(form.$('.o_progressbar_value').text(), '99 / 0',
+ 'Initial value should be correct');
+
+ await testUtilsDom.click(form.$('.o_progress'));
+
+ assert.containsNone(form, '.o_progressbar_value.o_input', 'no input in readonly mode');
+
+ assert.verifySteps(["/web/dataset/call_kw/partner/read"]);
+
+ form.destroy();
+ });
+
+ QUnit.test('Field ProgressBar: max value should update in readonly mode with right parameter when typing in input with field max value', async function (assert) {
+ assert.expect(5);
+ this.data.partner.records[0].int_field = 99;
+
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="qux" invisible="1" />' +
+ '<field name="int_field" widget="progressbar" options="{\'editable\': true, \'max_value\': \'qux\', \'edit_max_value\': true, \'editable_readonly\': true}" />' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (args.method === 'write') {
+ assert.strictEqual(args.args[1].qux, 69,
+ 'New value of progress bar saved');
+ }
+ return this._super.apply(this, arguments);
+ }
+ });
+
+ assert.ok(form.$('.o_form_view').hasClass('o_form_readonly'), 'Form in readonly mode');
+
+ assert.strictEqual(form.$('.o_progressbar_value').text(), '99 / 0',
+ 'Initial value should be correct');
+
+ await testUtilsDom.click(form.$('.o_progress'));
+
+ var $valInput = form.$('.o_progressbar_value.o_input');
+ assert.strictEqual($valInput.val(), "0.44444", 'Initial value in input is correct');
+
+ await testUtils.fields.editAndTrigger($valInput, '69', ['input', 'blur']);
+
+ assert.strictEqual(form.$('.o_progressbar_value').text(), '99 / 69',
+ 'New value should be different than initial after changing it');
+
+ form.destroy();
+ });
+
+ QUnit.test('Field ProgressBar: value should update in readonly mode with right parameter when typing in input with field value', async function (assert) {
+ assert.expect(5);
+ this.data.partner.records[0].int_field = 99;
+
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="int_field" widget="progressbar" options="{\'editable\': true, \'editable_readonly\': true}" />' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (args.method === 'write') {
+ assert.strictEqual(args.args[1].int_field, 69,
+ 'New value of progress bar saved');
+ }
+ return this._super.apply(this, arguments);
+ }
+ });
+
+ assert.ok(form.$('.o_form_view').hasClass('o_form_readonly'), 'Form in readonly mode');
+
+ assert.strictEqual(form.$('.o_progressbar_value').text(), '99%',
+ 'Initial value should be correct');
+
+ await testUtilsDom.click(form.$('.o_progress'));
+
+ var $valInput = form.$('.o_progressbar_value.o_input');
+ assert.strictEqual($valInput.val(), "99", 'Initial value in input is correct');
+
+ await testUtils.fields.editAndTrigger($valInput, '69.6', ['input', 'blur']);
+
+ assert.strictEqual(form.$('.o_progressbar_value').text(), '69%',
+ 'New value should be different than initial after changing it');
+
+ form.destroy();
+ });
+
+ QUnit.test('Field ProgressBar: write float instead of int works, in locale', async function (assert) {
+ assert.expect(5);
+ this.data.partner.records[0].int_field = 99;
+
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="int_field" widget="progressbar" options="{\'editable\': true}" />' +
+ '</form>',
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ translateParameters: {
+ thousands_sep: "#",
+ decimal_point: ":",
+ },
+ mockRPC: function (route, args) {
+ if (args.method === 'write') {
+ assert.strictEqual(args.args[1].int_field, 1037,
+ 'New value of progress bar saved');
+ }
+ return this._super.apply(this, arguments);
+ }
+ });
+
+ assert.ok(form.$('.o_form_view').hasClass('o_form_editable'), 'Form in edit mode');
+
+ assert.strictEqual(form.$('.o_progressbar_value').text(), '99%',
+ 'Initial value should be correct');
+
+ await testUtilsDom.click(form.$('.o_progress'));
+
+ var $valInput = form.$('.o_progressbar_value.o_input');
+ assert.strictEqual($valInput.val(), '99', 'Initial value in input is correct');
+
+ await testUtils.fields.editAndTrigger($valInput, '1#037:9', ['input', 'blur']);
+
+ await testUtilsDom.click(form.$buttons.find('.o_form_button_save'));
+
+ assert.strictEqual(form.$('.o_progressbar_value').text(), '1k%',
+ 'New value should be different than initial after click');
+
+ form.destroy();
+ });
+
+ QUnit.test('Field ProgressBar: write gibbrish instead of int throws warning', async function (assert) {
+ assert.expect(5);
+ this.data.partner.records[0].int_field = 99;
+
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="int_field" widget="progressbar" options="{\'editable\': true}" />' +
+ '</form>',
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ interceptsPropagate: {
+ call_service: function (ev) {
+ if (ev.data.service === 'notification') {
+ assert.strictEqual(ev.data.method, 'notify');
+ assert.strictEqual(ev.data.args[0].title, 'Wrong value entered!');
+ }
+ }
+ },
+ });
+
+ assert.ok(form.$('.o_form_view').hasClass('o_form_editable'), 'Form in edit mode');
+
+ assert.strictEqual(form.$('.o_progressbar_value').text(), '99%',
+ 'Initial value should be correct');
+
+ await testUtilsDom.click(form.$('.o_progress'));
+
+ var $valInput = form.$('.o_progressbar_value.o_input');
+ assert.strictEqual($valInput.val(), '99', 'Initial value in input is correct');
+
+ await testUtils.fields.editAndTrigger($valInput, 'trente sept virgule neuf', ['input']);
+
form.destroy();
});
});
diff --git a/addons/web/static/tests/views/search_panel_tests.js b/addons/web/static/tests/views/search_panel_tests.js
index 4b3517e4cc5..1490db60334 100644
--- a/addons/web/static/tests/views/search_panel_tests.js
+++ b/addons/web/static/tests/views/search_panel_tests.js
@@ -470,6 +470,61 @@ QUnit.module('Views', {
kanban.destroy();
});
+ QUnit.test('category has been archived', async function (assert) {
+ assert.expect(2);
+
+ this.data.company.fields.active = {type: 'boolean', string: 'Archived'};
+ this.data.company.records = [
+ {
+ name: 'Company 5',
+ id: 5,
+ active: true,
+ }, {
+ name: 'child of 5 archived',
+ parent_id: 5,
+ id: 666,
+ active: false,
+ }, {
+ name: 'child of 666',
+ parent_id: 666,
+ id: 777,
+ active: true,
+ }
+ ];
+
+ var kanban = await createView({
+ View: KanbanView,
+ model: 'partner',
+ data: this.data,
+ arch: '<kanban>' +
+ '<templates><t t-name="kanban-box">' +
+ '<div>' +
+ '<field name="foo"/>' +
+ '</div>' +
+ '</t></templates>' +
+ '<searchpanel>' +
+ '<field name="company_id"/>' +
+ '</searchpanel>' +
+ '</kanban>',
+ mockRPC: async function (route, args) {
+ if (route === '/web/dataset/call_kw/partner/search_panel_select_range') {
+ var results = await this._super.apply(this, arguments);
+ results.values = results.values.filter(rec => rec.active !== false);
+ return Promise.resolve(results);
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ assert.containsN(kanban, '.o_search_panel_category_value', 2,
+ 'The number of categories shold be 2: All and Company 5');
+
+ assert.containsNone(kanban, '.o_toggle_fold',
+ 'None of the categories should have children');
+
+ kanban.destroy();
+ });
+
QUnit.test('use two categories to refine search', async function (assert) {
assert.expect(14);