diff options
| author | Christophe Simonis <chs@odoo.com> | 2019-04-12 13:37:44 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2019-04-12 13:37:44 +0200 |
| commit | 0a09a4d8a819ef06db47e798f0465e500004e4c1 (patch) | |
| tree | b9d66d60a1e9b5ff6030be62fff89df410b744ee /addons/web | |
| parent | db97bb7f1136dc3ebde9924b79364a698a63765d (diff) | |
| parent | 120359bf0c98191051ac19ae2e5fde53ca7d2ae5 (diff) | |
[MERGE] forward port branch saas-12.3 up to 120359bf0c9
Diffstat (limited to 'addons/web')
| -rw-r--r-- | addons/web/static/src/js/chrome/systray_menu.js | 41 | ||||
| -rw-r--r-- | addons/web/static/src/js/core/domain.js | 4 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/basic/basic_renderer.js | 8 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/calendar/calendar_controller.js | 1 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/list/list_editable_renderer.js | 20 | ||||
| -rw-r--r-- | addons/web/static/src/scss/layout_background.scss | 7 | ||||
| -rw-r--r-- | addons/web/static/src/scss/list_view.scss | 2 | ||||
| -rw-r--r-- | addons/web/static/tests/chrome/systray_tests.js | 43 | ||||
| -rw-r--r-- | addons/web/static/tests/helpers/test_utils_create.js | 1 | ||||
| -rw-r--r-- | addons/web/static/tests/views/calendar_tests.js | 36 | ||||
| -rw-r--r-- | addons/web/static/tests/views/list_tests.js | 156 | ||||
| -rw-r--r-- | addons/web/views/webclient_templates.xml | 1 |
12 files changed, 291 insertions, 29 deletions
diff --git a/addons/web/static/src/js/chrome/systray_menu.js b/addons/web/static/src/js/chrome/systray_menu.js index fd214c0b650..cb292a9b90f 100644 --- a/addons/web/static/src/js/chrome/systray_menu.js +++ b/addons/web/static/src/js/chrome/systray_menu.js @@ -1,6 +1,7 @@ odoo.define('web.SystrayMenu', function (require) { "use strict"; +var dom = require('web.dom'); var Widget = require('web.Widget'); /** @@ -18,35 +19,35 @@ var SystrayMenu = Widget.extend({ this.widgets = []; }, /** + * Instanciate the items and add them into a temporary fragmenet * @override - * @returns {Promise} */ - start: function () { + willStart: function () { var self = this; - self._super.apply(this, arguments); - self._loadItems(); - - return new Promise(function (resolve, reject) { - Promise.all(self.items).then(resolve).guardedCatch(resolve); + var proms = []; + SystrayMenu.Items = _.sortBy(SystrayMenu.Items, function (item) { + return !_.isUndefined(item.prototype.sequence) ? item.prototype.sequence : 50; }); - }, - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- + SystrayMenu.Items.forEach(function (WidgetClass) { + var cur_systray_item = new WidgetClass(self); + self.widgets.push(cur_systray_item); + proms.push(cur_systray_item.appendTo($('<div>'))); + }); + return this._super.apply(this, arguments).then(function () { + return Promise.all(proms); + }); + }, /** - * Instantiate items, using the classes located in SystrayMenu.items. + * Add the instanciated items, using the object located in this.wisgets */ - _loadItems: function () { + start: function () { var self = this; - SystrayMenu.Items = _.sortBy(SystrayMenu.Items, function (item) { - return !_.isUndefined(item.prototype.sequence) ? item.prototype.sequence : 50; - }); - _.each(SystrayMenu.Items, function (WidgetClass) { - var cur_systray_item = new WidgetClass(self); - self.widgets.push(cur_systray_item); - self.items.push(cur_systray_item.prependTo(self.$el)); + return this._super.apply(this, arguments).then(function () { + self.widgets.forEach(function (widget) { + dom.prepend(self.$el, widget.$el); + }); }); }, }); diff --git a/addons/web/static/src/js/core/domain.js b/addons/web/static/src/js/core/domain.js index c7533aabae0..9c74dcb953e 100644 --- a/addons/web/static/src/js/core/domain.js +++ b/addons/web/static/src/js/core/domain.js @@ -263,8 +263,8 @@ var Domain = collections.Tree.extend({ }); break; case 'previous_year': - leftBoundaryParams.years = leftBoundaryParams.years ? leftBoundaryParams.years-- : -1; - rightBoundaryParams.years = rightBoundaryParams.years ? rightBoundaryParams.years-- : -1; + leftBoundaryParams.years = leftBoundaryParams.years ? leftBoundaryParams.years - 1 : -1; + rightBoundaryParams.years = rightBoundaryParams.years ? rightBoundaryParams.years - 1 : -1; break; } diff --git a/addons/web/static/src/js/views/basic/basic_renderer.js b/addons/web/static/src/js/views/basic/basic_renderer.js index f741e11b789..4c03d9b77a1 100644 --- a/addons/web/static/src/js/views/basic/basic_renderer.js +++ b/addons/web/static/src/js/views/basic/basic_renderer.js @@ -731,8 +731,12 @@ var BasicRenderer = AbstractRenderer.extend({ var defs = []; this.defs = defs; // Potentially filled by widget rerendering _.each(this.allModifiersData, function (modifiersData) { - modifiersData.evaluatedModifiers[record.id] = record.evalModifiers(modifiersData.modifiers); - self._applyModifiers(modifiersData, record); + // `allModifiersData` might contain modifiers registered for other + // records than the given record (e.g. <groupby> in list) + if (record.id in modifiersData.evaluatedModifiers) { + modifiersData.evaluatedModifiers[record.id] = record.evalModifiers(modifiersData.modifiers); + self._applyModifiers(modifiersData, record); + } }); delete this.defs; diff --git a/addons/web/static/src/js/views/calendar/calendar_controller.js b/addons/web/static/src/js/views/calendar/calendar_controller.js index ec85b567cf7..dda3978635b 100644 --- a/addons/web/static/src/js/views/calendar/calendar_controller.js +++ b/addons/web/static/src/js/views/calendar/calendar_controller.js @@ -272,6 +272,7 @@ var CalendarController = AbstractController.extend({ res_model: this.modelName, context: context, title: title, + view_id: this.formViewId || false, disable_multiple_selection: true, on_saved: function () { if (event.data.on_save) { 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 9f37fcecbea..818739c6f48 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 @@ -435,6 +435,18 @@ ListRenderer.include({ }); } }, + /** + * @override + */ + updateState: function (state, params) { + if (params.noRender) { + // the state changed, but we won't do a re-rendering right now, so + // remove computed modifiers data (as they are obsolete) to force + // them to be recomputed at next (sub-)rendering + this.allModifiersData = []; + } + return this._super.apply(this, arguments); + }, //-------------------------------------------------------------------------- // Private @@ -586,7 +598,7 @@ ListRenderer.include({ this.commitChanges(curRecordId).then(function () { var $prevRow = self._getRow(self.allRecordIds[prevRecordIndex]); var prevRowIndex = $prevRow.prop('rowIndex') - 1; - self._selectCell(prevRowIndex, self.columns.length - 1); + self._selectCell(prevRowIndex, self.columns.length - 1, {inc: -1}); }); }, /** @@ -955,6 +967,8 @@ ListRenderer.include({ * @param {boolean} [options.force=false] if true, force selecting the cell * even if seems to be already the selected one (useful after a re- * rendering, to reset the focus on the correct field) + * @param {integer} [options.inc=1] the increment to use when searching for + * the "next" possible cell (if the cell to select can't be selected) * @return {Promise} fails if no cell could be selected */ _selectCell: function (rowIndex, fieldIndex, options) { @@ -979,7 +993,7 @@ ListRenderer.include({ var oldFieldIndex = self.currentFieldIndex; self.currentFieldIndex = fieldIndex; fieldIndex = self._activateFieldWidget(record, fieldIndex, { - inc: 1, + inc: options.inc || 1, wrap: wrap, event: options && options.event, }); @@ -1183,7 +1197,7 @@ ListRenderer.include({ switch (ev.data.direction) { case 'previous': if (this.currentFieldIndex > 0) { - this._selectCell(this.currentRow, this.currentFieldIndex - 1, {wrap: false}) + this._selectCell(this.currentRow, this.currentFieldIndex - 1, {inc: -1, wrap: false}) .guardedCatch(this._moveToPreviousLine.bind(this)); } else { this._moveToPreviousLine(); diff --git a/addons/web/static/src/scss/layout_background.scss b/addons/web/static/src/scss/layout_background.scss index 9b137fde8c1..caeb1f0c2bf 100644 --- a/addons/web/static/src/scss/layout_background.scss +++ b/addons/web/static/src/scss/layout_background.scss @@ -82,3 +82,10 @@ } } } + +/* special case for displaying report in iframe */ +.o_in_iframe { + .o_background_header { + min-width: 0; + } +} diff --git a/addons/web/static/src/scss/list_view.scss b/addons/web/static/src/scss/list_view.scss index 07da78731f3..86b989dbd10 100644 --- a/addons/web/static/src/scss/list_view.scss +++ b/addons/web/static/src/scss/list_view.scss @@ -76,7 +76,7 @@ } } - .o_list_record_selector { + .o_list_record_selector, .o_list_record_remove, .o_handle_cell { width: 1px; // to prevent the column to expand } diff --git a/addons/web/static/tests/chrome/systray_tests.js b/addons/web/static/tests/chrome/systray_tests.js new file mode 100644 index 00000000000..c5238ceff04 --- /dev/null +++ b/addons/web/static/tests/chrome/systray_tests.js @@ -0,0 +1,43 @@ +odoo.define('web.systray_tests', function (require) { + "use strict"; + + var testUtils = require('web.test_utils'); + var SystrayMenu = require('web.SystrayMenu'); + var Widget = require('web.Widget'); + + QUnit.test('Adding async components to the registry respects the sequence', async function (assert) { + assert.expect(2); + var parent = testUtils.createParent({ + }); + var prom = testUtils.makeTestPromise(); + + var synchronousFirstWidget = Widget.extend({ + sequence: 3, // bigger sequence means more to the left + start: function () { + this.$el.addClass('first'); + } + }); + var asynchronousSecondWidget = Widget.extend({ + sequence: 1, // smaller sequence means more to the right + willStart: function () { + return prom; + }, + start: function () { + this.$el.addClass('second'); + } + }); + + SystrayMenu.Items = [synchronousFirstWidget, asynchronousSecondWidget]; + var menu = new SystrayMenu(parent); + + menu.appendTo($('#qunit-fixture')); + await testUtils.nextTick(); + prom.resolve(); + await testUtils.nextTick(); + + assert.hasClass(menu.$('div:eq(0)'), 'first'); + assert.hasClass(menu.$('div:eq(1)'), 'second'); + + parent.destroy(); + }) +}); diff --git a/addons/web/static/tests/helpers/test_utils_create.js b/addons/web/static/tests/helpers/test_utils_create.js index 097d46e93f7..f1c67284707 100644 --- a/addons/web/static/tests/helpers/test_utils_create.js +++ b/addons/web/static/tests/helpers/test_utils_create.js @@ -392,7 +392,6 @@ function createParent(params) { return { createActionManager: createActionManager, - createView: createView, createCalendarView: createCalendarView, createControlPanel: createControlPanel, createDebugManager: createDebugManager, diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js index 9f637c4fdc5..01f431589fc 100644 --- a/addons/web/static/tests/views/calendar_tests.js +++ b/addons/web/static/tests/views/calendar_tests.js @@ -2371,6 +2371,42 @@ QUnit.module('Views', { calendar.destroy(); }); + QUnit.test('form_view_id attribute works with popup (for creating events)', async function (assert) { + assert.expect(1); + + var calendar = await createCalendarView({ + View: CalendarView, + model: 'event', + data: this.data, + arch: '<calendar class="o_calendar_test" '+ + 'date_start="start" '+ + 'date_stop="stop" '+ + 'mode="month" '+ + 'event_open_popup="true" ' + + 'quick_add="false" ' + + 'form_view_id="1">'+ + '<field name="name"/>'+ + '</calendar>', + archs: archs, + viewOptions: { + initialDate: initialDate, + }, + mockRPC: function (route, args) { + if (args.method === "load_views") { + assert.strictEqual(args.kwargs.views[0][0], 1, + "should load view with id 1"); + } + return this._super(route, args); + }, + }); + + var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); + testUtils.dom.triggerMouseEvent($cell, "mousedown"); + testUtils.dom.triggerMouseEvent($cell, "mouseup"); + await testUtils.nextTick(); + calendar.destroy(); + }); + QUnit.test('calendar fallback to form view id in action if necessary', async function (assert) { assert.expect(1); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index 5e5a0c7b7a1..7aff294fb97 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -1957,6 +1957,37 @@ QUnit.module('Views', { list.destroy(); }); + QUnit.test('editable list view with groupby node and modifiers', async function (assert) { + assert.expect(3); + + var list = await createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '<tree expand="1" editable="bottom">' + + '<field name="foo"/>' + + '<groupby name="currency_id">' + + '<field name="position"/>' + + '<button string="Button 1" type="object" name="button_method" attrs=\'{"invisible": [("position", "=", "after")]}\'/>' + + '</groupby>' + + '</tree>', + groupBy: ['currency_id'], + }); + + assert.doesNotHaveClass(list.$('.o_data_row:first'), 'o_selected_row', + "first row should be in readonly mode"); + + await testUtils.dom.click(list.$('.o_data_row:first .o_data_cell')); + assert.hasClass(list.$('.o_data_row:first'), 'o_selected_row', + "the row should be in edit mode"); + + await testUtils.fields.triggerKeydown($(document.activeElement), 'escape'); + assert.doesNotHaveClass(list.$('.o_data_row:first'), 'o_selected_row', + "the row should be back in readonly mode"); + + list.destroy(); + }); + QUnit.test('groupby node with edit button', async function (assert) { assert.expect(1); @@ -3212,6 +3243,90 @@ QUnit.module('Views', { list.destroy(); }); + QUnit.test('pressing SHIFT-TAB in editable list with a readonly field [REQUIRE FOCUS]', async function (assert) { + assert.expect(4); + + var list = await createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '<tree editable="bottom">' + + '<field name="foo"/>' + + '<field name="int_field" readonly="1"/>' + + '<field name="qux"/>' + + '</tree>', + }); + + // start on 'qux', line 3 + await testUtils.dom.click(list.$('.o_data_row:nth(2) .o_data_cell:nth(2)')); + assert.hasClass(list.$('.o_data_row:nth(2)'), 'o_selected_row'); + assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(2) .o_data_cell input[name=qux]')[0]); + + // Press 'shift-Tab' -> should go to first cell (same line) + $(document.activeElement).trigger({type: 'keydown', which: $.ui.keyCode.TAB, shiftKey: true}); + await testUtils.nextTick(); + assert.hasClass(list.$('.o_data_row:nth(2)'), 'o_selected_row'); + assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(2) .o_data_cell input[name=foo]')[0]); + + list.destroy(); + }); + + QUnit.test('pressing SHIFT-TAB in editable list with a readonly field in first column [REQUIRE FOCUS]', async function (assert) { + assert.expect(4); + + var list = await createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '<tree editable="bottom">' + + '<field name="int_field" readonly="1"/>' + + '<field name="foo"/>' + + '<field name="qux"/>' + + '</tree>', + }); + + // start on 'foo', line 3 + await testUtils.dom.click(list.$('.o_data_row:nth(2) .o_data_cell:nth(1)')); + assert.hasClass(list.$('.o_data_row:nth(2)'), 'o_selected_row'); + assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(2) .o_data_cell input[name=foo]')[0]); + + // Press 'shift-Tab' -> should go to previous line (last cell) + $(document.activeElement).trigger({type: 'keydown', which: $.ui.keyCode.TAB, shiftKey: true}); + await testUtils.nextTick(); + assert.hasClass(list.$('.o_data_row:nth(1)'), 'o_selected_row'); + assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(1) .o_data_cell input[name=qux]')[0]); + + list.destroy(); + }); + + QUnit.test('pressing SHIFT-TAB in editable list with a readonly field in last column [REQUIRE FOCUS]', async function (assert) { + assert.expect(4); + + var list = await createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '<tree editable="bottom">' + + '<field name="int_field"/>' + + '<field name="foo"/>' + + '<field name="qux" readonly="1"/>' + + '</tree>', + }); + + // start on 'int_field', line 3 + await testUtils.dom.click(list.$('.o_data_row:nth(2) .o_data_cell:first')); + assert.hasClass(list.$('.o_data_row:nth(2)'), 'o_selected_row'); + assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(2) .o_data_cell input[name=int_field]')[0]); + + // Press 'shift-Tab' -> should go to previous line ('foo' field) + $(document.activeElement).trigger({type: 'keydown', which: $.ui.keyCode.TAB, shiftKey: true}); + await testUtils.nextTick(); + assert.hasClass(list.$('.o_data_row:nth(1)'), 'o_selected_row'); + assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(1) .o_data_cell input[name=foo]')[0]); + + list.destroy(); + }); + QUnit.test('skip invisible fields when navigating list view with TAB', async function (assert) { assert.expect(2); @@ -4460,6 +4575,47 @@ QUnit.module('Views', { list.destroy(); }); + QUnit.test('create record on list with modifiers depending on id', async function (assert) { + assert.expect(8); + + var list = await createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '<tree editable="top">' + + '<field name="id" invisible="1"/>' + + '<field name="foo" attrs="{\'readonly\': [[\'id\',\'!=\',False]]}"/>' + + '<field name="int_field" attrs="{\'invisible\': [[\'id\',\'!=\',False]]}"/>' + + '</tree>', + }); + + // add a new record + await testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + + // modifiers should be evaluted to false + assert.containsOnce(list, '.o_selected_row'); + assert.doesNotHaveClass(list.$('.o_selected_row .o_data_cell:first'), 'o_readonly_modifier'); + assert.doesNotHaveClass(list.$('.o_selected_row .o_data_cell:nth(1)'), 'o_invisible_modifier'); + + // set a value and save + await testUtils.fields.editInput(list.$('.o_selected_row input[name=foo]'), 'some value'); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + + // modifiers should be evaluted to true + assert.hasClass(list.$('.o_data_row:first .o_data_cell:first'), 'o_readonly_modifier'); + assert.hasClass(list.$('.o_data_row:first .o_data_cell:nth(1)'), 'o_invisible_modifier'); + + // edit again the just created record + await testUtils.dom.click(list.$('.o_data_row:first .o_data_cell:first')); + + // modifiers should be evaluted to true + assert.containsOnce(list, '.o_selected_row'); + assert.hasClass(list.$('.o_selected_row .o_data_cell:first'), 'o_readonly_modifier'); + assert.hasClass(list.$('.o_selected_row .o_data_cell:nth(1)'), 'o_invisible_modifier'); + + list.destroy(); + }); + QUnit.test('grouped list with async widget', async function (assert) { assert.expect(4); diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml index 3759917ac09..5e5a19a85ba 100644 --- a/addons/web/views/webclient_templates.xml +++ b/addons/web/views/webclient_templates.xml @@ -586,6 +586,7 @@ <script type="text/javascript" src="/web/static/tests/chrome/pager_tests.js"></script> <script type="text/javascript" src="/web/static/tests/chrome/keyboard_navigation_mixin_tests.js"></script> <script type="text/javascript" src="/web/static/tests/chrome/user_menu_tests.js"></script> + <script type="text/javascript" src="/web/static/tests/chrome/systray_tests.js"></script> <script type="text/javascript" src="/web/static/tests/widgets/data_export_tests.js"/> <script type="text/javascript" src="/web/static/tests/widgets/domain_selector_tests.js"/> |
