From 57dbfff2db9053276a293d9a20bd8636f10100cc Mon Sep 17 00:00:00 2001 From: Arnaud Joset Date: Wed, 30 Jun 2021 10:37:33 +0000 Subject: [PATCH] [IMP] web: improve calendar view Before this commit, the calendar filter was a little bit limited: you could add filters to only display events related to these filtered records or display all events. If you wanted to select all active filters, we had to check all these individual filter. This commit introduce the possibility to select all existing filter at once or to unselect them. It is quite convenient when a lot of filter are displayed but unchecked. Taskid: 2484335 Part-of: odoo/odoo#68700 --- .../calendar/static/src/xml/base_calendar.xml | 2 +- .../js/views/calendar/calendar_controller.js | 61 +++++++++---------- .../js/views/calendar/calendar_model.js | 39 +++++++----- .../js/views/calendar/calendar_popover.js | 19 +++++- .../views/calendar/calendar_quick_create.js | 2 + .../js/views/calendar/calendar_renderer.js | 39 +++++++++++- .../legacy/js/views/calendar/calendar_view.js | 4 ++ .../static/src/legacy/scss/web_calendar.scss | 39 ++++++++---- .../static/src/legacy/xml/web_calendar.xml | 32 +++++++--- .../tests/legacy/views/calendar_tests.js | 37 +++++++---- 10 files changed, 192 insertions(+), 82 deletions(-) diff --git a/addons/calendar/static/src/xml/base_calendar.xml b/addons/calendar/static/src/xml/base_calendar.xml index 6c8a3c0a332..2630988340c 100644 --- a/addons/calendar/static/src/xml/base_calendar.xml +++ b/addons/calendar/static/src/xml/base_calendar.xml @@ -69,7 +69,7 @@ -
+
diff --git a/addons/web/static/src/legacy/js/views/calendar/calendar_controller.js b/addons/web/static/src/legacy/js/views/calendar/calendar_controller.js index 9f9f348f263..1bd80d1e07f 100644 --- a/addons/web/static/src/legacy/js/views/calendar/calendar_controller.js +++ b/addons/web/static/src/legacy/js/views/calendar/calendar_controller.js @@ -248,9 +248,12 @@ var CalendarController = AbstractController.extend({ * @param {OdooEvent} event */ _onChangeFilter: async function (event) { - if (event.data.value !== "all" && event.target.filter_field) { - const domain = [['user_id', '=', this.getSession().uid], [event.target.write_field, '=', parseInt(event.data.value)]]; - const contact = await this._rpc({ + if (event.data.value !== 'all' && event.target.filter_field) { + const domain = [['user_id', '=', this.getSession().uid]]; + if (event.data.value !== 'check_all') { + domain.push([event.target.write_field, '=', parseInt(event.data.value)]); + } + const existingFilter = await this._rpc({ model: event.target.write_model, method: 'search', args: [domain], @@ -261,7 +264,7 @@ var CalendarController = AbstractController.extend({ await this._rpc({ model: event.target.write_model, method: 'write', - args: [contact, val], + args: [existingFilter, val], }); } if (this.model.changeFilter(event.data) && !event.data.no_reload) { @@ -275,34 +278,32 @@ var CalendarController = AbstractController.extend({ _onDeleteRecord: async function (event) { var self = this; if (event.data.event.record.recurrency) { - const recurrenceUpdate = await this._askRecurrenceUpdatePolicy(); - event.data = _.extend({}, event.data, { - 'recurrenceUpdate': recurrenceUpdate, - }); - if (recurrenceUpdate === 'self_only') { - self.model.deleteRecords([event.data.id], self.modelName).then(function () { - self.reload(); - }); - } else { - return this._rpc({ - model: self.modelName, - method: 'action_mass_deletion', - args: [[event.data.id], recurrenceUpdate], - }).then( function () { - self.reload(); - }); - } - } else { - Dialog.confirm(this, _t("Are you sure you want to delete this record ?"), { - confirm_callback: function () { + const recurrenceUpdate = await this._askRecurrenceUpdatePolicy(); + event.data = Object.assign({}, event.data, { + 'recurrenceUpdate': recurrenceUpdate, + }); + if (recurrenceUpdate === 'self_only') { self.model.deleteRecords([event.data.id], self.modelName).then(function () { + self.reload(); + }); + } else { + return this._rpc({ + model: self.modelName, + method: 'action_mass_deletion', + args: [[event.data.id], recurrenceUpdate], + }).then( function () { self.reload(); }); } - }); + } else { + Dialog.confirm(this, _t("Are you sure you want to delete this record ?"), { + confirm_callback: function () { + self.model.deleteRecords([event.data.id], self.modelName).then(function () { + self.reload(); + }); + } + }); } - - }, /** * @private @@ -517,14 +518,12 @@ var CalendarController = AbstractController.extend({ */ _onAttendeeStatus: async function(event) { const self = this; - let recurrenceUpdate; + let recurrenceUpdate = false; if (event.data.record.recurrency) { recurrenceUpdate = await this._askRecurrenceUpdatePolicy(); - event.data = _.extend({}, event.data, { + event.data = Object.assign({}, event.data, { 'recurrenceUpdate': recurrenceUpdate, }); - } else { - recurrenceUpdate = false; } return this._rpc({ model: self.modelName, diff --git a/addons/web/static/src/legacy/js/views/calendar/calendar_model.js b/addons/web/static/src/legacy/js/views/calendar/calendar_model.js index 34e504d2fc3..a87de95e481 100644 --- a/addons/web/static/src/legacy/js/views/calendar/calendar_model.js +++ b/addons/web/static/src/legacy/js/views/calendar/calendar_model.js @@ -24,6 +24,7 @@ return AbstractModel.extend({ // calendar uses index 0 for Sunday but Odoo stores it as 7 this.week_start = week_start !== undefined && week_start !== false ? week_start % 7 : moment().startOf('week').day(); this.week_stop = this.week_start + 6; + this.filter_check_all = {}; }, //-------------------------------------------------------------------------- @@ -115,13 +116,30 @@ return AbstractModel.extend({ if (filter.value === 'all') { Filter.all = filter.active; } + else if (filter.value === 'check_all') { + // Select all numeric filters and update the checkboxes + this.filter_check_all[filter.fieldName] = filter.active; + // Deactivate the All selection + Filter.all = false; + if (filter.active) { + // Check every displayed filters except the 'all' filter + Filter.filters.filter(el => el.value !== 'all').forEach(element => element.active = true ); + return true; + + } else { + Filter.filters.forEach(element => element.active = false); + return true; + } + } var f = _.find(Filter.filters, function (f) { return f.value === filter.value; }); if (f) { if (f.active !== filter.active) { + // Update the value according to the checkbox state f.active = filter.active; - } else { + } else if (filter.active) { + // if the filter is NOT active, we remove it by returning true [triggered by _onFilterRemove] return false; } } else if (filter.active) { @@ -150,8 +168,6 @@ return AbstractModel.extend({ }); }, /** - * @todo I think this is dead code - * * @param {any} ids * @param {any} model * @returns @@ -384,6 +400,8 @@ return AbstractModel.extend({ _.each(this.data.filters, function (filter) { // Skip 'all' filters because they do not affect the domain if (filter.all) return; + // all visible filters have been activated + if (filter.visible) return; // Loop over subfilters to complete authorizedValues _.each(filter.filters, function (f) { @@ -518,18 +536,9 @@ return AbstractModel.extend({ _loadColors: function (element, events) { if (this.fieldColor) { var fieldName = this.fieldColor; - var self = this; _.each(events, function (event) { var value = event.record[fieldName]; - var filter = self.loadParams.filters[fieldName]; - var color = filter && _.map(filter.filters, f => f.value) || value; - var everyoneFilter = filter && (_.find(filter.filters, f => f.value === "all") || {}).active || false; - if (!everyoneFilter) { - color = [event.attendee_id || value] - } else { - color = value.includes(self.getSession().partner_id) ? self.getSession().partner_id : value - } - event.color_index = _.isArray(color) ? self._getColorIndex(value, color[0]) : color; + event.color_index = _.isArray(value) ? value[0] : value; }); this.model_color = this.fields[fieldName].relation || element.model; } @@ -539,7 +548,7 @@ return AbstractModel.extend({ * Get the color index used to render the event and the filter. * Since we have a maximum of 30 colors, we have to adapt the color * index in case the id exceeds the number of colors available. - * + * * - actual_color_list contains the list of color already used in the filter * - color_index is the color we try to apply for the event * @@ -550,7 +559,7 @@ return AbstractModel.extend({ */ _getColorIndex: function(actual_color_list, color_index) { var actual_color_list = actual_color_list || [0]; - var new_color_index = color_index % 30; + var new_color_index = color_index % 50; if (new_color_index !== color_index && actual_color_list.includes(new_color_index)) { new_color_index = this._getColorIndex(actual_color_list, new_color_index + 3); } diff --git a/addons/web/static/src/legacy/js/views/calendar/calendar_popover.js b/addons/web/static/src/legacy/js/views/calendar/calendar_popover.js index f7e2fa7a53f..adec67eea64 100644 --- a/addons/web/static/src/legacy/js/views/calendar/calendar_popover.js +++ b/addons/web/static/src/legacy/js/views/calendar/calendar_popover.js @@ -205,9 +205,26 @@ var CalendarPopover = Widget.extend(WidgetAdapterMixin, StandaloneFieldManagerMi const fieldModifier = record.evalModifiers(_.pick(fieldWidget.attrs.modifiers, 'invisible')); fieldClass += fieldModifier.invisible ? ' o_invisible_modifier' : ''; } + if (fieldWidget.attrs) { + if (fieldWidget.attrs.options && fieldWidget.attrs.options.icon) { + field.icon = fieldWidget.attrs.options.icon; + } + if (fieldWidget.attrs.class) { + fieldClass += ' ' + fieldWidget.attrs.class; + } + if (fieldWidget.attrs.modifiers) { + const fieldModifier = record.evalModifiers(fieldWidget.attrs.modifiers); + fieldClass += fieldModifier.invisible ? ' o_invisible_modifier' : ''; + } + } var $field = $('
  • ', {class: fieldClass}); - var $fieldLabel = $('', {class: 'mr-2', text: _.str.sprintf('%s : ', field.string)}); + let $fieldLabel; + if (field.icon) { + $fieldLabel = $('', {class: 'mr-2', html: _.str.sprintf("", field.icon)}); + } else { + $fieldLabel = $('', {class: 'mr-2', text: _.str.sprintf('%s : ', field.string)}); + } $fieldLabel.appendTo($field); var $fieldContainer = $('
    ', {class: 'flex-grow-1'}); $fieldContainer.appendTo($field); diff --git a/addons/web/static/src/legacy/js/views/calendar/calendar_quick_create.js b/addons/web/static/src/legacy/js/views/calendar/calendar_quick_create.js index 503b483340f..fba58a5e08d 100644 --- a/addons/web/static/src/legacy/js/views/calendar/calendar_quick_create.js +++ b/addons/web/static/src/legacy/js/views/calendar/calendar_quick_create.js @@ -40,6 +40,8 @@ var QuickCreate = Dialog.extend({ var self = this; this._super(parent, { title: options.title, + // Close the dialog when clicking outside of it + backdrop: true, size: 'small', buttons: this._buttons ? [ {text: _t("Create"), classes: 'btn-primary', click: function () { diff --git a/addons/web/static/src/legacy/js/views/calendar/calendar_renderer.js b/addons/web/static/src/legacy/js/views/calendar/calendar_renderer.js index d733ae00b82..325563b2b6d 100644 --- a/addons/web/static/src/legacy/js/views/calendar/calendar_renderer.js +++ b/addons/web/static/src/legacy/js/views/calendar/calendar_renderer.js @@ -53,6 +53,7 @@ var SidebarFilter = Widget.extend(FieldManagerMixin, { this.filters = options.filters; this.label = options.label; this.getColor = options.getColor; + this.filter_check_all = options.check_all; }, /** * @override @@ -95,6 +96,7 @@ var SidebarFilter = Widget.extend(FieldManagerMixin, { } this.$el.on('click', '.o_remove', this._onFilterRemove.bind(this)); this.$el.on('click', '.o_calendar_filter_items input', this._onFilterActive.bind(this)); + this.$el.on('click', '.o_calendar_filter_items_checkall input', this._onFilterCheckAll.bind(this)); }, //-------------------------------------------------------------------------- @@ -130,6 +132,7 @@ var SidebarFilter = Widget.extend(FieldManagerMixin, { */ _onFilterActive: function (e) { var $input = $(e.currentTarget); + this.filter_check_all[this.fieldName] = false; this.trigger_up('changeFilter', { 'fieldName': this.fieldName, 'value': $input.closest('.o_calendar_filter_item').data('value'), @@ -143,6 +146,7 @@ var SidebarFilter = Widget.extend(FieldManagerMixin, { _onFilterRemove: function (e) { var self = this; var $filter = $(e.currentTarget).closest('.o_calendar_filter_item'); + this.filter_check_all[this.fieldName] = false; self._rpc({ model: self.write_model, method: 'unlink', @@ -157,6 +161,23 @@ var SidebarFilter = Widget.extend(FieldManagerMixin, { }); }); }, + /** + * @private + * When the user check the box near the filter title: all existing filters + * are active, excepts the 'all' filter that removes the domain on active filters. + * @param {MouseEvent} e + */ + _onFilterCheckAll: function (e) { + let $input = $(e.currentTarget); + let $filter = $(e.currentTarget).closest('.o_calendar_filter_item'); + this.filter_check_all[this.fieldName] = $input.prop('checked'); + this.trigger_up('changeFilter', { + 'fieldName': this.fieldName, + 'value': 'check_all', + 'active': $input.prop('checked'), + }); + + }, }); return AbstractRenderer.extend({ @@ -381,6 +402,10 @@ return AbstractRenderer.extend({ _preOpenCreate: function (data) { if (this.$('.o_cw_popover').length) { this._unselectEvent(); + // Don't take into account the selection if the user closed a popover by doing a "selection" + this.$('.fc-event').not(".o_event").remove(); + // Clicking anywhere in the interface will only close the popover but we don't want the quickCreate + return; } if (this.state.context.default_name) { data.title = this.state.context.default_name; @@ -546,6 +571,8 @@ return AbstractRenderer.extend({ }, height: 'parent', unselectAuto: false, + // prevent too small events + timeGridEventMinHeight: 15, dir: _t.database.parameters.direction, events: (info, successCB) => { successCB(self.state.data); @@ -612,6 +639,7 @@ return AbstractRenderer.extend({ */ _initSidebar: function () { this.$sidebar = this.$('.o_calendar_sidebar'); + this.$calendarSyncContainer = this.$('#calendar_sync'); this.$sidebar_container = this.$(".o_calendar_sidebar_container"); this._initCalendarMini(); }, @@ -857,6 +885,12 @@ return AbstractRenderer.extend({ * @param {Object} eventData */ _getPopoverParams: function (eventData) { + // Display a small lock for private events that we manage + const displayLock = eventData.extendedProps.record.privacy === 'private' && eventData.extendedProps.record.partner_ids.includes(session.partner_id); + let title = eventData.extendedProps.record.display_name; + if (title.length > 30) { + title = title.substring(0, 30) + "…"; + } return { animation: false, delay: { @@ -865,8 +899,9 @@ return AbstractRenderer.extend({ }, trigger: 'manual', html: true, - title: eventData.extendedProps.record.display_name, - template: qweb.render('CalendarView.event.popover.placeholder', {color: this.getColor(eventData.extendedProps.color_index)}), + title: title, + template: qweb.render('CalendarView.event.popover.placeholder', + {color: this.getColor(eventData.extendedProps.color_index), displayLock: displayLock}), container: eventData.allDay ? '.fc-view' : '.fc-scroller', } }, diff --git a/addons/web/static/src/legacy/js/views/calendar/calendar_view.js b/addons/web/static/src/legacy/js/views/calendar/calendar_view.js index 9e69842a989..582471e1ecf 100644 --- a/addons/web/static/src/legacy/js/views/calendar/calendar_view.js +++ b/addons/web/static/src/legacy/js/views/calendar/calendar_view.js @@ -102,6 +102,8 @@ var CalendarView = AbstractView.extend({ 'title': fields[fieldName].string, 'fieldName': fieldName, 'filters': [], + 'check_all': {}, + 'filters': [], }; filters[fieldName].avatar_field = child.attrs.avatar_field; filters[fieldName].avatar_model = fields[fieldName].relation; @@ -111,6 +113,7 @@ var CalendarView = AbstractView.extend({ 'title': fields[fieldName].string, 'fieldName': fieldName, 'filters': [], + 'check_all': {}, }; filters[fieldName].write_model = child.attrs.write_model; filters[fieldName].write_field = child.attrs.write_field; // can't use a x2many fields @@ -122,6 +125,7 @@ var CalendarView = AbstractView.extend({ filters[fieldName] = filters[fieldName] || { 'title': fields[fieldName].string, 'fieldName': fieldName, + 'check_all': {}, 'filters': [], }; if (child.attrs.color) { diff --git a/addons/web/static/src/legacy/scss/web_calendar.scss b/addons/web/static/src/legacy/scss/web_calendar.scss index 28636d85b8b..62a62a4ec8f 100644 --- a/addons/web/static/src/legacy/scss/web_calendar.scss +++ b/addons/web/static/src/legacy/scss/web_calendar.scss @@ -1,5 +1,4 @@ // Variables -$o-cw-color-today: nth($o-colors, 3); $o-cw-color-today-accent: #FC3D39; $o-cw-popup-avatar-size: 16px; $o-cw-filter-avatar-size: 20px; @@ -140,10 +139,10 @@ $o-cw-filter-avatar-size: 20px; padding-left: $o-horizontal-padding; } - // Hide unnecessary borders - table td, div.fc-row.fc-week.fc-widget-content { - border-left-color: transparent; - border-right-color: transparent; + .fc-widget-header.fc-today{ + border-radius: 25px; + background: $o-brand-odoo; + color: white; } // Reinfornce default border color @@ -178,6 +177,8 @@ $o-cw-filter-avatar-size: 20px; div.fc-day-grid { background-color: $o-view-background-color; box-shadow: 0 6px 12px -6px rgba(black, 0.16); + border-left-color: rgba($o-brand-odoo, 0.3); + border-right-color: rgba($o-brand-odoo, 0.3); + hr.fc-widget-header { padding: 1px 0 0; @@ -196,9 +197,9 @@ $o-cw-filter-avatar-size: 20px; } .fc-day.fc-widget-content.fc-today { - border-left-color: rgba($o-cw-color-today, 0.3); - border-right-color: rgba($o-cw-color-today, 0.3); - background: rgba($o-cw-color-today, 0.05 ); + border-left-color: rgba($o-brand-odoo, 0.3); + border-right-color: rgba($o-brand-odoo, 0.3); + background: rgba($o-brand-odoo, .05); } .fc-event { @@ -272,6 +273,7 @@ $o-cw-filter-avatar-size: 20px; .fc-day-top { text-align: center; padding-top: 3px; + padding-bottom: 3px; .fc-day-number { float: none !important; } @@ -557,6 +559,10 @@ $o-cw-filter-avatar-size: 20px; @include media-breakpoint-up('xl') { width: 250px; } + // sync buttons are only displayed on calendar.event views + .o_calendar_sync { + padding-top: 0.5em; + } } .ui-datepicker { @@ -670,7 +676,7 @@ $o-cw-filter-avatar-size: 20px; opacity: 1; } - .o_calendar_filter_item { + .o_calendar_filter_items_checkall, .o_calendar_filter_item { cursor: pointer; overflow: hidden; @@ -775,7 +781,7 @@ $o-cw-filter-avatar-size: 20px; .o_cw_popover_fields_secondary { max-height: 170px; // Fallback for old browsers - max-height: 75vh; + max-height: 25vh; overflow: auto; padding-bottom: 1px; // prevents the scrollbar to show when not needed @@ -802,9 +808,17 @@ $o-cw-filter-avatar-size: 20px; } } +// Generated with http://phrogz.net/css/distinct-colors.html +$o-filter_colors: #ff1e00, #bf9600, #00ff84, #00aeff, #0003b3, #ff006a, #cc3300, #ffea00, #00cc85, #007ab3, + #6900cc, #ff0048, #ff6200, #c2cc00, #00ffc8, #008cff, #a600ff, #bf0036, #b24400, #d0ff00, + #00ffea, #0062b3, #ea00ff, #ff0026, #ff8400, #8bcc00, #00bfaf, #006aff, #af00bf, #bf001d, + #bf6300, #8cff00, #00f2ff, #004ab3, #ff00d0, #ffa600, #3acc00, #00b6bf, #0048ff, #d90094, + #bf7c00, #04ff00, #00d0ff, #0036bf, #ff008c, #ffc800, #00bf49, #0092b3, #0004ff, #b20062, + #649173 !default; + // =============== Generate color classes =============== -@for $i from 1 through length($o-colors-complete) { - $color: nth($o-colors-complete, $i); +@for $i from 1 through length($o-filter_colors) { + $color: nth($o-filter_colors, $i); .o_calendar_view .fc-view { .o_calendar_hatched_#{$i - 1} { @@ -918,6 +932,7 @@ $o-cw-filter-avatar-size: 20px; } .card-header { + padding-left: 2px; border-color: mix($card-border-color, mix(white, $color)); } } diff --git a/addons/web/static/src/legacy/xml/web_calendar.xml b/addons/web/static/src/legacy/xml/web_calendar.xml index 789fce74c0d..0dceb264b39 100644 --- a/addons/web/static/src/legacy/xml/web_calendar.xml +++ b/addons/web/static/src/legacy/xml/web_calendar.xml @@ -7,6 +7,7 @@
    +
    @@ -23,8 +24,10 @@
    -

    - +

    + +

    +

    @@ -55,12 +58,25 @@
    -
    - -
    - -
    - +
    +
    + + +
    +
    + + +
    +
    +
    diff --git a/addons/web/static/tests/legacy/views/calendar_tests.js b/addons/web/static/tests/legacy/views/calendar_tests.js index 0f560f3d7e9..a2c60b1693d 100644 --- a/addons/web/static/tests/legacy/views/calendar_tests.js +++ b/addons/web/static/tests/legacy/views/calendar_tests.js @@ -237,7 +237,7 @@ QUnit.module('Views', { // test filters assert.containsN($sidebar, '.o_calendar_filter', 2, "should display 2 filters"); - var $typeFilter = $sidebar.find('.o_calendar_filter:has(h5:contains(user))'); + var $typeFilter = $sidebar.find('.o_calendar_filter:has(span:contains(user))'); assert.ok($typeFilter.length, "should display 'user' filter"); assert.containsN($typeFilter, '.o_calendar_filter_item', 3, "should display 3 filter items for 'user'"); @@ -246,27 +246,27 @@ QUnit.module('Views', { assert.strictEqual($typeFilter.find('.o_calendar_filter_item:last .o_cw_filter_title').text(), "Undefined", "filters having false value should display 'Undefined' string"); assert.strictEqual($typeFilter.find('.o_calendar_filter_item:last label img').length, 0, "filters having false value should not have any user image"); - var $attendeesFilter = $sidebar.find('.o_calendar_filter:has(h5:contains(attendees))'); + var $attendeesFilter = $sidebar.find('.o_calendar_filter:has(span:contains(attendees))'); assert.ok($attendeesFilter.length, "should display 'attendees' filter"); - assert.containsN($attendeesFilter, '.o_calendar_filter_item', 3, "should display 3 filter items for 'attendees' who use write_model (2 saved + Everything)"); + assert.containsN($attendeesFilter, '.o_calendar_filter_item', 3, "should display 3 filter items for 'attendees' who use write_model (checkall + 2 saved + Everything)"); assert.ok($attendeesFilter.find('.o_field_many2one').length, "should display one2many search bar for 'attendees' filter"); assert.containsN(calendar, '.fc-event', 7, "should display 7 events ('event 5' counts for 2 because it spans two weeks and thus generate two fc-event elements)"); - await testUtils.dom.click(calendar.$('.o_calendar_filter input[type="checkbox"]').first()); + await testUtils.dom.click(calendar.$('.o_calendar_filter input[type="checkbox"]').eq(1)); // click on partner 2 assert.containsN(calendar, '.fc-event', 4, "should now only display 4 event"); - await testUtils.dom.click(calendar.$('.o_calendar_filter input[type="checkbox"]').eq(1)); + await testUtils.dom.click(calendar.$('.o_calendar_filter input[type="checkbox"]').eq(2)); assert.containsNone(calendar, '.fc-event', "should not display any event anymore"); // test search bar in filter await testUtils.dom.click($sidebar.find('input[type="text"]')); - assert.strictEqual($('ul.ui-autocomplete li:not(.o_m2o_dropdown_option)').length, 2,"should display 2 choices in one2many autocomplete"); // TODO: remove :not(.o_m2o_dropdown_option) because can't have "create & edit" choice + assert.strictEqual($('ul.ui-autocomplete li').length, 2,"should display 2 choices in one2many autocomplete"); await testUtils.dom.click($('ul.ui-autocomplete li:first')); - assert.containsN($sidebar, '.o_calendar_filter:has(h5:contains(attendees)) .o_calendar_filter_item', 4, "should display 4 filter items for 'attendees'"); + assert.containsN($sidebar, '.o_calendar_filter:has(span:contains(attendees)) .o_calendar_filter_item', 4, "should display 4 filter items for 'attendees'"); await testUtils.dom.click($sidebar.find('input[type="text"]')); - assert.strictEqual($('ul.ui-autocomplete li:not(.o_m2o_dropdown_option)').text(), "partner 4", "should display the last choice in one2many autocomplete"); // TODO: remove :not(.o_m2o_dropdown_option) because can't have "create & edit" choice - await testUtils.dom.click($sidebar.find('.o_calendar_filter_item .o_remove').first(), {allowInvisible: true}); - assert.containsN($sidebar, '.o_calendar_filter:has(h5:contains(attendees)) .o_calendar_filter_item', 3, "click on remove then should display 3 filter items for 'attendees'"); + assert.strictEqual($('ul.ui-autocomplete li').text(), "partner 4", "should display the last choice in one2many autocomplete"); + await testUtils.dom.click($sidebar.find('.o_calendar_filter_item .o_remove').eq(1), {allowInvisible: true}); + assert.containsN($sidebar, '.o_calendar_filter:has(span:contains(attendees)) .o_calendar_filter_item', 3, "click on remove then should display 3 filter items for 'attendees'"); calendar.destroy(); }); @@ -2279,7 +2279,7 @@ QUnit.module('Views', { assert.containsN(calendar, '.o_calendar_filter', 2, "should display 2 filters"); - var $typeFilter = calendar.$('.o_calendar_filter:has(h5:contains(Event Type))'); + var $typeFilter = calendar.$('.o_calendar_filter:has(span:contains(Event Type))'); assert.ok($typeFilter.length, "should display 'Event Type' filter"); assert.containsOnce($typeFilter, '#o_cw_filter_collapse_EventType', "Id should be equals to o_cw_filter_collapse_EventType for 'Event Type'"); assert.containsN($typeFilter, '.o_calendar_filter_item', 3, "should display 3 filter items for 'Event Type'"); @@ -2450,7 +2450,7 @@ QUnit.module('Views', { }); QUnit.test('Update event with filters', async function (assert) { - assert.expect(6); + assert.expect(12); var records = this.data.user.records; records.push({ @@ -2516,6 +2516,19 @@ QUnit.module('Views', { assert.containsN(calendar, '.o_calendar_filter_item', 6, "should add the missing filter (active)"); assert.containsN(calendar, '.fc-event', 3, "should display the updated item"); + // test the behavior of the 'select all' input checkbox + assert.containsN(calendar, '.o_calendar_filter_item input:checked', 3, "should display 3 true checkbox"); + assert.containsN(calendar, '.o_calendar_filter_item input:not(:checked)', 3, "should display 3 false checkbox"); + // Click to select all users + await testUtils.dom.click(calendar.$('.o_calendar_filter_items_checkall:eq(1) input')); + // should contains 4 events + assert.containsN(calendar, '.fc-event', 4, "should display the updated events"); + // Should have 4 checked boxes + assert.containsN(calendar, '.o_calendar_filter_item input:checked', 4, "should display 4 true checkbox"); + // unselect all user + await testUtils.dom.click(calendar.$('.o_calendar_filter_items_checkall:eq(1) input')); + assert.containsN(calendar, '.fc-event', 0, "should not display any event"); + assert.containsN(calendar, '.o_calendar_filter_item input:checked', 1, "should display 1 true checkbox"); calendar.destroy(); });