[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
This commit is contained in:
Arnaud Joset
2021-08-26 10:21:00 +00:00
parent bb9f21d945
commit 57dbfff2db
10 changed files with 192 additions and 82 deletions
@@ -69,7 +69,7 @@
</t>
<t t-name="Calendar.calendar-box">
<div t-attf-class="#{record.is_highlighted ? 'o_event_hightlight' : ''} #{typeof color === 'number' ? _.str.sprintf('o_calendar_color_%s', color) : 'o_calendar_color_1'} fc-event o_attendee_status_#{record.is_alone ? 'alone' : record.attendee_status} py-0" >
<div t-attf-class="#{record.is_highlighted ? 'o_event_hightlight' : ''} #{typeof color === 'number' ? _.str.sprintf('o_calendar_color_%s', color) : 'o_calendar_color_1'} fc-event o_event o_attendee_status_#{record.is_alone ? 'alone' : record.attendee_status} py-0" >
<div class="o_event_title mr-2">
<span t-if="record.is_alone" class="fa fa-exclamation-circle"/>
<t t-esc="record.display_name"/>
@@ -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,
@@ -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);
}
@@ -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 = $('<li>', {class: fieldClass});
var $fieldLabel = $('<strong>', {class: 'mr-2', text: _.str.sprintf('%s : ', field.string)});
let $fieldLabel;
if (field.icon) {
$fieldLabel = $('<strong>', {class: 'mr-2', html: _.str.sprintf("<b><i class='%s'/></b>", field.icon)});
} else {
$fieldLabel = $('<strong>', {class: 'mr-2', text: _.str.sprintf('%s : ', field.string)});
}
$fieldLabel.appendTo($field);
var $fieldContainer = $('<div>', {class: 'flex-grow-1'});
$fieldContainer.appendTo($field);
@@ -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 () {
@@ -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',
}
},
@@ -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) {
@@ -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));
}
}
@@ -7,6 +7,7 @@
<div class="o_calendar_sidebar_container d-none d-md-block">
<div class="o_calendar_sidebar">
<div class="o_calendar_mini"/>
<div id="calendar_sync" class="o_calendar_sync container inline btn-group"/>
</div>
</div>
</div>
@@ -23,8 +24,10 @@
<div t-attf-class="o_cw_popover popover card shadow #{typeof color === 'number' ? _.str.sprintf('o_calendar_color_%s', color) : ''}" role="tooltip">
<div class="arrow"/>
<div class="card-header d-flex justify-content-between py-2 pr-2">
<h4 class="popover-header border-0 p-0 pt-1"/>
<span class="o_cw_popover_close ml-4"><i class="fa fa-close small"/></span>
<h4 class="p-0 pt-1">
<span class="popover-header border-0"/><span t-if="displayLock" class="ml-1"><i class="fa fa-lock"/></span>
</h4>
<span class="o_cw_popover_close ml-4 mt-2 mr-2"><i class="fa fa-close"/></span>
</div>
<div class="o_cw_body">
</div>
@@ -55,12 +58,25 @@
<t t-set="needCollapse" t-value="widget.filters.length > 2 and widget.title"></t>
<t t-set="titleWithoutSpace" t-value="widget.title.split(' ').join('')"></t>
<div class="o_calendar_filter">
<h5 t-if="needCollapse" type="button" data-toggle="collapse" t-attf-data-target="#o_cw_filter_collapse_#{titleWithoutSpace}" class="d-flex justify-content-between align-items-center">
<t t-esc="widget.title"/><i class="o_cw_filter_collapse_icon fa fa-chevron-down"/>
</h5>
<h5 t-elif="widget.title" t-esc="widget.title"/>
<div class="d-flex">
<div class="o_calendar_filter_items_checkall mr-2" data-value="check_all">
<input t-att-id="widget.title + '_id'" type="checkbox" name="selection" class="position-absolute"
t-att-checked="widget.filter_check_all[widget.fieldName] ? true : undefined"
/>
<label t-att-for="widget.title + '_id'" class="align-items-center m-0">
<span class="o_cw_filter_input_bg o_calendar_filter_all">
<i class="fa fa-check position-relative"/>
</span>
</label>
</div>
<div t-if="needCollapse" type="button" data-toggle="collapse"
t-attf-data-target="#o_cw_filter_collapse_#{titleWithoutSpace}"
class="justify-content-between align-items-center h5">
<span t-esc="widget.title"/>
<i class="o_cw_filter_collapse_icon fa fa-chevron-down"/>
</div>
<h5 t-elif="widget.title" t-esc="widget.title"/>
</div>
<div t-attf-class="o_calendar_filter_items #{needCollapse ? 'collapse show' : 0}" aria-expanded="true" t-attf-id="o_cw_filter_collapse_#{titleWithoutSpace}">
<t t-foreach="widget.filters" t-as="filter" t-if="filter.display == null || filter.display">
<t t-set="idForLabel" t-value="_.uniqueId('o_calendar_filter_item_')"/>
@@ -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();
});