', {
+ class: 'table-responsive',
+ html: $table
+ }));
+
+ if (self.optionalColumns.length) {
+ self.$el.addClass('o_list_optional_columns')
+ self.$('table').append($('
'));
+ self.$el.append(self._renderOptionalColumnsDropdown());
+ }
if (self.selection.length) {
var $checked_rows = self.$('tr').filter(function (index, el) {
@@ -914,6 +1004,45 @@ var ListRenderer = BasicRenderer.extend({
ev.stopPropagation();
}
},
+ /**
+ * When the user clicks on the checkbox in optional fields dropdown the
+ * column is added to listview and displayed
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onToggleOptionalColumn: function (ev) {
+ var self = this;
+ ev.stopPropagation();
+ var input = ev.currentTarget.querySelector('input');
+ var fieldIndex = this.optionalColumnsEnabled.indexOf(input.name);
+ if (fieldIndex >= 0) {
+ this.optionalColumnsEnabled.splice(fieldIndex, 1);
+ } else {
+ this.optionalColumnsEnabled.push(input.name);
+ }
+ this.call('local_storage', 'setItem', this._getOptionalColumnsStorageKey(), this.optionalColumnsEnabled);
+ this._processColumns(this.columnInvisibleFields || {});
+ this._renderView().then(function() {
+ self._onToggleOptionalColumnDropdown(ev);
+ })
+ },
+ /**
+ * When the user clicks on the three dots (ellipsis), toggle the optional
+ * fields dropdown.
+ *
+ * @private
+ */
+ _onToggleOptionalColumnDropdown: function (ev) {
+ // The dropdown toggle is inside the overflow hidden container because
+ // the ellipsis is always in the last column, but we want the actual
+ // dropdown to be outside of the overflow hidden container since it
+ // could easily have a higher height than the table. However, separating
+ // the toggle and the dropdown itself is not supported by popper.js by
+ // default, which is why we need to toggle the dropdown manually.
+ ev.stopPropagation();
+ this.$('.o_optional_columns .dropdown-toggle').dropdown('toggle');
+ },
/**
* Manages the keyboard events on the list. If the list is not editable, when the user navigates to
* a cell using the keyboard, if he presses enter, enter the model represented by the line
diff --git a/addons/web/static/src/scss/list_view.scss b/addons/web/static/src/scss/list_view.scss
index 45744a7a1f8..3d46aeef890 100644
--- a/addons/web/static/src/scss/list_view.scss
+++ b/addons/web/static/src/scss/list_view.scss
@@ -1,233 +1,266 @@
.o_list_view {
position: relative;
- cursor: pointer;
- background-color: $o-view-background-color;
- margin-bottom: 0px;
- // Don't understand why but without those, the list view appears broken
- // in both form views and standalone list views.
- border-collapse: initial;
- border-spacing: 0;
-
- thead {
- color: $o-main-text-color;
- border-bottom: 1px solid #cacaca;
- > tr > th:not(.o_list_record_selector) {
- border-left: 1px solid #dfdfdf;
- @include o-text-overflow(table-cell);
- &:not(.o_column_sortable):hover {
- cursor: default;
- }
- }
- }
- tbody > tr > td:not(.o_list_record_selector) {
- @include o-text-overflow(table-cell);
-
- &.o_list_number {
- text-align: right;
- }
- &.o_list_text {
- white-space: pre-wrap;
- }
- }
-
- tfoot {
- cursor: default;
- color: $o-list-footer-color;
- background-color: $o-list-footer-bg-color;
- font-weight: $o-list-footer-font-weight;
- border-top: 2px solid #cacaca;
- border-bottom: 1px solid #cacaca;
- }
-
- .o_column_sortable {
+ .o_list_table {
position: relative;
- user-select: none; // Prevent unwanted selection while sorting
-
- &::after {
- font-family: FontAwesome;
- content: "\f0d7";
- opacity: 0;
- }
- &:not(:empty)::after {
- margin-left: 6px;
- }
- &.o-sort-up {
- cursor: n-resize;
- &::after {
- content: "\f0d7";
- }
- }
- &.o-sort-down {
- cursor: s-resize;
- &::after {
- content: "\f0d8";
- }
- }
- &:hover::after {
- opacity: 0.3;
- }
- &.o-sort-up, &.o-sort-down {
- color: $headings-color;
- &::after {
- opacity: 1;
- }
- }
- }
-
- .o_list_record_selector, .o_list_record_remove, .o_handle_cell {
- width: 1px; // to prevent the column to expand
- }
-
- .o_list_record_remove button {
- padding: 0px;
- background: none;
- border-style: none;
- display: table-cell;
cursor: pointer;
- }
+ background-color: $o-view-background-color;
+ margin-bottom: 0px;
- // Contextual classes
- @each $color, $value in $theme-colors {
- $safe: saturate(theme-color-level($color, 2), 10%);
- @include text-emphasis-variant(".text-#{$color}", $safe);
- }
+ // Don't understand why but without those, the list view appears broken
+ // in both form views and standalone list views.
+ border-collapse: initial;
+ border-spacing: 0;
- // Grouped list views
- tbody > tr.o_group_header {
- > th, > td {
- vertical-align: middle;
- padding-top: ($table-cell-padding-sm*2);
- padding-bottom: ($table-cell-padding-sm*2);
- }
- .o_group_name {
- white-space: nowrap;
- }
- .o_group_buttons {
- display: inline-block;
- margin-left: 10px;
-
- > button {
- @include o-hover-text-color($o-brand-primary, darken($o-brand-primary, 20%));
- background-color: transparent;
- border: none;
- padding-top: 0;
- padding-bottom: 0;
+ thead {
+ color: $o-main-text-color;
+ border-bottom: 1px solid #cacaca;
+ > tr > th:not(.o_list_record_selector) {
+ border-left: 1px solid #dfdfdf;
+ @include o-text-overflow(table-cell);
+ &:not(.o_column_sortable):hover {
+ cursor: default;
+ }
}
}
- .o_group_pager {
+ tbody > tr > td:not(.o_list_record_selector) {
+ @include o-text-overflow(table-cell);
+
+ &.o_list_number {
+ text-align: right;
+ }
+ &.o_list_text {
+ white-space: pre-wrap;
+ }
+ }
+
+ tfoot {
+ cursor: default;
+ color: $o-list-footer-color;
+ background-color: $o-list-footer-bg-color;
+ font-weight: $o-list-footer-font-weight;
+ border-top: 2px solid #cacaca;
+ border-bottom: 1px solid #cacaca;
+ }
+
+ .o_column_sortable {
position: relative;
+ user-select: none; // Prevent unwanted selection while sorting
- .o_pager {
- @include o-position-absolute(0, 16px);
- cursor: text;
- user-select: none;
- padding-top: 3px !important;
- padding-bottom: 0 !important;
+ &::after {
+ font-family: FontAwesome;
+ content: "\f0d7";
+ opacity: 0;
}
- .o_pager_previous, .o_pager_next {
- max-height: 30px;
- background-color: $o-list-group-header-color;
- &:hover {
- background-color: darken($o-list-group-header-color, 10%);
+ &:not(:empty)::after {
+ margin-left: 6px;
+ }
+ &.o-sort-up {
+ cursor: n-resize;
+ &::after {
+ content: "\f0d7";
+ }
+ }
+ &.o-sort-down {
+ cursor: s-resize;
+ &::after {
+ content: "\f0d8";
+ }
+ }
+ &:hover::after {
+ opacity: 0.3;
+ }
+ &.o-sort-up, &.o-sort-down {
+ color: $headings-color;
+ &::after {
+ opacity: 1;
}
}
}
- }
- tbody + tbody {
- border-top: none; // Override bootstrap for grouped list views
- }
- &, &.table-striped {
- tr:focus-within {
- background-color: lighten($o-form-lightsecondary, 10%);
- }
- }
- thead th:focus-within, tbody td:focus-within {
- background-color: $o-form-lightsecondary;
- outline: none;
- }
-
- .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'.
- display: table-cell!important;
- }
-
- //--------------------------------------------------------------------------
- // Edition
- //--------------------------------------------------------------------------
-
- &.o_editable_list {
- table-layout: fixed;
-
- .o_list_record_selector {
- width: 40px;
- }
- .o_list_record_remove_header, .o_handle_cell {
- width: 32px;
+ .o_list_record_selector, .o_list_record_remove, .o_handle_cell {
+ width: 1px; // to prevent the column to expand
}
- // checkboxes in editable lists should be clickable and activate the row
- .o_data_row:not(.o_selected_row) .o_data_cell {
- .custom-checkbox:not(.o_readonly_modifier) {
- pointer-events: none;
+ .o_list_record_remove button {
+ padding: 0px;
+ background: none;
+ border-style: none;
+ display: table-cell;
+ cursor: pointer;
+ }
+
+ // Contextual classes
+ @each $color, $value in $theme-colors {
+ $safe: saturate(theme-color-level($color, 2), 10%);
+ @include text-emphasis-variant(".text-#{$color}", $safe);
+ }
+
+ // Grouped list views
+ tbody > tr.o_group_header {
+ > th, > td {
+ vertical-align: middle;
+ padding-top: ($table-cell-padding-sm*2);
+ padding-bottom: ($table-cell-padding-sm*2);
}
- }
-
- .o_data_row.o_selected_row > .o_data_cell:not(.o_readonly_modifier) {
- position: relative; // for o_field_translate
- background-color: white;
-
- .o_input {
- border: none;
- padding: 0;
+ .o_group_name {
+ white-space: nowrap;
}
- > .o_field_text {
- vertical-align: top;
- }
- > .o_field_widget {
- width: 100%;
- > .o_external_button {
- padding: 0;
+ .o_group_buttons {
+ display: inline-block;
+ margin-left: 10px;
+
+ > button {
+ @include o-hover-text-color($o-brand-primary, darken($o-brand-primary, 20%));
+ background-color: transparent;
border: none;
- background-color: inherit;
- margin-left: 5px;
- font-size: 12px;
+ padding-top: 0;
+ padding-bottom: 0;
}
}
- > .o_field_monetary input {
- width: 0; // override width: 100px and let flex makes this input grow
- margin: 0 4px;
- }
- > .o_field_many2manytags > .o_field_many2one {
- // reduce basis to prevent many2one input from going to the next line to often
- flex-basis: 40px;
- }
- .o_input_dropdown, .o_datepicker {
- > .o_input {
- padding-right: 15px; // to prevent caret overlapping
+ .o_group_pager {
+ position: relative;
+
+ .o_pager {
+ @include o-position-absolute(0, 16px);
+ cursor: text;
+ user-select: none;
+ padding-top: 3px !important;
+ padding-bottom: 0 !important;
}
- > .o_dropdown_button, .o_datepicker_button {
- @include o-position-absolute(0, 0);
+ .o_pager_previous, .o_pager_next {
+ max-height: 30px;
+ background-color: $o-list-group-header-color;
+ &:hover {
+ background-color: darken($o-list-group-header-color, 10%);
+ }
}
}
- > .o_field_translate {
- @include o-position-absolute(0, 5px);
- width: 16px; // to have some space on the right
+ }
+ tbody + tbody {
+ border-top: none; // Override bootstrap for grouped list views
+ }
+
+ &, &.table-striped {
+ tr:focus-within {
+ background-color: lighten($o-form-lightsecondary, 10%);
}
- > .o_row_handle {
- visibility: hidden; // hide sequence when editing
+ }
+ thead th:focus-within, tbody td:focus-within {
+ background-color: $o-form-lightsecondary;
+ outline: none;
+ }
+
+ .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'.
+ display: table-cell!important;
+ }
+
+ //--------------------------------------------------------------------------
+ // Edition
+ //--------------------------------------------------------------------------
+
+ &.o_editable_list {
+ table-layout: fixed;
+
+ .o_list_record_selector {
+ width: 40px;
+ }
+ .o_list_record_remove_header, .o_handle_cell {
+ width: 32px;
+ }
+
+ // checkboxes in editable lists should be clickable and activate the row
+ .o_data_row:not(.o_selected_row) .o_data_cell {
+ .custom-checkbox:not(.o_readonly_modifier) {
+ pointer-events: none;
+ }
+ }
+
+ .o_data_row.o_selected_row > .o_data_cell:not(.o_readonly_modifier) {
+ position: relative; // for o_field_translate
+ background-color: white;
+
+ .o_input {
+ border: none;
+ padding: 0;
+ }
+ > .o_field_text {
+ vertical-align: top;
+ }
+ > .o_field_widget {
+ width: 100%;
+ > .o_external_button {
+ padding: 0;
+ border: none;
+ background-color: inherit;
+ margin-left: 5px;
+ font-size: 12px;
+ }
+ }
+ > .o_field_monetary input {
+ width: 0; // override width: 100px and let flex makes this input grow
+ margin: 0 4px;
+ }
+ > .o_field_many2manytags > .o_field_many2one {
+ // reduce basis to prevent many2one input from going to the next line to often
+ flex-basis: 40px;
+ }
+ .o_input_dropdown, .o_datepicker {
+ > .o_input {
+ padding-right: 15px; // to prevent caret overlapping
+ }
+ > .o_dropdown_button, .o_datepicker_button {
+ @include o-position-absolute(0, 0);
+ }
+ }
+ > .o_field_translate {
+ @include o-position-absolute(0, 5px);
+ width: 16px; // to have some space on the right
+ }
+ > .o_row_handle {
+ visibility: hidden; // hide sequence when editing
+ }
+ }
+ }
+
+ .o_field_x2many_list_row_add, .o_group_field_row_add {
+ a:focus, a:active {
+ color: $link-hover-color;
+ outline: none;
}
}
}
- .o_field_x2many_list_row_add, .o_group_field_row_add {
- a:focus, a:active {
- color: $link-hover-color;
- outline: none;
+ // Optional fields
+ &.o_list_optional_columns {
+ th:last-child {
+ padding-right: 15px;
+ }
+ }
+
+ .o_optional_columns_dropdown_toggle,.o_optional_columns {
+ @include o-position-absolute($top: 0, $right: 0);
+ }
+
+ .o_optional_columns_dropdown_toggle {
+ width: 10px;
+ text-align: center;
+ line-height: 30px;
+ }
+
+ .o_optional_columns {
+ .o_optional_columns_dropdown {
+ margin-top: 30px;
+ user-select: none;
+ .dropdown-item {
+ label {
+ padding-left: 10px;
+ }
+ }
}
}
}
diff --git a/addons/web/static/tests/chrome/action_manager_tests.js b/addons/web/static/tests/chrome/action_manager_tests.js
index b14ccefb447..7ac817af30c 100644
--- a/addons/web/static/tests/chrome/action_manager_tests.js
+++ b/addons/web/static/tests/chrome/action_manager_tests.js
@@ -2889,7 +2889,7 @@ QUnit.module('ActionManager', {
});
await actionManager.doAction(3);
- assert.doesNotHaveClass(actionManager.$('.o_list_view'), 'o_list_view_grouped',
+ assert.doesNotHaveClass(actionManager.$('.o_list_table'), 'o_list_table_grouped',
"list view is not grouped");
// open group by dropdown
@@ -2898,7 +2898,7 @@ QUnit.module('ActionManager', {
// click on first link
await testUtils.dom.click($('.o_control_panel .o_group_by_menu a:first'));
- assert.hasClass(actionManager.$('.o_list_view'),'o_list_view_grouped',
+ assert.hasClass(actionManager.$('.o_list_table'),'o_list_table_grouped',
'list view is now grouped');
actionManager.destroy();
@@ -3110,7 +3110,7 @@ QUnit.module('ActionManager', {
});
await actionManager.doAction(3);
- assert.containsOnce(actionManager, '.o_list_view_grouped',
+ assert.containsOnce(actionManager, '.o_list_table_grouped',
"should be grouped");
assert.containsN(actionManager, '.o_group_header', 2,
"should be grouped by 'bar' (two groups) at first load");
@@ -3125,7 +3125,7 @@ QUnit.module('ActionManager', {
// remove the groupby in the searchview
await testUtils.dom.click($('.o_control_panel .o_searchview .o_facet_remove'));
- assert.containsOnce(actionManager, '.o_list_view_grouped',
+ assert.containsOnce(actionManager, '.o_list_table_grouped',
"should still be grouped");
assert.containsN(actionManager, '.o_group_header', 2,
"should be grouped by 'bar' (two groups) at reload");
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index 6f72b827cae..aec23c150bc 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -1,8 +1,10 @@
odoo.define('web.relational_fields_tests', function (require) {
"use strict";
+var AbstractStorageService = require('web.AbstractStorageService');
var FormView = require('web.FormView');
var ListView = require('web.ListView');
+var RamStorage = require('web.RamStorage');
var relationalFields = require('web.relational_fields');
var testUtils = require('web.test_utils');
@@ -2888,6 +2890,81 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('one2many field in edit mode with optional fields and trash icon', async function (assert) {
+ assert.expect(13);
+
+ var RamStorageService = AbstractStorageService.extend({
+ storage: new RamStorage(),
+ });
+
+ this.data.partner.records[0].p = [2];
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '
',
+ res_id: 1,
+ archs: {
+ 'partner,false,list': '
' +
+ '' +
+ '' +
+ '',
+ },
+ services: {
+ local_storage: RamStorageService,
+ },
+ });
+
+ // should have 2 columns 1 for foo and 1 for advanced dropdown
+ assert.containsN(form.$('.o_field_one2many'), 'th', 1,
+ "should be 1 th in the one2many in readonly mode");
+ assert.containsOnce(form.$('.o_field_one2many table'), '.o_optional_columns_dropdown_toggle',
+ "should have the optional columns dropdown toggle inside the table");
+ await testUtils.form.clickEdit(form);
+ // should have 2 columns 1 for foo and 1 for trash icon, dropdown is displayed
+ // on trash icon cell, no separate cell created for trash icon and advanced field dropdown
+ assert.containsN(form.$('.o_field_one2many'), 'th', 2,
+ "should be 2 th in the one2many edit mode");
+ assert.containsN(form.$('.o_field_one2many'), '.o_data_row:first > td', 2,
+ "should be 2 cells in the one2many in edit mode");
+
+ await testUtils.dom.click(form.$('.o_field_one2many table .o_optional_columns_dropdown_toggle'));
+ assert.containsN(form.$('.o_field_one2many'), 'div.o_optional_columns div.dropdown-item:visible', 2,
+ "dropdown have 2 advanced field foo with checked and bar with unchecked");
+ await testUtils.dom.click(form.$('div.o_optional_columns div.dropdown-item:eq(1) input'));
+ assert.containsN(form.$('.o_field_one2many'), 'th', 3,
+ "should be 3 th in the one2many after enabling bar column from advanced dropdown");
+
+ await testUtils.dom.click(form.$('div.o_optional_columns div.dropdown-item:first input'));
+ assert.containsN(form.$('.o_field_one2many'), 'th', 2,
+ "should be 2 th in the one2many after disabling foo column from advanced dropdown");
+
+ assert.containsN(form.$('.o_field_one2many'), 'div.o_optional_columns div.dropdown-item:visible', 2,
+ "dropdown is still open");
+ await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
+ assert.containsN(form.$('.o_field_one2many'), 'div.o_optional_columns div.dropdown-item:visible', 0,
+ "dropdown is closed");
+ var $selectedRow = form.$('.o_field_one2many tr.o_selected_row');
+ assert.strictEqual($selectedRow.length, 1, "should have selected row i.e. edition mode");
+
+ await testUtils.dom.click(form.$('.o_field_one2many table .o_optional_columns_dropdown_toggle'));
+ await testUtils.dom.click(form.$('div.o_optional_columns div.dropdown-item:first input'));
+ $selectedRow = form.$('.o_field_one2many tr.o_selected_row');
+ assert.strictEqual($selectedRow.length, 0,
+ "current edition mode discarded when selecting advanced field");
+ assert.containsN(form.$('.o_field_one2many'), 'th', 3,
+ "should be 3 th in the one2many after re-enabling foo column from advanced dropdown");
+
+ // check after form reload advanced column hidden or shown are still preserved
+ await form.reload();
+ assert.containsN(form.$('.o_field_one2many .o_list_view'), 'th', 3,
+ "should still have 3 th in the one2many after reloading whole form view");
+
+ form.destroy();
+ });
+
QUnit.module('TabNavigation');
QUnit.test('when Navigating to a many2one with tabs, it receives the focus and adds a new line', async function (assert) {
assert.expect(3);
diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js
index 02121d01c5e..7a357d9b345 100644
--- a/addons/web/static/tests/views/form_tests.js
+++ b/addons/web/static/tests/views/form_tests.js
@@ -245,18 +245,18 @@ QUnit.module('Views', {
});
assert.containsOnce(form, 'div.o_field_one2many:eq(0):not(.o_readonly_modifier)', "first one2many widget should not be readonly");
assert.hasClass(form.$('div.o_field_one2many:eq(1)'),'o_readonly_modifier', "second one2many widget should be readonly");
- await testUtils.dom.click(form.$('div.tab-content table.o_list_view:eq(0) tr.o_data_row td.o_data_cell:eq(0)'));
- assert.strictEqual(form.$('div.tab-content table.o_list_view tr.o_selected_row input[name="foo"]').val(), "yop",
+ await testUtils.dom.click(form.$('div.tab-content table.o_list_table:eq(0) tr.o_data_row td.o_data_cell:eq(0)'));
+ assert.strictEqual(form.$('div.tab-content table.o_list_table tr.o_selected_row input[name="foo"]').val(), "yop",
"first line in one2many of first tab contains yop");
- assert.strictEqual(form.$('div.tab-content table.o_list_view:eq(1) tr.o_data_row td.o_data_cell:eq(0)').text(),
+ assert.strictEqual(form.$('div.tab-content table.o_list_table:eq(1) tr.o_data_row td.o_data_cell:eq(0)').text(),
"yop", "first line in one2many of second tab contains yop");
- await testUtils.fields.editInput(form.$('div.tab-content table.o_list_view tr.o_selected_row input[name="foo"]'), "hello");
- assert.strictEqual(form.$('div.tab-content table.o_list_view:eq(1) tr.o_data_row td.o_data_cell:eq(0)').text(), "hello",
+ await testUtils.fields.editInput(form.$('div.tab-content table.o_list_table tr.o_selected_row input[name="foo"]'), "hello");
+ assert.strictEqual(form.$('div.tab-content table.o_list_table:eq(1) tr.o_data_row td.o_data_cell:eq(0)').text(), "hello",
"first line in one2many of second tab contains hello");
- await testUtils.dom.click(form.$('div.tab-content table.o_list_view:eq(0) a:contains(Add a line)'));
- assert.strictEqual(form.$('div.tab-content table.o_list_view tr.o_selected_row input[name="foo"]').val(), "My little Foo Value",
+ await testUtils.dom.click(form.$('div.tab-content table.o_list_table:eq(0) a:contains(Add a line)'));
+ assert.strictEqual(form.$('div.tab-content table.o_list_table tr.o_selected_row input[name="foo"]').val(), "My little Foo Value",
"second line in one2many of first tab contains 'My little Foo Value'");
- assert.strictEqual(form.$('div.tab-content table.o_list_view:eq(1) tr.o_data_row:eq(1) td.o_data_cell:eq(0)').text(),
+ assert.strictEqual(form.$('div.tab-content table.o_list_table:eq(1) tr.o_data_row:eq(1) td.o_data_cell:eq(0)').text(),
"My little Foo Value", "first line in one2many of second tab contains hello");
form.destroy();
});
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index bbc6cf763cf..fa62c6f3a25 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -1,6 +1,7 @@
odoo.define('web.list_tests', function (require) {
"use strict";
+var AbstractStorageService = require('web.AbstractStorageService');
var BasicModel = require('web.BasicModel');
var config = require('web.config');
var core = require('web.core');
@@ -9,6 +10,7 @@ var FormView = require('web.FormView');
var ListView = require('web.ListView');
var mixins = require('web.mixins');
var NotificationService = require('web.NotificationService');
+var RamStorage = require('web.RamStorage');
var testUtils = require('web.test_utils');
var widgetRegistry = require('web.widget_registry');
var Widget = require('web.Widget');
@@ -2295,7 +2297,7 @@ QUnit.module('Views', {
});
await testUtils.dom.click(list.$buttons.find('.o_list_button_add'));
- await testUtils.dom.click(list.$('div.table-responsive'));
+ await testUtils.dom.click(list.$('.o_list_view'));
assert.strictEqual(createCount, 1, "should have created a record");
@@ -6134,6 +6136,302 @@ QUnit.module('Views', {
list.destroy();
});
+ QUnit.test('list view with optional fields rendering', async function (assert) {
+ assert.expect(9);
+
+ var RamStorageService = AbstractStorageService.extend({
+ storage: new RamStorage(),
+ });
+
+ var list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '',
+ services: {
+ local_storage: RamStorageService,
+ },
+ });
+
+ assert.containsN(list, 'th', 3,
+ "should have 3 th, 1 for selector, 2 for columns");
+
+ assert.containsOnce(list.$('table'), '.o_optional_columns_dropdown_toggle',
+ "should have the optional columns dropdown toggle inside the table");
+
+ // optional fields
+ await testUtils.dom.click(list.$('table .o_optional_columns_dropdown_toggle'));
+ assert.containsN(list, 'div.o_optional_columns div.dropdown-item', 2,
+ "dropdown have 2 optional field foo with checked and bar with unchecked");
+
+ // enable optional field
+ await testUtils.dom.click(list.$('div.o_optional_columns div.dropdown-item:first input'));
+ // 5 th (1 for checkbox, 4 for columns)
+ assert.containsN(list, 'th', 4, "should have 4 th");
+ assert.ok(list.$('th:contains(M2O field)').is(':visible'),
+ "should have a visible m2o field"); //m2o field
+
+ // disable optional field
+ await testUtils.dom.click(list.$('table .o_optional_columns_dropdown_toggle'));
+ assert.strictEqual(list.$('div.o_optional_columns div.dropdown-item:first input:checked')[0],
+ list.$('div.o_optional_columns div.dropdown-item [name="m2o"]')[0],
+ "m2o advanced field check box should be checked in dropdown");
+
+ await testUtils.dom.click(list.$('div.o_optional_columns div.dropdown-item:first input'));
+ // 3 th (1 for checkbox, 2 for columns)
+ assert.containsN(list, 'th', 3, "should have 3 th");
+ assert.notOk(list.$('th:contains(M2O field)').is(':visible'),
+ "should not have a visible m2o field"); //m2o field not displayed
+
+ await testUtils.dom.click(list.$('table .o_optional_columns_dropdown_toggle'));
+ assert.notOk(list.$('div.o_optional_columns div.dropdown-item [name="m2o"]').is(":checked"));
+
+ list.destroy();
+ });
+
+ QUnit.test('optinal fields do not disappear even after listview reload', async function (assert) {
+ assert.expect(7);
+
+ var RamStorageService = AbstractStorageService.extend({
+ storage: new RamStorage(),
+ });
+
+ var list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '',
+ services: {
+ local_storage: RamStorageService,
+ },
+ });
+
+ assert.containsN(list, 'th', 3,
+ "should have 3 th, 1 for selector, 2 for columns");
+
+ // enable optional field
+ await testUtils.dom.click(list.$('table .o_optional_columns_dropdown_toggle'));
+ assert.notOk(list.$('div.o_optional_columns div.dropdown-item [name="m2o"]').is(":checked"));
+ await testUtils.dom.click(list.$('div.o_optional_columns div.dropdown-item:first input'));
+ assert.containsN(list, 'th', 4,
+ "should have 4 th 1 for selector, 3 for columns");
+ assert.ok(list.$('th:contains(M2O field)').is(':visible'),
+ "should have a visible m2o field"); //m2o field
+
+ // reload listview
+ await list.reload();
+ assert.containsN(list, 'th', 4,
+ "should have 4 th 1 for selector, 3 for columns ever after listview reload");
+ assert.ok(list.$('th:contains(M2O field)').is(':visible'),
+ "should have a visible m2o field even after listview reload");
+
+ await testUtils.dom.click(list.$('table .o_optional_columns_dropdown_toggle'));
+ assert.ok(list.$('div.o_optional_columns div.dropdown-item [name="m2o"]').is(":checked"));
+
+ list.destroy();
+ });
+
+ QUnit.test('change the viewType of the current action', async function (assert) {
+ assert.expect(25);
+
+ this.actions = [{
+ id: 1,
+ name: 'Partners Action 1',
+ res_model: 'foo',
+ type: 'ir.actions.act_window',
+ views: [[1, 'kanban']],
+ }, {
+ id: 2,
+ name: 'Partners',
+ res_model: 'foo',
+ type: 'ir.actions.act_window',
+ views: [[false, 'list'], [1, 'kanban']],
+ }];
+
+ this.archs = {
+ 'foo,1,kanban': '
' +
+ '
' +
+ '',
+
+ 'foo,false,list': '
' +
+ '' +
+ '' +
+ '',
+
+ 'foo,false,search': '
',
+ };
+
+ var RamStorageService = AbstractStorageService.extend({
+ storage: new RamStorage(),
+ });
+
+ var actionManager = await testUtils.createActionManager({
+ actions: this.actions,
+ archs: this.archs,
+ data: this.data,
+ services: {
+ local_storage: RamStorageService,
+ },
+ });
+ await actionManager.doAction(2);
+
+ assert.containsOnce(actionManager, '.o_list_view',
+ "should have rendered a list view");
+
+ assert.containsN(actionManager, 'th', 3, "should display 3 th (selector + 2 fields)");
+
+ // enable optional field
+ await testUtils.dom.click(actionManager.$('table .o_optional_columns_dropdown_toggle'));
+ assert.notOk(actionManager.$('div.o_optional_columns div.dropdown-item [name="m2o"]').is(":checked"));
+ assert.ok(actionManager.$('div.o_optional_columns div.dropdown-item [name="o2m"]').is(":checked"));
+ await testUtils.dom.click(actionManager.$('div.o_optional_columns div.dropdown-item:first'));
+ assert.containsN(actionManager, 'th', 4, "should display 4 th (selector + 3 fields)");
+ assert.ok(actionManager.$('th:contains(M2O field)').is(':visible'),
+ "should have a visible m2o field"); //m2o field
+
+ // switch to kanban view
+ await actionManager.loadState({
+ action: 2,
+ view_type: 'kanban',
+ });
+
+ assert.containsNone(actionManager, '.o_list_view',
+ "should not display the list view anymore");
+ assert.containsOnce(actionManager, '.o_kanban_view',
+ "should have switched to the kanban view");
+
+ // switch back to list view
+ await actionManager.loadState({
+ action: 2,
+ view_type: 'list',
+ });
+
+ assert.containsNone(actionManager, '.o_kanban_view',
+ "should not display the kanban view anymoe");
+ assert.containsOnce(actionManager, '.o_list_view',
+ "should display the list view");
+
+ assert.containsN(actionManager, 'th', 4, "should display 4 th");
+ assert.ok(actionManager.$('th:contains(M2O field)').is(':visible'),
+ "should have a visible m2o field"); //m2o field
+ assert.ok(actionManager.$('th:contains(O2M field)').is(':visible'),
+ "should have a visible o2m field"); //m2o field
+
+ // disable optional field
+ await testUtils.dom.click(actionManager.$('table .o_optional_columns_dropdown_toggle'));
+ assert.ok(actionManager.$('div.o_optional_columns div.dropdown-item [name="m2o"]').is(":checked"));
+ assert.ok(actionManager.$('div.o_optional_columns div.dropdown-item [name="o2m"]').is(":checked"));
+ await testUtils.dom.click(actionManager.$('div.o_optional_columns div.dropdown-item:last input'));
+ assert.ok(actionManager.$('th:contains(M2O field)').is(':visible'),
+ "should have a visible m2o field"); //m2o field
+ assert.notOk(actionManager.$('th:contains(O2M field)').is(':visible'),
+ "should have a visible o2m field"); //m2o field
+ assert.containsN(actionManager, 'th', 3, "should display 3 th");
+
+ await actionManager.doAction(1);
+
+ assert.containsNone(actionManager, '.o_list_view',
+ "should not display the list view anymore");
+ assert.containsOnce(actionManager, '.o_kanban_view',
+ "should have switched to the kanban view");
+
+ await actionManager.doAction(2);
+
+ assert.containsNone(actionManager, '.o_kanban_view',
+ "should not havethe kanban view anymoe");
+ assert.containsOnce(actionManager, '.o_list_view',
+ "should display the list view");
+
+ assert.containsN(actionManager, 'th', 3, "should display 3 th");
+ assert.ok(actionManager.$('th:contains(M2O field)').is(':visible'),
+ "should have a visible m2o field"); //m2o field
+ assert.notOk(actionManager.$('th:contains(O2M field)').is(':visible'),
+ "should have a visible o2m field"); //m2o field
+
+ actionManager.destroy();
+ });
+
+ QUnit.test('list view with optional fields rendering and local storage mock', async function (assert) {
+ assert.expect(12);
+
+ var forceLocalStorage = true;
+
+ var Storage = RamStorage.extend({
+ getItem: function (key) {
+ assert.step('getItem ' + key);
+ return forceLocalStorage ? '["m2o"]' : this._super.apply(this, arguments);
+ },
+ setItem: function (key, value) {
+ assert.step('setItem ' + key + ' to ' + value);
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ var RamStorageService = AbstractStorageService.extend({
+ storage: new Storage(),
+ });
+
+ var list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '
' +
+ '' +
+ '' +
+ '' +
+ '',
+ services: {
+ local_storage: RamStorageService,
+ },
+ });
+
+ assert.verifySteps(['getItem list_optional_fields,foo,list,foo:char,m2o:many2one,reference:reference']);
+
+ assert.containsN(list, 'th', 3,
+ "should have 3 th, 1 for selector, 2 for columns");
+
+ assert.ok(list.$('th:contains(M2O field)').is(':visible'),
+ "should have a visible m2o field"); //m2o field
+
+ assert.notOk(list.$('th:contains(Reference Field)').is(':visible'),
+ "should not have a visible reference field");
+
+ // optional fields
+ await testUtils.dom.click(list.$('table .o_optional_columns_dropdown_toggle'));
+ assert.containsN(list, 'div.o_optional_columns div.dropdown-item', 2,
+ "dropdown have 2 optional fields");
+
+ forceLocalStorage = false;
+ // enable optional field
+ await testUtils.dom.click(list.$('div.o_optional_columns div.dropdown-item:eq(1) input'));
+
+ assert.verifySteps([
+ 'setItem list_optional_fields,foo,list,foo:char,m2o:many2one,reference:reference to ["m2o","reference"]',
+ 'getItem list_optional_fields,foo,list,foo:char,m2o:many2one,reference:reference',
+ ]);
+
+ // 4 th (1 for checkbox, 3 for columns)
+ assert.containsN(list, 'th', 4, "should have 4 th");
+
+ assert.ok(list.$('th:contains(M2O field)').is(':visible'),
+ "should have a visible m2o field"); //m2o field
+
+ assert.ok(list.$('th:contains(Reference Field)').is(':visible'),
+ "should have a visible reference field");
+
+ list.destroy();
+ });
+
// TODO: write test on:
// - default_get with a field not in view
});
diff --git a/addons/web/static/tests/views/view_dialogs_tests.js b/addons/web/static/tests/views/view_dialogs_tests.js
index 847bbd0f3be..558caeeaf37 100644
--- a/addons/web/static/tests/views/view_dialogs_tests.js
+++ b/addons/web/static/tests/views/view_dialogs_tests.js
@@ -350,7 +350,7 @@ QUnit.module('Views', {
'There should be two modals');
var $second_modal = $modals.not($modal);
- await testUtils.dom.click($second_modal.find('.o_list_view.table.table-sm.table-striped.o_list_view_ungrouped .o_data_row input[type=checkbox]'));
+ await testUtils.dom.click($second_modal.find('.o_list_table.table.table-sm.table-striped.o_list_table_ungrouped .o_data_row input[type=checkbox]'));
await testUtils.dom.click($second_modal.find('.o_select_button'));
diff --git a/odoo/addons/base/rng/common.rng b/odoo/addons/base/rng/common.rng
index 8a2b9f6fbc1..66ca9a6135b 100644
--- a/odoo/addons/base/rng/common.rng
+++ b/odoo/addons/base/rng/common.rng
@@ -261,6 +261,7 @@
+
diff --git a/odoo/addons/test_main_flows/static/src/js/tour.js b/odoo/addons/test_main_flows/static/src/js/tour.js
index 5113a81dc6e..f13cfb64e44 100644
--- a/odoo/addons/test_main_flows/static/src/js/tour.js
+++ b/odoo/addons/test_main_flows/static/src/js/tour.js
@@ -152,7 +152,7 @@ tour.register('main_flow_tour', {
{
// Add second component
trigger: ".o_field_x2many_list_row_add > a",
- extra_trigger: "body:not(:has(table.o_list_view div.o_field_widget[name='product_id'] input))",
+ extra_trigger: "body:not(:has(table.o_list_table div.o_field_widget[name='product_id'] input))",
content: _t("Click here to add some lines."),
position: "bottom",
},