From fbb0450d512e97c960de780d772486203529e6c0 Mon Sep 17 00:00:00 2001 From: David Monjoie Date: Fri, 24 Mar 2017 15:51:22 +0100 Subject: [PATCH] [ADD] web: add state_selection widget tests and documentation This commit also adds the special_click property which is used to mark a DOM element such that clicks on it will not be intercepted by the list view. Basically, we need this on elements which rely on bootstrap events, since those need to bubble all the way up. If we catch them in the list view, to open a cell or a record, then they never get to bootstrap's level and so the widgets relying on these elements cannot work. We plan to remove it at some point, when we have more time to figure out a way for the click event to reach bootstrap from inside the list view without breaking the list view behavior itself. --- .../web/static/src/js/fields/basic_fields.js | 48 ++++- .../js/views/list/list_editable_renderer.js | 18 +- .../static/src/js/views/list/list_renderer.js | 10 +- .../static/test/fields/basic_fields_tests.js | 199 ++++++++++++++++++ 4 files changed, 261 insertions(+), 14 deletions(-) diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index 81ca4323871..e62eaf7b8e3 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -1167,9 +1167,21 @@ var StateSelectionWidget = AbstractField.extend({ 'click a': function (e) { e.preventDefault(); }, - 'click li': 'set_selection' + 'click li': '_setSelection' }, - prepare_dropdown_values: function () { + readonly: true, + supportedFieldTypes: ['selection'], + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Prepares the state values to be rendered using the FormSelection.Items template. + * + * @private + */ + _prepareDropdownValues: function () { var self = this; var _data = []; var current_stage_id = self.recordData.stage_id && self.recordData.stage_id[0]; @@ -1197,26 +1209,44 @@ var StateSelectionWidget = AbstractField.extend({ }); return _data; }, + + /** + * This widget uses the FormSelection template but needs to customize it a bit. + * + * @private + * @override + */ _render: function () { var self = this; - var states = this.prepare_dropdown_values(); + var states = this._prepareDropdownValues(); // Adapt "FormSelection" - var current_state = _.find(states, function (state) { - return state.name === self.value; - }); + // Like priority, default on the first possible value if no value is given. + var currentState = _.findWhere(states, {name: self.value}) || states[0]; this.$('.o_status') .removeClass('o_status_red o_status_green') - .addClass(current_state.state_class); + .addClass(currentState.state_class) + .prop('special_click', true); // Render "FormSelection.Items" and move it into "FormSelection" var $items = $(qweb.render('FormSelection.items', { - states: _.without(states, current_state) + states: _.without(states, currentState) })); var $dropdown = this.$('.dropdown-menu'); $dropdown.children().remove(); // remove old items $items.appendTo($dropdown); }, - set_selection: function (ev) { + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Intercepts the click on the FormSelection.Item to set the widget value. + * + * @private + * @param {MouseEvent} ev + */ + _setSelection: function (ev) { var li = $(ev.target).closest('li'); if (li.length) { var value = String(li.data('value')); 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 0c6a9a4c9ce..d8ab57e7a69 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 @@ -492,10 +492,13 @@ ListRenderer.include({ /** * When the user clicks on a cell, we simply select it. * + * @private * @param {MouseEvent} event */ _onCellClick: function (event) { - if (this.mode === 'readonly' || !this.editable) { + // The special_click property explicitely allow events to bubble all + // the way up to bootstrap's level rather than being stopped earlier. + if (this.mode === 'readonly' || !this.editable || $(event.target).prop('special_click')) { return; } var $td = $(event.currentTarget); @@ -503,7 +506,18 @@ ListRenderer.include({ var colIndex = $td.data('index'); if (colIndex !== undefined && rowIndex !== undefined) { this._selectCell(rowIndex, colIndex); - event.stopPropagation(); + } + }, + /** + * If the list view editable, just let the event bubble. We don't want to + * open the record in this case anyway. + * + * @override + * @private + */ + _onRowClicked: function () { + if (this.mode === 'readonly' || !this.editable) { + this._super.apply(this, arguments); } }, /** diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js index fe80c9b17f9..3495e78d2f2 100644 --- a/addons/web/static/src/js/views/list/list_renderer.js +++ b/addons/web/static/src/js/views/list/list_renderer.js @@ -657,9 +657,13 @@ var ListRenderer = BasicRenderer.extend({ * @param {MouseEvent} event */ _onRowClicked: function (event) { - var id = $(event.currentTarget).data('id'); - if (id) { - this.trigger_up('open_record', {id:id, target: event.target}); + // The special_click property explicitely allow events to bubble all + // the way up to bootstrap's level rather than being stopped earlier. + if (!$(event.target).prop('special_click')) { + var id = $(event.currentTarget).data('id'); + if (id) { + this.trigger_up('open_record', {id:id, target: event.target}); + } } }, /** diff --git a/addons/web/static/test/fields/basic_fields_tests.js b/addons/web/static/test/fields/basic_fields_tests.js index 62d1fd6efe5..d489db97572 100644 --- a/addons/web/static/test/fields/basic_fields_tests.js +++ b/addons/web/static/test/fields/basic_fields_tests.js @@ -57,6 +57,7 @@ QUnit.module('basic_fields', { trululu: 1, sequence: 4, currency_id: 2, + selection: 'normal', }, { id: 4, display_name: "aaa", @@ -64,6 +65,7 @@ QUnit.module('basic_fields', { sequence: 9, int_field: false, qux: false, + selection: 'done', }, {id: 3, bar: true, foo: "gnap", int_field: 17, qux: -3.89859, m2o: 1, m2m: []}, {id: 5, bar: false, foo: "blop", int_field: -4, qux: 9.1, m2o: 1, m2m: [1], currency_id: 1}], @@ -1882,6 +1884,203 @@ QUnit.module('basic_fields', { }); + QUnit.module('StateSelection Widget'); + + QUnit.test('state_selection widget in form view', function (assert) { + assert.expect(21); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + }); + + assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status.o_status_red').length, 1, + "should have one red status since selection is the second, blocked state"); + assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status.o_status_green').length, 0, + "should not have one green status since selection is the second, blocked state"); + assert.strictEqual(form.$('ul.dropdown-menu.state:visible').length, 0, + "there should not be a dropdown"); + + // Click on the status button to make the dropdown appear + form.$('.o_form_field.o_selection .o_status').first().click(); + assert.strictEqual(form.$('ul.dropdown-menu.state:visible').length, 1, + "there should be a dropdown"); + assert.strictEqual(form.$('ul.dropdown-menu.state:visible li').length, 2, + "there should be two options in the dropdown"); + + // Click on the first option, "Normal" + form.$('ul.dropdown-menu.state:visible li').first().click(); + assert.strictEqual(form.$('ul.dropdown-menu.state:visible').length, 0, + "there should not be a dropdown anymore"); + assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status.o_status_red').length, 0, + "should not have one red status since selection is the first, normal state"); + assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status.o_status_green').length, 0, + "should not have one green status since selection is the first, normal state"); + assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status').length, 1, + "should have one grey status since selection is the first, normal state"); + + // switch to edit mode and check the result + form.$buttons.find('.o_form_button_edit').click(); + assert.strictEqual(form.$('ul.dropdown-menu.state:visible').length, 0, + "there should still not be a dropdown"); + assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status.o_status_red').length, 0, + "should still not have one red status since selection is the first, normal state"); + assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status.o_status_green').length, 0, + "should still not have one green status since selection is the first, normal state"); + assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status').length, 1, + "should still have one grey status since selection is the first, normal state"); + + // Click on the status button to make the dropdown appear + form.$('.o_form_field.o_selection .o_status').first().click(); + assert.strictEqual(form.$('ul.dropdown-menu.state:visible').length, 1, + "there should be a dropdown"); + assert.strictEqual(form.$('ul.dropdown-menu.state:visible li').length, 2, + "there should be two options in the dropdown"); + + // Click on the last option, "Done" + form.$('ul.dropdown-menu.state:visible li').last().click(); + assert.strictEqual(form.$('ul.dropdown-menu.state:visible').length, 0, + "there should not be a dropdown anymore"); + assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status.o_status_red').length, 0, + "should not have one red status since selection is the third, done state"); + assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status.o_status_green').length, 1, + "should have one green status since selection is the third, done state"); + + // save + form.$buttons.find('.o_form_button_save').click(); + assert.strictEqual(form.$('ul.dropdown-menu.state:visible').length, 0, + "there should still not be a dropdown anymore"); + assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status.o_status_red').length, 0, + "should still not have one red status since selection is the third, done state"); + assert.strictEqual(form.$('.o_form_field.o_selection > a span.o_status.o_status_green').length, 1, + "should still have one green status since selection is the third, done state"); + + form.destroy(); + }); + + QUnit.test('state_selection widget in editable list view', function(assert) { + assert.expect(34); + + var list = createView({ + View: ListView, + model: 'partner', + data: this.data, + arch: '' + + '' + + '' + + '', + }); + + assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status').length, 5, + "should have five status selection widgets"); + assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status.o_status_red').length, 1, + "should have one red status"); + assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status.o_status_green').length, 1, + "should have one green status"); + assert.strictEqual(list.$('ul.dropdown-menu.state:visible').length, 0, + "there should not be a dropdown"); + + // Click on the status button to make the dropdown appear + var $cell = list.$('tbody td.o_state_selection_cell').first(); + list.$('.o_state_selection_cell .o_selection > a span.o_status').first().click(); + assert.ok(!$cell.hasClass('o_edit_mode'), + 'should not be in edit mode since we clicked on the widget and not the cell itself'); + assert.ok(!$cell.siblings().hasClass('o_edit_mode'), + 'siblings should not be in edit mode since we clicked on the widget and not the cell itself'); + assert.strictEqual(list.$('ul.dropdown-menu.state:visible').length, 1, + "there should be a dropdown"); + assert.strictEqual(list.$('ul.dropdown-menu.state:visible li').length, 2, + "there should be two options in the dropdown"); + + // Click on the first option, "Normal" + list.$('ul.dropdown-menu.state:visible li').first().click(); + assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status').length, 5, + "should still have five status selection widgets"); + assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status.o_status_red').length, 0, + "should now have no red status"); + assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status.o_status_green').length, 1, + "should still have one green status"); + assert.strictEqual(list.$('ul.dropdown-menu.state:visible').length, 0, + "there should not be a dropdown"); + + // switch to edit mode and check the result + $cell = list.$('tbody td.o_state_selection_cell').first(); + $cell.click(); + assert.ok(!$cell.hasClass('o_edit_mode'), + 'should still not be in edit mode since widget is readonly'); + assert.ok($cell.siblings().hasClass('o_edit_mode'), + 'siblings should be in edit mode since we clicked on the cell and not the widget itself'); + assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status').length, 5, + "should still have five status selection widgets"); + assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status.o_status_red').length, 0, + "should now have no red status"); + assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status.o_status_green').length, 1, + "should still have one green status"); + assert.strictEqual(list.$('ul.dropdown-menu.state:visible').length, 0, + "there should not be a dropdown"); + + // Click on the status button to make the dropdown appear + list.$('.o_state_selection_cell .o_selection > a span.o_status').first().click(); + assert.strictEqual(list.$('ul.dropdown-menu.state:visible').length, 1, + "there should be a dropdown"); + assert.strictEqual(list.$('ul.dropdown-menu.state:visible li').length, 2, + "there should be two options in the dropdown"); + + // Click on another row + var $firstCell = list.$('tbody td.o_state_selection_cell').first(); + var $lastCell = list.$('tbody td.o_state_selection_cell').last(); + $lastCell.click(); + assert.strictEqual(list.$('ul.dropdown-menu.state:visible').length, 0, + "there should not be a dropdown anymore"); + assert.ok(!$firstCell.siblings().hasClass('o_edit_mode'), + 'first row should not be in edit mode anymore'); + assert.ok($lastCell.siblings().hasClass('o_edit_mode'), + 'last row should be in edit mode'); + + // Click on the last status button to make the dropdown appear + list.$('.o_state_selection_cell .o_selection > a span.o_status').last().click(); + assert.strictEqual(list.$('ul.dropdown-menu.state:visible').length, 1, + "there should be a dropdown"); + assert.strictEqual(list.$('ul.dropdown-menu.state:visible li').length, 2, + "there should be two options in the dropdown"); + + // Click on the last option, "Done" + list.$('ul.dropdown-menu.state:visible li').last().click(); + assert.strictEqual(list.$('ul.dropdown-menu.state:visible').length, 0, + "there should not be a dropdown anymore"); + assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status').length, 5, + "should still have five status selection widgets"); + assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status.o_status_red').length, 0, + "should still have no red status"); + assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status.o_status_green').length, 2, + "should now have two green status"); + assert.strictEqual(list.$('ul.dropdown-menu.state:visible').length, 0, + "there should not be a dropdown"); + + // save + list.$buttons.find('.o_list_button_save').click(); + assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status').length, 5, + "should have five status selection widgets"); + assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status.o_status_red').length, 0, + "should have no red status"); + assert.strictEqual(list.$('.o_state_selection_cell .o_selection > a span.o_status.o_status_green').length, 2, + "should have two green status"); + assert.strictEqual(list.$('ul.dropdown-menu.state:visible').length, 0, + "there should not be a dropdown"); + + list.destroy(); + }); + + QUnit.module('FieldDomain'); QUnit.test('basic domain field usage is ok', function (assert) {