diff options
| author | Christophe Simonis <chs@odoo.com> | 2019-03-13 11:45:01 +0000 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2019-03-13 14:24:51 +0000 |
| commit | 44515bc7beae3b4c46b5bc2959c320695b51439d (patch) | |
| tree | 5f56871242f7b5bc8a323c51f2749411e555159e /addons/web/static | |
| parent | bf69975b54164a21bfe00b2359d972329516a34e (diff) | |
| parent | c9f832d9f032b6af51807b387b325b5c2b8aad00 (diff) | |
[MERGE] forward port branch saas-12.2 up to c9f832d9f03
closes odoo/odoo#31790
Signed-off-by: Christophe Simonis <chs@odoo.com>
Diffstat (limited to 'addons/web/static')
22 files changed, 410 insertions, 79 deletions
diff --git a/addons/web/static/src/js/chrome/action_manager_act_window.js b/addons/web/static/src/js/chrome/action_manager_act_window.js index d00b3a49eb2..8d13f4d9679 100644 --- a/addons/web/static/src/js/chrome/action_manager_act_window.js +++ b/addons/web/static/src/js/chrome/action_manager_act_window.js @@ -535,6 +535,9 @@ ActionManager.include({ return newController(controller.jsID); } else { return Promise.resolve(controller.widget.willRestore()).then(function () { + viewOptions = _.extend({}, viewOptions, { + breadcrumbs: self._getBreadcrumbs(self.controllerStack.slice(0, index)), + }); return controller.widget.reload(viewOptions).then(function () { return controller; }); diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index a87ff3e4436..7439c36037f 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -1330,7 +1330,9 @@ var CopyClipboard = { */ destroy: function () { this._super.apply(this, arguments); - this.clipboard.destroy(); + if (this.clipboard) { + this.clipboard.destroy(); + } }, //-------------------------------------------------------------------------- @@ -1345,8 +1347,8 @@ var CopyClipboard = { var $clipboardBtn = this.$('.o_clipboard_button'); $clipboardBtn.tooltip({title: _t('Copied !'), trigger: 'manual', placement: 'right'}); this.clipboard = new ClipboardJS($clipboardBtn[0], { - text: function (_) { - return self.value.trim(); + text: function () { + return self.value.trim(); }, // Container added because of Bootstrap modal that give the focus to another element. // We need to give to correct focus to ClipboardJS (see in ClipboardJS doc) @@ -1362,40 +1364,31 @@ var CopyClipboard = { }); }); }, -}; - -var TextCopyClipboard = FieldText.extend(CopyClipboard, { - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - /** * @override */ - _render: function() { + _render: function () { this._super.apply(this, arguments); this.$el.addClass('o_field_copy'); - this.$el.append($(qweb.render('CopyClipboardText'))); - this._initClipboard(); - } -}); - -var CharCopyClipboard = FieldChar.extend(CopyClipboard, { - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - + }, /** * @override */ - _render: function() { + _renderReadonly: function () { this._super.apply(this, arguments); - this.$el.addClass('o_field_copy'); - this.$el.append($(qweb.render('CopyClipboardChar'))); - this._initClipboard(); + if (this.value) { + this.$el.append($(qweb.render(this.clipboardTemplate))); + this._initClipboard(); + } } +}; + +var TextCopyClipboard = FieldText.extend(CopyClipboard, { + clipboardTemplate: 'CopyClipboardText', +}); + +var CharCopyClipboard = FieldChar.extend(CopyClipboard, { + clipboardTemplate: 'CopyClipboardChar', }); var AbstractFieldBinary = AbstractField.extend({ diff --git a/addons/web/static/src/js/fields/field_registry.js b/addons/web/static/src/js/fields/field_registry.js index 0e0ba3dc117..cd4c92087eb 100644 --- a/addons/web/static/src/js/fields/field_registry.js +++ b/addons/web/static/src/js/fields/field_registry.js @@ -44,8 +44,9 @@ registry .add('priority', basic_fields.PriorityWidget) .add('attachment_image', basic_fields.AttachmentImage) .add('label_selection', basic_fields.LabelSelection) + .add('kanban_label_selection', basic_fields.LabelSelection) // deprecated, use label_selection .add('state_selection', basic_fields.StateSelectionWidget) - .add('kanban_state_selection', basic_fields.StateSelectionWidget) + .add('kanban_state_selection', basic_fields.StateSelectionWidget) // deprecated, use state_selection .add('boolean_favorite', basic_fields.FavoriteWidget) .add('boolean_button', basic_fields.FieldBooleanButton) .add('boolean_toggle', basic_fields.BooleanToggle) diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index 469cedb8ff1..15b7975aa42 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -242,7 +242,7 @@ var FieldMany2One = AbstractField.extend({ _bindAutoComplete: function () { var self = this; // avoid ignoring autocomplete="off" by obfuscating placeholder, see #30439 - if (this.$input.attr('placeholder')) { + if ($.browser.chrome && this.$input.attr('placeholder')) { this.$input.attr('placeholder', function (index, val) { return val.split('').join('\ufeff'); }); @@ -1901,6 +1901,7 @@ var FieldMany2ManyTags = AbstractField.extend({ } this.colorField = this.nodeOptions.color_field; + this.hasDropdown = false; }, //-------------------------------------------------------------------------- @@ -1969,6 +1970,7 @@ var FieldMany2ManyTags = AbstractField.extend({ return { colorField: this.colorField, elements: elements, + hasDropdown: this.hasDropdown, readonly: this.mode === "readonly", }; }, @@ -2080,6 +2082,14 @@ var FormFieldMany2ManyTags = FieldMany2ManyTags.extend({ 'mousedown .o_colorpicker a': '_onUpdateColor', 'mousedown .o_colorpicker .o_hide_in_kanban': '_onUpdateColor', }), + /** + * @override + */ + init: function () { + this._super.apply(this, arguments); + + this.hasDropdown = !!this.colorField; + }, //-------------------------------------------------------------------------- // Handlers diff --git a/addons/web/static/src/js/views/abstract_controller.js b/addons/web/static/src/js/views/abstract_controller.js index 0c251874480..965ce404a64 100644 --- a/addons/web/static/src/js/views/abstract_controller.js +++ b/addons/web/static/src/js/views/abstract_controller.js @@ -254,7 +254,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { return; } self.renderer.setLocalState(localState); - return self._update(state); + return self._update(state, params); }); }); }, @@ -415,12 +415,15 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { * * @private * @param {Object} state the state given by the model + * @param {Object} [params] + * @param {Object[]} [params.breadcrumbs] * @returns {Promise} */ - _update: function () { + _update: function (state, params) { // AAB: update the control panel -> this will be moved elsewhere at some point var cpContent = _.extend({}, this.controlPanelElements); this.updateControlPanel({ + breadcrumbs: params && params.breadcrumbs, cp_content: cpContent, title: this.getTitle(), }); diff --git a/addons/web/static/src/js/views/control_panel/control_panel_controller.js b/addons/web/static/src/js/views/control_panel/control_panel_controller.js index 913c226ba85..dc8fce9f5d1 100644 --- a/addons/web/static/src/js/views/control_panel/control_panel_controller.js +++ b/addons/web/static/src/js/views/control_panel/control_panel_controller.js @@ -17,6 +17,7 @@ var ControlPanelController = mvc.Controller.extend({ activate_time_range: '_onActivateTimeRange', autocompletion_filter: '_onAutoCompletionFilter', reload: '_onReload', + reset: '_onReset', }, /** @@ -244,6 +245,15 @@ var ControlPanelController = mvc.Controller.extend({ ev.stopPropagation(); this.trigger_up('search', this.model.getQuery()); }, + /** + * @private + * @param {OdooEvent} ev + */ + _onReset: function (ev) { + ev.stopPropagation(); + var state = this.model.get(); + this.renderer.updateState(state); + }, }); return ControlPanelController; 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 4c2e71a378f..6248489922c 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 @@ -187,10 +187,16 @@ var SearchBar = Widget.extend({ */ _onAutoCompleteSelected: function (e, ui) { e.preventDefault(); - var filter = ui.item.facet.filter; + var facet = ui.item.facet; + if (!facet) { + // this happens when selecting "(no result)" item + this.trigger_up('reset'); + return; + } + var filter = facet.filter; if (filter.type === 'field') { var values = filter.autoCompleteValues; - values.push(ui.item.facet.values[0]); + values.push(facet.values[0]); this.trigger_up('autocompletion_filter', { filterId: filter.id, autoCompleteValues: values, diff --git a/addons/web/static/src/js/views/kanban/kanban_renderer.js b/addons/web/static/src/js/views/kanban/kanban_renderer.js index eda15f706d3..c69632129cd 100644 --- a/addons/web/static/src/js/views/kanban/kanban_renderer.js +++ b/addons/web/static/src/js/views/kanban/kanban_renderer.js @@ -249,7 +249,6 @@ var KanbanRenderer = BasicRenderer.extend({ */ updateState: function (state) { this._setState(state); - this._toggleNoContentHelper(); return this._super.apply(this, arguments); }, diff --git a/addons/web/static/src/js/views/list/list_editable_renderer.js b/addons/web/static/src/js/views/list/list_editable_renderer.js index 4619cb86e66..cb652cb968b 100644 --- a/addons/web/static/src/js/views/list/list_editable_renderer.js +++ b/addons/web/static/src/js/views/list/list_editable_renderer.js @@ -285,6 +285,8 @@ ListRenderer.include({ this.currentRow = null; } + // destroy widgets first + this._destroyFieldWidgets(recordID); // remove the row var $row = this.$('.o_data_row:nth(' + rowIndex + ')'); if (this.state.count >= 4) { @@ -292,8 +294,6 @@ ListRenderer.include({ } else { $row.replaceWith(this._renderEmptyRow()); } - - this._destroyFieldWidgets(recordID); }, /** * Updates the already rendered row associated to the given recordID so that 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 a4a2bae8fb0..989eaa448a9 100644 --- a/addons/web/static/src/js/views/pivot/pivot_model.js +++ b/addons/web/static/src/js/views/pivot/pivot_model.js @@ -992,7 +992,6 @@ var PivotModel = AbstractModel.extend({ var groupBys; var m; - for (var i = 0; i < rowGroupBys.length + 1; i++) { for (var j = 0; j < colGroupBys.length + 1; j++) { for (var k = 0; k < data[index].length; k++) { diff --git a/addons/web/static/src/js/widgets/auto_complete.js b/addons/web/static/src/js/widgets/auto_complete.js index 6dd78f8f730..4da54cf8b5c 100644 --- a/addons/web/static/src/js/widgets/auto_complete.js +++ b/addons/web/static/src/js/widgets/auto_complete.js @@ -201,10 +201,8 @@ return Widget.extend({ this.current_result = $li.data('result'); }, select_item: function (ev) { - if (this.current_result.facet) { - this.select(ev, {item: {facet: this.current_result.facet}}); - this.close(); - } + this.select(ev, {item: {facet: this.current_result.facet}}); + this.close(); }, show: function () { this.$el.show(); diff --git a/addons/web/static/src/scss/fields.scss b/addons/web/static/src/scss/fields.scss index 27d0d13770e..55559546e74 100644 --- a/addons/web/static/src/scss/fields.scss +++ b/addons/web/static/src/scss/fields.scss @@ -178,7 +178,7 @@ } } } - } + } .o_tag_error { box-shadow: inset 0 0 0 2px #FF0000; @@ -443,7 +443,10 @@ } } } +} +span.o_field_copy:empty { + border: none; } button.o_field_float_toggle { diff --git a/addons/web/static/src/scss/form_view.scss b/addons/web/static/src/scss/form_view.scss index 0645e00544e..812e6cbd6cc 100644 --- a/addons/web/static/src/scss/form_view.scss +++ b/addons/web/static/src/scss/form_view.scss @@ -81,16 +81,10 @@ .o_form_uri { display: inline-block; + color: $link-color; - color: $o-main-text-color; - &:first-line { - color: $link-color; - } &:hover { - color: darken($o-main-text-color, 1%); // hack required on chrome - &:first-line { - color: darken($link-color, 15%); - } + color: darken($link-color, 15%); } } diff --git a/addons/web/static/src/scss/list_view.scss b/addons/web/static/src/scss/list_view.scss index 85b9b9f6f07..55f5b755df1 100644 --- a/addons/web/static/src/scss/list_view.scss +++ b/addons/web/static/src/scss/list_view.scss @@ -137,6 +137,7 @@ white-space: pre-wrap; } + .o_data_cell.o_text_overflow, .o_data_cell.o_invisible_modifier { // Override display: none to keep table cells aligned. Note: cannot use // 'initial' as it will force them to 'inline', not 'table-cell'. diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 8126fcf11c4..7a6bca6aa7a 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -846,11 +846,11 @@ <t t-foreach="elements" t-as="el"> <t t-set="color" t-value="el[colorField] || 0"/> <t t-set="colornames" t-value="['No color', 'Red', 'Orange', 'Yellow', 'Light blue', 'Dark purple', 'Salmon pink', 'Medium blue', 'Dark blue', 'Fushia', 'Green', 'Purple']"/> - <div t-attf-class="badge badge-pill dropdown o_tag_color_#{color}" t-att-data-color="color" t-att-data-index="el_index" t-att-data-id="el.id" t-attf-title="Tag color: #{colornames[color]}"> + <div t-attf-class="badge badge-pill #{hasDropdown ? 'dropdown' : ''} o_tag_color_#{color}" t-att-data-color="color" t-att-data-index="el_index" t-att-data-id="el.id" t-attf-title="Tag color: #{colornames[color]}"> <t t-set="_badge_text"> <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"> + <t t-if="hasDropdown"> <a role="button" href="#" class="dropdown-toggle o-no-caret" data-toggle="dropdown" aria-expanded="false"> <t t-raw="_badge_text"/> </a> diff --git a/addons/web/static/tests/chrome/action_manager_tests.js b/addons/web/static/tests/chrome/action_manager_tests.js index bef2057a076..45ac864aa8e 100644 --- a/addons/web/static/tests/chrome/action_manager_tests.js +++ b/addons/web/static/tests/chrome/action_manager_tests.js @@ -2262,7 +2262,7 @@ QUnit.module('ActionManager', { }); QUnit.test('breadcrumbs are updated when switching between views', async function (assert) { - assert.expect(10); + assert.expect(15); var actionManager = await createActionManager({ actions: this.actions, @@ -2283,6 +2283,21 @@ QUnit.module('ActionManager', { assert.strictEqual($('.o_control_panel .breadcrumb-item').text(), 'Partners', "breadcrumbs should still display the display_name of the action"); + // open a record in form view + await testUtils.dom.click(actionManager.$('.o_kanban_view .o_kanban_record:first')); + await testUtils.nextTick(); + assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 2, + "there should be two controllers in the breadcrumbs"); + assert.strictEqual($('.o_control_panel .breadcrumb-item:last').text(), 'First record', + "breadcrumbs should contain the display_name of the opened record"); + + // go back to kanban view using the breadcrumbs + await testUtils.dom.click($('.o_control_panel .breadcrumb a')); + assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 1, + "there should be one controller in the breadcrumbs"); + assert.strictEqual($('.o_control_panel .breadcrumb-item').text(), 'Partners', + "breadcrumbs should display the display_name of the action"); + // switch back to list view await testUtils.dom.click($('.o_control_panel .o_cp_switch_list')); assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 1, @@ -2299,6 +2314,8 @@ QUnit.module('ActionManager', { // go back to list view using the breadcrumbs await testUtils.dom.click($('.o_control_panel .breadcrumb a')); + assert.containsOnce(actionManager, '.o_list_view', + "should be back on list view"); assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 1, "there should be one controller in the breadcrumbs"); assert.strictEqual($('.o_control_panel .breadcrumb-item').text(), 'Partners', diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js index 223e1e5764c..a7d3582b5ca 100644 --- a/addons/web/static/tests/fields/basic_fields_tests.js +++ b/addons/web/static/tests/fields/basic_fields_tests.js @@ -1409,45 +1409,79 @@ QUnit.module('basic_fields', { list.destroy(); }); - QUnit.module('copy_clipboard', { - beforeEach: function () { - this.data = { - partner: { - fields: { - script_external: {string: "Script External", type: "text"}, - web_page: {string: "Web page link", type: "char"} - }, - records: [{ - id: 1, - script_external:'Random Text', - web_page: 'web page links' - },], - }, - }; - } - }); + QUnit.module('CopyClipboard'); QUnit.test('Char & Text Fields: Copy to clipboard button', async function (assert) { assert.expect(2); - var done = assert.async(); - testUtils.createView({ + + var form = await createView({ View: FormView, model: 'partner', data: this.data, arch: '<form string="Partners">' + '<sheet>' + '<div>' + - '<field name="script_external" widget="CopyClipboardText"/>' + - '<field name="web_page" widget="CopyClipboardChar"/>' + + '<field name="txt" widget="CopyClipboardText"/>' + + '<field name="foo" widget="CopyClipboardChar"/>' + '</div>' + '</sheet>' + '</form>', - }).then(function (form) { - assert.containsOnce(form, '.o_clipboard_button.o_btn_text_copy',"Should have copy button on text type field"); - assert.containsOnce(form, '.o_clipboard_button.o_btn_char_copy',"Should have copy button on char type field"); - form.destroy(); - done(); + res_id: 1, + }); + + assert.containsOnce(form, '.o_clipboard_button.o_btn_text_copy',"Should have copy button on text type field"); + assert.containsOnce(form, '.o_clipboard_button.o_btn_char_copy',"Should have copy button on char type field"); + + form.destroy(); + }); + + QUnit.test('CopyClipboard widget on unset field', async function (assert) { + assert.expect(1); + + this.data.partner.records[0].foo = false; + + var form = await createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form>' + + '<sheet>' + + '<group>' + + '<field name="foo" widget="CopyClipboardChar" />' + + '</group>' + + '</sheet>' + + '</form>', + res_id: 1, + }); + + assert.containsNone(form, '.o_field_copy[name="foo"] .o_clipboard_button', + "foo (unset) should not contain a button"); + + form.destroy(); + }); + + QUnit.test('CopyClipboard widget on readonly unset fields in create mode', async function (assert) { + assert.expect(1); + + this.data.partner.fields.display_name.readonly = true; + + var form = await createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form>' + + '<sheet>' + + '<group>' + + '<field name="display_name" widget="CopyClipboardChar" />' + + '</group>' + + '</sheet>' + + '</form>', }); + + assert.containsNone(form, '.o_field_copy[name="display_name"] .o_clipboard_button', + "the readonly unset field should not contain a button"); + + form.destroy(); }); QUnit.module('FieldText'); diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index af64afbb9e4..e8d89aaf00d 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -1265,6 +1265,30 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('fieldmany2many tags in tree view', async function (assert) { + assert.expect(3); + + this.data.partner.records[0].timmy = [12, 14]; + var list = await createView({ + View: ListView, + model: 'partner', + data: this.data, + arch: '<tree string="Partners">' + + '<field name="timmy" widget="many2many_tags" options="{\'color_field\': \'color\'}"/>' + + '</tree>', + }); + assert.containsN(list, '.o_field_many2manytags .badge', 2, "there should be 2 tags"); + assert.containsNone(list, '.badge.dropdown-toggle', "the tags should not be dropdowns"); + + testUtils.intercept(list, 'switch_view', function (event) { + assert.strictEqual(event.data.view_type, "form", "should switch to form view"); + }); + // click on the tag: should do nothing and open the form view + testUtils.dom.click(list.$('.o_field_many2manytags .badge:first')); + + list.destroy(); + }); + QUnit.test('fieldmany2many tags view a domain', async function (assert) { assert.expect(7); diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js index 956578f2bf8..fbe4acff674 100644 --- a/addons/web/static/tests/views/kanban_tests.js +++ b/addons/web/static/tests/views/kanban_tests.js @@ -5341,6 +5341,119 @@ QUnit.module('Views', { kanbanController.destroy(); delete fieldRegistry.map.asyncWidget; }); + QUnit.test('asynchronous rendering of a field widget with display attr', async function (assert) { + assert.expect(3); + + var fooFieldDef = makeTestPromise(); + var FieldChar = fieldRegistry.get('char'); + fieldRegistry.add('asyncwidget', FieldChar.extend({ + willStart: function () { + return fooFieldDef; + }, + start: function () { + this.$el.html('LOADED'); + }, + })); + + var kanbanController; + testUtils.createAsyncView({ + View: KanbanView, + model: 'partner', + data: this.data, + arch: '<kanban class="o_kanban_test"><templates><t t-name="kanban-box">' + + '<div><field name="foo" display="right" widget="asyncwidget"/></div>' + + '</t></templates></kanban>', + }).then(function (kanban) { + kanbanController = kanban; + }); + + assert.containsNone(document.body, '.o_kanban_record'); + + fooFieldDef.resolve(); + await nextTick(); + assert.strictEqual(kanbanController.$('.o_kanban_record').text(), + "LOADEDLOADEDLOADEDLOADED"); + assert.hasClass(kanbanController.$('.o_kanban_record:first .o_field_char'), 'float-right'); + + kanbanController.destroy(); + delete fieldRegistry.map.asyncWidget; + }); + + QUnit.test('asynchronous rendering of a widget', async function (assert) { + assert.expect(2); + + var widgetDef = makeTestPromise(); + widgetRegistry.add('asyncwidget', Widget.extend({ + willStart: function () { + return widgetDef; + }, + start: function () { + this.$el.html('LOADED'); + }, + })); + + var kanbanController; + testUtils.createAsyncView({ + View: KanbanView, + model: 'partner', + data: this.data, + arch: '<kanban class="o_kanban_test"><templates><t t-name="kanban-box">' + + '<div><widget name="asyncwidget"/></div>' + + '</t></templates></kanban>', + }).then(function (kanban) { + kanbanController = kanban; + }); + + assert.containsNone(document.body, '.o_kanban_record'); + + widgetDef.resolve(); + await nextTick(); + assert.strictEqual(kanbanController.$('.o_kanban_record .o_widget').text(), + "LOADEDLOADEDLOADEDLOADED"); + + kanbanController.destroy(); + delete widgetRegistry.map.asyncWidget; + }); + + QUnit.test('update kanban with asynchronous field widget', async function (assert) { + assert.expect(3); + + var fooFieldDef = makeTestPromise(); + var FieldChar = fieldRegistry.get('char'); + fieldRegistry.add('asyncwidget', FieldChar.extend({ + willStart: function () { + return fooFieldDef; + }, + start: function () { + this.$el.html('LOADED'); + }, + })); + + var kanban = await testUtils.createView({ + View: KanbanView, + model: 'partner', + data: this.data, + arch: '<kanban class="o_kanban_test"><templates><t t-name="kanban-box">' + + '<div><field name="foo" widget="asyncwidget"/></div>' + + '</t></templates></kanban>', + domain: [['id', '=', '0']], // no record matches this domain + }); + + assert.containsNone(kanban, '.o_kanban_record:not(.o_kanban_ghost)'); + + kanban.update({domain: []}); // this rendering will be async + + assert.containsNone(kanban, '.o_kanban_record:not(.o_kanban_ghost)'); + + fooFieldDef.resolve(); + await nextTick(); + + assert.strictEqual(kanban.$('.o_kanban_record').text(), + "LOADEDLOADEDLOADEDLOADED"); + + kanban.destroy(); + delete widgetRegistry.map.asyncWidget; + }); }); }); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index 3b9e5836cb5..ac34dd95028 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -275,6 +275,52 @@ QUnit.module('Views', { list.destroy(); }); + QUnit.test('editable list datetimepicker destroy widget', async function (assert) { + assert.expect(7); + var eventPromise = testUtils.makeTestPromise(); + + var list = await createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '<tree editable="top">' + + '<field name="date"/>' + + '</tree>', + }); + list.$el.on({ + 'show.datetimepicker': async function () { + assert.equal($('.bootstrap-datetimepicker-widget').length, 1, + 'The datetimepicker is open'); + + assert.equal(list.$('.o_data_row').length, 5, + 'There should be 5 rows'); + + assert.equal(list.$('.o_selected_row').length, 1, + 'One row in edit mode'); + + await testUtils.fields.triggerKeydown(list.$('.o_datepicker_input'), 'escape'); + + assert.equal(list.$('.o_data_row').length, 4, + 'There should be 4 rows'); + + assert.equal(list.$('.o_selected_row').length, 0, + 'No row should be in edit mode'); + + eventPromise.resolve(); + } + }); + assert.equal(list.$('.o_data_row').length, 4, + 'There should be 4 rows'); + + assert.equal(list.$('.o_selected_row').length, 0, + 'No row should be in edit mode'); + + await testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + + await eventPromise; + list.destroy(); + }); + QUnit.test('at least 4 rows are rendered, even if less data', async function (assert) { assert.expect(1); diff --git a/addons/web/static/tests/views/pivot_tests.js b/addons/web/static/tests/views/pivot_tests.js index c098ecd7867..c0b46be3733 100644 --- a/addons/web/static/tests/views/pivot_tests.js +++ b/addons/web/static/tests/views/pivot_tests.js @@ -1096,6 +1096,48 @@ QUnit.module('Views', { pivot.destroy(); }); + QUnit.skip('Reload, group by columns, reload', async function (assert) { + assert.expect(2); + + var pivot = await createView({ + View: PivotView, + model: "partner", + data: this.data, + arch: '<pivot/>', + }); + + // Set a column groupby + pivot.$('thead .o_pivot_header_cell_closed').click(); + pivot.$('.o_field_selection .dropdown-item[data-field=customer]').click(); + + // Set a domain + pivot.update({domain: [['product_id', '=', 41]]}); + + var expectedContext = {pivot_column_groupby: ['customer'], + pivot_measures: ['__count'], + pivot_row_groupby: []}; + + // Check that column groupbys were not lost + assert.deepEqual(pivot.getContext(), expectedContext, + 'Column groupby not lost after first reload'); + + // Set a column groupby + pivot.$('thead .o_pivot_header_cell_closed').click(); + pivot.$('.o_field_selection .dropdown-item[data-field=product_id]').click(); + + // Set a domain + pivot.update({domain: [['product_id', '=', 37]]}); + + var expectedContext = {pivot_column_groupby: ['customer', 'product_id'], + pivot_measures: ['__count'], + pivot_row_groupby: []}; + + assert.deepEqual(pivot.getContext(), expectedContext, + 'Column groupby not lost after second reload'); + + pivot.destroy(); + }); + QUnit.test('correctly uses pivot_ keys from the context', async function (assert) { assert.expect(7); diff --git a/addons/web/static/tests/views/search_view_tests.js b/addons/web/static/tests/views/search_view_tests.js index e57194ef19c..f7d07f8da9d 100644 --- a/addons/web/static/tests/views/search_view_tests.js +++ b/addons/web/static/tests/views/search_view_tests.js @@ -1085,6 +1085,41 @@ QUnit.module('Search View', { actionManager.destroy(); }); + QUnit.test('selecting (no result) triggers a re-render', async function (assert) { + assert.expect(3); + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + }); + + actionManager.doAction(10); + await testUtils.nextTick(); + + // 'a' key to filter nothing on bar + actionManager.$('.o_searchview_input').val('a'); + actionManager.$('.o_searchview_input').trigger($.Event('keypress', { which: 65, keyCode: 65 })); + await testUtils.nextTick(); + actionManager.$('.o_searchview_input').trigger($.Event('keydown', { which: $.ui.keyCode.DOWN, keyCode: $.ui.keyCode.DOWN })); + await testUtils.nextTick(); + actionManager.$('.o_searchview_input').trigger($.Event('keydown', { which: $.ui.keyCode.RIGHT, keyCode: $.ui.keyCode.RIGHT })); + await testUtils.nextTick(); + actionManager.$('.o_searchview_input').trigger($.Event('keydown', { which: $.ui.keyCode.DOWN, keyCode: $.ui.keyCode.DOWN })); + await testUtils.nextTick(); + + assert.strictEqual(actionManager.$('.o_searchview_autocomplete .o-selection-focus').text(), "(no result)", + "there should be no result for 'a' in bar"); + + actionManager.$('.o_searchview_input').trigger($.Event('keydown', { which: $.ui.keyCode.ENTER, keyCode: $.ui.keyCode.ENTER })); + await testUtils.nextTick(); + + assert.containsNone(actionManager, '.o_searchview_facet_label'); + assert.strictEqual(actionManager.$('.o_searchview_input').val(), "", + "the search input should be re-rendered"); + + actionManager.destroy(); + }); + QUnit.module('TimeRangeMenu'); QUnit.test('time range menu stays hidden', async function (assert) { |
