[REF] mail: adapt code after jQuery update

Part of task 1896658

Co-authored-by: Aaron Bohy <aab@odoo.com>
Co-authored-by: Christophe Matthieu <chm@odoo.com>
Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com>
Co-authored-by: David Monjoie <dmo@odoo.com>
Co-authored-by: Martin Geubelle <mge@odoo.com>
Co-authored-by: svs-odoo <svs@odoo.com>
Co-authored-by: Vincent Schippefilt <vsc@odoo.com>
This commit is contained in:
Vincent Schippefilt
2019-03-06 20:07:17 +01:00
committed by Aaron Bohy
co-authored by Aaron Bohy Christophe Matthieu Mathieu Duckerts-Antoine David Monjoie Martin Geubelle svs-odoo
parent aa900d976f
commit 1ae64793fa
59 changed files with 2613 additions and 2637 deletions
+11 -11
View File
@@ -25,11 +25,11 @@ var _t = core._t;
*
* @param {Widget} self a widget instance that can perform RPCs
* @param {Array} ids the ids of activities to read
* @return {Deferred<Array>} resolved with the activities
* @return {Promise<Array>} resolved with the activities
*/
function _readActivities(self, ids) {
if (!ids.length) {
return $.when([]);
return Promise.resolve([]);
}
var context = self.getSession().user_context;
if (self.record && !_.isEmpty(self.record.getContext())) {
@@ -64,7 +64,7 @@ BasicModel.include({
* @private
* @param {Object} record - an element from the localData
* @param {string} fieldName
* @return {Deferred<Array>} resolved with the activities
* @return {Promise<Array>} resolved with the activities
*/
_fetchSpecialActivity: function (record, fieldName) {
var localID = (record._changes && fieldName in record._changes) ?
@@ -144,7 +144,7 @@ var BasicActivity = AbstractField.extend({
/**
* @param {integer} previousActivityTypeID
* @return {$.Promise}
* @return {Promise}
*/
scheduleActivity: function () {
var callback = this._reload.bind(this, { activity: true, thread: true });
@@ -213,7 +213,7 @@ var BasicActivity = AbstractField.extend({
* @param {integer} id
* @param {integer} previousActivityTypeID
* @param {function} callback
* @return {$.Deferred}
* @return {Promise}
*/
_openActivityForm: function (id, callback) {
var action = {
@@ -237,7 +237,7 @@ var BasicActivity = AbstractField.extend({
* @param {integer} activityID
* @param {string} feedback
* @param {integer[]} attachmentIds
* @return {$.Promise}
* @return {Promise}
*/
_sendActivityFeedback: function (activityID, feedback, attachmentIds) {
return this._rpc({
@@ -303,7 +303,7 @@ var BasicActivity = AbstractField.extend({
/**
* @private
* @param {MouseEvent} ev
* @returns {$.Promise}
* @returns {Promise}
*/
_onEditActivity: function (ev) {
ev.preventDefault();
@@ -449,7 +449,7 @@ var BasicActivity = AbstractField.extend({
/**
* @private
* @param {MouseEvent} ev
* @returns {$.Deferred}
* @returns {Promise}
*/
_onPreviewMailTemplate: function (ev) {
ev.stopPropagation();
@@ -477,7 +477,7 @@ var BasicActivity = AbstractField.extend({
/**
* @private
* @param {MouseEvent} ev
* @returns {$.Promise}
* @returns {Promise}
*/
_onSendMailTemplate: function (ev) {
ev.stopPropagation();
@@ -493,7 +493,7 @@ var BasicActivity = AbstractField.extend({
/**
* @private
* @param {MouseEvent} ev
* @returns {$.Deferred}
* @returns {Promise}
*/
_onScheduleActivity: function (ev) {
ev.preventDefault();
@@ -504,7 +504,7 @@ var BasicActivity = AbstractField.extend({
* @private
* @param {MouseEvent} ev
* @param {Object} options
* @returns {$.Promise}
* @returns {Promise}
*/
_onUnlinkActivity: function (ev, options) {
ev.preventDefault();
+28 -25
View File
@@ -103,7 +103,7 @@ var Chatter = Widget.extend({
}));
// start and append the widgets
var fieldDefs = _.invoke(this.fields, 'appendTo', $('<div>'));
var def = this._dp.add($.when.apply($, fieldDefs));
var def = this._dp.add(Promise.all(fieldDefs));
this._render(def).then(this._updateMentionSuggestions.bind(this));
return this._super.apply(this, arguments);
@@ -153,7 +153,7 @@ var Chatter = Widget.extend({
fieldsToReset = this.fields;
}
var fieldDefs = _.invoke(fieldsToReset, 'reset', record);
var def = this._dp.add($.when.apply($, fieldDefs));
var def = this._dp.add(Promise.all(fieldDefs));
this._render(def).then(function () {
self.$el.height('auto');
self._updateMentionSuggestions();
@@ -203,17 +203,18 @@ var Chatter = Widget.extend({
* Discard changes on the record.
*
* @private
* @returns {$.Deferred} resolved if successfully discarding changes on
* @returns {Promise} resolved if successfully discarding changes on
* the record, rejected otherwise
*/
_discardChanges: function () {
var def = $.Deferred();
this.trigger_up('discard_changes', {
recordID: this.record.id,
onSuccess: def.resolve.bind(def),
onFailure: def.reject.bind(def),
var self = this;
return new Promise(function (resolve, reject) {
self.trigger_up('discard_changes', {
recordID: self.record.id,
onSuccess: resolve,
onFailure: reject,
});
});
return def;
},
/**
* Discard changes on the record if the message will reload the record
@@ -221,14 +222,14 @@ var Chatter = Widget.extend({
*
* @private
* @param {Object} messageData
* @return {$.Deferred} resolved if no reload or proceed to discard the
* @return {Promise} resolved if no reload or proceed to discard the
* changes on the record, rejected otherwise
*/
_discardOnReload: function (messageData) {
if (this._reloadAfterPost(messageData)) {
return this._discardChanges();
}
return $.when();
return Promise.resolve();
},
/**
* @private
@@ -323,7 +324,7 @@ var Chatter = Widget.extend({
self._reloadAttachmentBox();
self.trigger_up('reload', {fieldNames: ['message_attachment_count'], keepChanges: true});
}
}).fail(function () {
}).guardedCatch(function () {
self._enableComposer();
});
});
@@ -375,9 +376,9 @@ var Chatter = Widget.extend({
_render: function (def) {
// the rendering of the chatter is aynchronous: relational data of its fields needs to be
// fetched (in some case, it might be synchronous as they hold an internal cache).
// this function takes a deferred as argument, which is resolved once all fields have
// this function takes a promise as argument, which is resolved once all fields have
// fetched their data
// this function appends the fields where they should be once the given deferred is resolved
// this function appends the fields where they should be once the given promise is resolved
// and if it takes more than 500ms, displays a spinner to indicate that it is loading
var self = this;
@@ -385,6 +386,11 @@ var Chatter = Widget.extend({
concurrency.rejectAfter(concurrency.delay(500), def).then(function () {
$spinner.appendTo(self.$el);
});
var always = function () {
// disable widgets in create mode, otherwise enable
self._isCreateMode ? self._disableChatter() : self._enableChatter();
$spinner.remove();
};
return def.then(function () {
if (self.fields.activity) {
@@ -400,11 +406,7 @@ var Chatter = Widget.extend({
if (self.fields.thread) {
self.fields.thread.$el.appendTo(self.$el);
}
}).always(function () {
// disable widgets in create mode, otherwise enable
self._isCreateMode ? self._disableChatter() : self._enableChatter();
$spinner.remove();
});
}).then(always).guardedCatch(always);
},
/**
* @private
@@ -517,7 +519,7 @@ var Chatter = Widget.extend({
if (!this._areAttachmentsLoaded) {
def = this._fetchAttachments();
}
$.when(def).then(this._openAttachmentBox.bind(this));
Promise.resolve(def).then(this._openAttachmentBox.bind(this));
}
},
/**
@@ -534,10 +536,10 @@ var Chatter = Widget.extend({
_onOpenComposerMessage: function () {
var self = this;
if (!this.suggested_partners_def) {
this.suggested_partners_def = $.Deferred();
var method = 'message_get_suggested_recipients';
var args = [[this.context.default_res_id], this.context];
this._rpc({model: this.record.model, method: method, args: args})
this.suggested_partners_def = new Promise(function (resolve, reject) {
var method = 'message_get_suggested_recipients';
var args = [[self.context.default_res_id], self.context];
self._rpc({model: self.record.model, method: method, args: args})
.then(function (result) {
if (!self.suggested_partners_def) {
return; // widget has been reset (e.g. we just switched to another record)
@@ -555,8 +557,9 @@ var Chatter = Widget.extend({
reason: recipient[2],
});
});
self.suggested_partners_def.resolve(suggested_partners);
resolve(suggested_partners);
});
});
}
this.suggested_partners_def.then(function (suggested_partners) {
self._openComposer({ isLog: false, suggested_partners: suggested_partners });
@@ -128,7 +128,7 @@ var BasicComposer = Widget.extend({
.on('update_typing_partners', this, this._onUpdateTypingPartners);
// Mention
this._mentionManager.prependTo(this.$('.o_composer'));
var prependPromise = this._mentionManager.prependTo(this.$('.o_composer'));
// Drag-Drop files
// Allowing body to detect dragenter and dragleave for display
@@ -137,7 +137,7 @@ var BasicComposer = Widget.extend({
$body.on('dragleave.' + this._dropZoneNS, this._onBodyFileDragLeave.bind(this));
$body.on("dragover." + this._dropZoneNS, this._onBodyFileDragover.bind(this));
$body.on("drop." + this._dropZoneNS, this._onBodyFileDrop.bind(this));
return this._super();
return Promise.all([this._super(), prependPromise]);
},
destroy: function () {
$("body").off('dragleave.' + this._dropZoneNS);
@@ -236,7 +236,7 @@ var BasicComposer = Widget.extend({
* displayed to the user. If none of them match, then it will fetch for more
* partner suggestions (@see _mentionFetchPartners).
*
* @param {$.Deferred<Object[]>} prefetchedPartners list of list of
* @param {Promise<Object[]>} prefetchedPartners list of list of
* prefetched partners.
*/
mentionSetPrefetchedPartners: function (prefetchedPartners) {
@@ -299,20 +299,21 @@ var BasicComposer = Widget.extend({
/**
* @private
* @param {string} search
* @returns {$.Deferred<Object[]>}
* @returns {Promise<Object[]>}
*/
_mentionGetCannedResponses: function (search) {
var self = this;
var def = $.Deferred();
clearTimeout(this._cannedTimeout);
this._cannedTimeout = setTimeout(function () {
var cannedResponses = self.call('mail_service', 'getCannedResponses');
var matches = fuzzy.filter(utils.unaccent(search), _.pluck(cannedResponses, 'source'));
var indexes = _.pluck(matches.slice(0, self.options.mentionFetchLimit), 'index');
def.resolve(_.map(indexes, function (index) {
return cannedResponses[index];
}));
}, 500);
var def = new Promise(function (resolve, reject) {
clearTimeout(self._cannedTimeout);
self._cannedTimeout = setTimeout(function () {
var cannedResponses = self.call('mail_service', 'getCannedResponses');
var matches = fuzzy.filter(utils.unaccent(search), _.pluck(cannedResponses, 'source'));
var indexes = _.pluck(matches.slice(0, self.options.mentionFetchLimit), 'index');
resolve(_.map(indexes, function (index) {
return cannedResponses[index];
}));
}, 500);
});
return def;
},
/**
@@ -346,7 +347,7 @@ var BasicComposer = Widget.extend({
*/
_mentionFetchPartners: function (search) {
var self = this;
return $.when(this._mentionPrefetchedPartners).then(function (prefetchedPartners) {
return Promise.resolve(this._mentionPrefetchedPartners).then(function (prefetchedPartners) {
// filter prefetched partners with the given search string
var suggestions = [];
var limit = self.options.mentionFetchLimit;
@@ -371,7 +372,7 @@ var BasicComposer = Widget.extend({
{ limit: limit, search: search }
);
}
return $.when(suggestions).then(function (suggestions) {
return Promise.resolve(suggestions).then(function (suggestions) {
//add im_status on suggestions
_.each(suggestions, function (suggestionsSet) {
_.each(suggestionsSet, function (suggestion) {
@@ -387,27 +388,27 @@ var BasicComposer = Widget.extend({
* @param {string} model
* @param {string} method
* @param {Object} kwargs
* @return {$.Deferred}
* @return {Promise}
*/
_mentionFetchThrottled: function (model, method, kwargs) {
var self = this;
// Delays the execution of the RPC to prevent unnecessary RPCs when the user is still typing
var def = $.Deferred();
clearTimeout(this.mentionFetchTimer);
this.mentionFetchTimer = setTimeout(function () {
return self._rpc({model: model, method: method, kwargs: kwargs})
return new Promise(function (resolve, reject) {
clearTimeout(self.mentionFetchTimer);
self.mentionFetchTimer = setTimeout(function () {
return self._rpc({model: model, method: method, kwargs: kwargs})
.then(function (results) {
def.resolve(results);
resolve(results);
});
}, this.MENTION_THROTTLE);
return def;
}, self.MENTION_THROTTLE);
});
},
/**
* @private
* @returns {$.Deferred}
* @returns {Promise}
*/
_preprocessMessage: function () {
// Return a deferred as this function is extended with asynchronous
// Return a promise as this function is extended with asynchronous
// behavior for the chatter composer
//Removing unwanted extra spaces from message
@@ -420,7 +421,7 @@ var BasicComposer = Widget.extend({
var commands = this.options.commandsEnabled ?
this._mentionManager.getListenerSelection('/') :
[];
return $.when({
return Promise.resolve({
content: this._mentionManager.generateLinks(value),
attachment_ids: _.pluck(this.get('attachment_ids'), 'id'),
partner_ids: _.uniq(_.pluck(this._mentionManager.getListenerSelection('@'), 'id')),
@@ -59,109 +59,112 @@ var ChatterComposer = BasicComposer.extend({
* @private
* @param {Array} checkedSuggestedPartners list of 'recipient' partners to
* complete informations or validate
* @returns {Deferred} resolved with the list of checked suggested partners
* @returns {Promise} resolved with the list of checked suggested partners
* (real partner)
**/
_checkSuggestedPartners: function (checkedSuggestedPartners) {
var self = this;
var checkDone = $.Deferred();
var recipients = _.filter(checkedSuggestedPartners, function (recipient) {
return recipient.checked;
});
var recipientsToFind = _.filter(recipients, function (recipient) {
return (! recipient.partner_id);
});
var namesToFind = _.pluck(recipientsToFind, 'full_name');
var recipientsToCheck = _.filter(recipients, function (recipient) {
return (recipient.partner_id && ! recipient.email_address);
});
var recipientIDs = _.pluck(_.filter(recipients, function (recipient) {
return recipient.partner_id && recipient.email_address;
}), 'partner_id');
var namesToRemove = [];
var recipientIDsToRemove = [];
// have unknown names
// -> call message_get_partner_info_from_emails to try to find
// partner_id
var def;
if (namesToFind.length > 0) {
def = this._rpc({
model: this._model,
method: 'message_partner_info_from_emails',
args: [[this.context.default_res_id], namesToFind],
});
}
// for unknown names + incomplete partners
// -> open popup - cancel = remove from recipients
$.when(def).pipe(function (result) {
result = result || [];
var emailDefs = [];
var recipientPopups = result.concat(recipientsToCheck);
_.each(recipientPopups, function (partnerInfo) {
var deferred = $.Deferred();
emailDefs.push(deferred);
var partnerName = partnerInfo.full_name;
var partnerID = partnerInfo.partner_id;
var parsedEmail = mailUtils.parseEmail(partnerName);
var dialog = new viewDialogs.FormViewDialog(self, {
res_model: 'res.partner',
res_id: partnerID,
context: {
active_model: self._model,
active_id: self.context.default_res_id,
force_email: true,
ref: 'compound_context',
default_name: parsedEmail[0],
default_email: parsedEmail[1],
},
title: _t("Please complete customer's informations"),
disable_multiple_selection: true,
}).open();
dialog.on('closed', self, function () {
deferred.resolve();
});
dialog.opened().then(function () {
dialog.form_view.on('on_button_cancel', self, function () {
namesToRemove.push(partnerName);
if (partnerID) {
recipientIDsToRemove.push(partnerID);
}
});
});
var checkDone = new Promise(function (resolve, reject) {
var recipients = _.filter(checkedSuggestedPartners, function (recipient) {
return recipient.checked;
});
$.when.apply($, emailDefs).then(function () {
var newNamesToFind = _.difference(namesToFind, namesToRemove);
var def;
if (newNamesToFind.length > 0) {
def = self._rpc({
model: self._model,
method: 'message_partner_info_from_emails',
args: [[self.context.default_res_id], newNamesToFind, true],
var recipientsToFind = _.filter(recipients, function (recipient) {
return (! recipient.partner_id);
});
var namesToFind = _.pluck(recipientsToFind, 'full_name');
var recipientsToCheck = _.filter(recipients, function (recipient) {
return (recipient.partner_id && ! recipient.email_address);
});
var recipientIDs = _.pluck(_.filter(recipients, function (recipient) {
return recipient.partner_id && recipient.email_address;
}), 'partner_id');
var namesToRemove = [];
var recipientIDsToRemove = [];
// have unknown names
// -> call message_get_partner_info_from_emails to try to find
// partner_id
var def;
if (namesToFind.length > 0) {
def = self._rpc({
model: self._model,
method: 'message_partner_info_from_emails',
args: [[self.context.default_res_id], namesToFind],
});
}
// for unknown names + incomplete partners
// -> open popup - cancel = remove from recipients
Promise.resolve(def).then(function (result) {
result = result || [];
var emailDefs = [];
var recipientPopups = result.concat(recipientsToCheck);
_.each(recipientPopups, function (partnerInfo) {
var prom = new Promise(function(innerResolve, innerReject) {
var partnerName = partnerInfo.full_name;
var partnerID = partnerInfo.partner_id;
var parsedEmail = mailUtils.parseEmail(partnerName);
var dialog = new viewDialogs.FormViewDialog(self, {
res_model: 'res.partner',
res_id: partnerID,
context: {
active_model: self._model,
active_id: self.context.default_res_id,
force_email: true,
ref: 'compound_context',
default_name: parsedEmail[0],
default_email: parsedEmail[1],
},
title: _t("Please complete customer's informations"),
disable_multiple_selection: true,
}).open();
dialog.on('closed', self, function () {
innerResolve();
});
dialog.opened().then(function () {
dialog.form_view.on('on_button_cancel', self, function () {
namesToRemove.push(partnerName);
if (partnerID) {
recipientIDsToRemove.push(partnerID);
}
});
});
}
$.when(def).pipe(function (result) {
result = result || [];
var recipientPopups = result.concat(recipientsToCheck);
_.each(recipientPopups, function (partnerInfo) {
if (
partnerInfo.partner_id &&
_.indexOf(partnerInfo.partner_id, recipientIDsToRemove) === -1
) {
recipientIDs.push(partnerInfo.partner_id);
}
});
}).pipe(function () {
checkDone.resolve(recipientIDs);
emailDefs.push(prom);
});
Promise.all(emailDefs).then(function () {
var newNamesToFind = _.difference(namesToFind, namesToRemove);
var def;
if (newNamesToFind.length > 0) {
def = self._rpc({
model: self._model,
method: 'message_partner_info_from_emails',
args: [[self.context.default_res_id], newNamesToFind, true],
});
}
Promise.resolve(def).then(function (result) {
result = result || [];
var recipientPopups = result.concat(recipientsToCheck);
_.each(recipientPopups, function (partnerInfo) {
if (
partnerInfo.partner_id &&
_.indexOf(partnerInfo.partner_id, recipientIDsToRemove) === -1
) {
recipientIDs.push(partnerInfo.partner_id);
}
});
}).then(function () {
resolve(recipientIDs);
});
});
});
});
return checkDone;
},
/**
@@ -187,41 +190,40 @@ var ChatterComposer = BasicComposer.extend({
/**
* @override
* @private
* @returns {$.Deferred}
* @returns {Promise}
*/
_preprocessMessage: function () {
var self = this;
var def = $.Deferred();
this._super().then(function (message) {
message = _.extend(message, {
subtype: 'mail.mt_comment',
message_type: 'comment',
context: _.defaults({}, self.context, session.user_context),
});
// Subtype
if (self.options.isLog) {
message.subtype = 'mail.mt_note';
}
// Partner_ids
if (!self.options.isLog) {
var checkedSuggestedPartners = self._getCheckedSuggestedPartners();
self._checkSuggestedPartners(checkedSuggestedPartners).done(function (partnerIDs) {
message.partner_ids = (message.partner_ids || []).concat(partnerIDs);
// update context
message.context = _.defaults({}, message.context, {
mail_post_autofollow: true,
});
def.resolve(message);
return new Promise(function (resolve, reject) {
self._super().then(function (message) {
message = _.extend(message, {
subtype: 'mail.mt_comment',
message_type: 'comment',
context: _.defaults({}, self.context, session.user_context),
});
} else {
def.resolve(message);
}
// Subtype
if (self.options.isLog) {
message.subtype = 'mail.mt_note';
}
// Partner_ids
if (!self.options.isLog) {
var checkedSuggestedPartners = self._getCheckedSuggestedPartners();
self._checkSuggestedPartners(checkedSuggestedPartners).then(function (partnerIDs) {
message.partner_ids = (message.partner_ids || []).concat(partnerIDs);
// update context
message.context = _.defaults({}, message.context, {
mail_post_autofollow: true,
});
resolve(message);
});
} else {
resolve(message);
}
});
});
return def;
},
/**
* @override
@@ -244,20 +246,20 @@ var ChatterComposer = BasicComposer.extend({
}
var self = this;
var recipientDoneDef = $.Deferred();
// any operation on the full-composer will reload the record, so
// warn the user that any unsaved changes on the record will be lost.
this.trigger_up('discard_record_changes', {
proceed: function () {
if (self.options.isLog) {
recipientDoneDef.resolve([]);
} else {
var checkedSuggestedPartners = self._getCheckedSuggestedPartners();
self._checkSuggestedPartners(checkedSuggestedPartners)
.then(recipientDoneDef.resolve.bind(recipientDoneDef));
}
},
var recipientDoneDef = new Promise(function (resolve, reject) {
// any operation on the full-composer will reload the record, so
// warn the user that any unsaved changes on the record will be lost.
self.trigger_up('discard_record_changes', {
proceed: function () {
if (self.options.isLog) {
resolve([]);
} else {
var checkedSuggestedPartners = self._getCheckedSuggestedPartners();
self._checkSuggestedPartners(checkedSuggestedPartners)
.then(resolve);
}
},
});
});
recipientDoneDef.then(function (partnerIDs) {
@@ -102,7 +102,7 @@ var ExtendedComposer = BasicComposer.extend({
/**
* @override
* @private
* @returns {$.Deferred}
* @returns {Promise}
*/
_preprocessMessage: function () {
var self = this;
@@ -114,7 +114,7 @@ var MentionManager = Widget.extend({
if (this._activeListener) {
var mentionWord = this._mentionWord;
$.when(this._activeListener.fetchCallback(mentionWord))
Promise.resolve(this._activeListener.fetchCallback(mentionWord))
.then(function (suggestions) {
if (mentionWord === self._mentionWord) {
// update suggestions only if mentionWord didn't change
+12 -15
View File
@@ -82,7 +82,7 @@ var PartnerInviteDialog = Dialog.extend({
/**
* @private
* @returns {$.Promise}
* @returns {Promise}
*/
_addChannel: function () {
var self = this;
@@ -297,7 +297,7 @@ var Discuss = AbstractAction.extend({
* @override
*/
willStart: function () {
return $.when(this._super(), this.call('mail_service', 'isReady'));
return Promise.all([this._super(), this.call('mail_service', 'isReady')]);
},
/**
* @override
@@ -331,7 +331,7 @@ var Discuss = AbstractAction.extend({
this._extendedComposer.appendTo(this.$('.o_mail_discuss_content')));
defs.push(this._super.apply(this, arguments));
return $.when.apply($, defs)
return Promise.all(defs)
.then(function () {
return self._setThread(self._defaultThreadID);
})
@@ -474,7 +474,7 @@ var Discuss = AbstractAction.extend({
},
/**
* @private
* @returns {$.Promise}
* @returns {Promise}
*/
_fetchAndRenderThread: function () {
var self = this;
@@ -567,7 +567,7 @@ var Discuss = AbstractAction.extend({
* loaded when scrolling to the top, so they can't be loaded if there is no
* scrollbar)
*
* @returns {Deferred} resolved when there are enough messages to fill the
* @returns {Promise} resolved when there are enough messages to fill the
* screen, or when there is no more message to fetch
*/
_loadEnoughMessages: function () {
@@ -583,7 +583,7 @@ var Discuss = AbstractAction.extend({
* Load more messages for the current thread
*
* @private
* @returns {$.Promise}
* @returns {Promise}
*/
_loadMoreMessages: function () {
var self = this;
@@ -647,7 +647,7 @@ var Discuss = AbstractAction.extend({
autoFocus: true,
source: function (request, response) {
self._lastSearchVal = _.escape(request.term);
self._searchChannel(self._lastSearchVal).done(function (result){
self._searchChannel(self._lastSearchVal).then(function (result){
result.push({
label: _.str.sprintf(
'<strong>' + _t("Create %s (Public)") + '</strong>',
@@ -689,7 +689,7 @@ var Discuss = AbstractAction.extend({
autoFocus: true,
source: function (request, response) {
self._lastSearchVal = _.escape(request.term);
self.call('mail_service', 'searchPartner', self._lastSearchVal, 10).done(response);
self.call('mail_service', 'searchPartner', self._lastSearchVal, 10).then(response);
},
select: function (ev, ui) {
var partnerID = ui.item.id;
@@ -819,7 +819,7 @@ var Discuss = AbstractAction.extend({
* Renders, binds events and appends a thread widget.
*
* @private
* @returns {Deferred}
* @returns {Promise}
*/
_renderThread: function () {
this._threadWidget = new ThreadWidget(this, {
@@ -877,7 +877,7 @@ var Discuss = AbstractAction.extend({
/**
* @private
* @param {string} searchVal
* @returns {$.Promise<Array>}
* @returns {Promise<Array>}
*/
_searchChannel: function (searchVal){
return this._rpc({
@@ -915,7 +915,7 @@ var Discuss = AbstractAction.extend({
this._extendedComposer.do_show();
this._threadWidget.scrollToMessage({
msgID: messageID,
messageID: messageID,
duration: 200,
onlyIfNecessary: true
});
@@ -927,7 +927,7 @@ var Discuss = AbstractAction.extend({
*
* @private
* @param {integer} threadID a thread with such ID
* @returns {$.Promise}
* @returns {Promise}
*/
_setThread: function (threadID) {
var self = this;
@@ -1483,9 +1483,6 @@ var Discuss = AbstractAction.extend({
} else {
self._threadWidget.scrollToBottom();
}
})
.fail(function () {
// todo: display notifications
});
},
/**
+2 -2
View File
@@ -116,7 +116,7 @@ Discuss.include({
this._thread = thread;
if (thread.getType() !== 'mailbox') {
this.call('mail_service', 'openThreadWindow', threadID);
return $.when();
return Promise.resolve();
} else {
return this._super.apply(this, arguments);
}
@@ -179,7 +179,7 @@ Discuss.include({
});
def = this.call('mail_service', 'getChannelPreviews', channels);
}
return $.when(def).then(function (previews) {
return Promise.resolve(def).then(function (previews) {
// update content
if (inMailbox) {
if (!previouslyInInbox) {
+2 -2
View File
@@ -76,7 +76,7 @@ var DocumentViewer = Widget.extend({
start: function () {
this.$el.modal('show');
this.$el.on('hidden.bs.modal', _.bind(this._onDestroy, this));
this.$('.o_viewer_img').load(_.bind(this._onImageLoaded, this));
this.$('.o_viewer_img').on("load", _.bind(this._onImageLoaded, this));
this.$('[data-toggle="tooltip"]').tooltip({delay: 0});
return this._super.apply(this, arguments);
},
@@ -132,7 +132,7 @@ var DocumentViewer = Widget.extend({
this.$('.o_viewer_content').html(QWeb.render('DocumentViewer.Content', {
widget: this
}));
this.$('.o_viewer_img').load(_.bind(this._onImageLoaded, this));
this.$('.o_viewer_img').on("load", _.bind(this._onImageLoaded, this));
this.$('[data-toggle="tooltip"]').tooltip({delay: 0});
this._reset();
},
+19 -19
View File
@@ -55,14 +55,14 @@ var Followers = AbstractField.extend({
// note: the rendering of this widget is asynchronous as it needs to
// fetch the details of the followers, but it performs a first rendering
// synchronously (_displayGeneric), and updates its rendering once it
// has fetched the required data, so this function doesn't return a deferred
// has fetched the required data, so this function doesn't return a promise
// as we don't want to wait to the data to be loaded to display the widget
var self = this;
var fetch_def = this.dp.add(this._readFollowers());
concurrency.rejectAfter(concurrency.delay(0), fetch_def).then(this._displayGeneric.bind(this));
fetch_def.then(function () {
return fetch_def.then(function () {
self._displayButtons();
self._displayFollowers(self.followers);
if (self.subtypes) { // current user is follower
@@ -203,7 +203,7 @@ var Followers = AbstractField.extend({
params: { follower_ids: missing_ids, res_model: this.model }
});
}
return $.when(def).then(function (results) {
return Promise.resolve(def).then(function (results) {
if (results) {
self.followers = _.uniq(results.followers.concat(self.followers), 'id');
if (results.subtypes) { //read_followers will return False if current user is not in the list
@@ -241,27 +241,27 @@ var Followers = AbstractField.extend({
*/
_unfollow: function (ids) {
var self = this;
var def = $.Deferred();
var text = _t("Warning! \n If you remove a follower, he won't be notified of any email or discussion on this document.\n Do you really want to remove this follower ?");
Dialog.confirm(this, text, {
confirm_callback: function () {
var args = [
[self.res_id],
ids.partner_ids,
ids.channel_ids,
{}, // FIXME
];
self._rpc({
return new Promise(function (resolve, reject) {
var text = _t("Warning! \n If you remove a follower, he won't be notified of any email or discussion on this document.\n Do you really want to remove this follower ?");
Dialog.confirm(this, text, {
confirm_callback: function () {
var args = [
[self.res_id],
ids.partner_ids,
ids.channel_ids,
{}, // FIXME
];
self._rpc({
model: self.model,
method: 'message_unsubscribe',
args: args
})
.then(self._reload.bind(self));
def.resolve();
},
cancel_callback: def.reject.bind(def),
resolve();
},
cancel_callback: reject,
});
});
return def;
},
_updateSubscription: function (event, followerID, isChannel) {
var ids = {};
@@ -290,7 +290,7 @@ var Followers = AbstractField.extend({
// If no more subtype followed, unsubscribe the follower
if (!checklist.length) {
this._unfollow(ids).fail(function () {
this._unfollow(ids).guardedCatch(function () {
$(event.currentTarget).find('input').addBack('input').prop('checked', true);
});
} else {
+9 -3
View File
@@ -50,18 +50,24 @@ FormRenderer.include({
* @private
*/
_renderNode: function (node) {
var self = this;
if (node.tag === 'div' && node.attrs.class === 'oe_chatter') {
if (!this.chatter) {
this.chatter = new Chatter(this, this.state, this.mailFields, {
isEditable: this.activeActions.edit,
viewType: 'form',
});
this.chatter.appendTo($('<div>'));
this._handleAttributes(this.chatter.$el, node);
var $temporaryParentDiv = $('<div>');
this.defs.push(this.chatter.appendTo($temporaryParentDiv).then(function () {
self.chatter.$el.unwrap();
self._handleAttributes(self.chatter.$el, node);
}));
return $temporaryParentDiv;
} else {
this.chatter.update(this.state);
return this.chatter.$el;
}
return this.chatter.$el;
} else {
return this._super.apply(this, arguments);
}
@@ -20,7 +20,7 @@ BasicModel.include({
* @private
* @param {Object} record - an element from the localData
* @param {string} fieldName
* @return {Deferred<Object>} the deferred is resolved with the
* @return {Promise<Object>} the promise is resolved with the
* invalidPartnerIds
*/
_setInvalidMany2ManyTagsEmail: function (record, fieldName) {
@@ -42,10 +42,10 @@ BasicModel.include({
var changes = {operation: 'DELETE', ids: _.pluck(invalidPartnerIds, 'id')};
def = this._applyX2ManyChange(record, fieldName, changes);
}
return $.when(def).then(function () {
return $.when({
return Promise.resolve(def).then(function () {
return {
invalidPartnerIds: _.pluck(invalidPartnerIds, 'res_id'),
});
};
});
},
});
@@ -65,7 +65,7 @@ var FieldMany2ManyTagsEmail = M2MTags.extend({
* Open a popup for each invalid partners (without email) to fill the email.
*
* @private
* @returns {Deferred}
* @returns {Promise}
*/
_checkEmailPopup: function () {
var self = this;
@@ -75,25 +75,25 @@ var FieldMany2ManyTagsEmail = M2MTags.extend({
// propose the user to correct invalid partners
_.each(this.record.specialData[this.name].invalidPartnerIds, function (resID) {
var def = $.Deferred();
popupDefs.push(def);
var pop = new form_common.FormViewDialog(self, {
res_model: self.field.relation,
res_id: resID,
context: self.record.context,
title: "",
on_saved: function (record) {
if (record.data.email) {
validPartners.push(record.res_id);
}
},
}).open();
pop.on('closed', self, function () {
def.resolve();
var def = new Promise(function (resolve, reject) {
var pop = new form_common.FormViewDialog(self, {
res_model: self.field.relation,
res_id: resID,
context: self.record.context,
title: "",
on_saved: function (record) {
if (record.data.email) {
validPartners.push(record.res_id);
}
},
}).open();
pop.on('closed', self, function () {
resolve();
});
});
popupDefs.push(def);
});
return $.when.apply($, popupDefs).then(function() {
return Promise.all(popupDefs).then(function() {
// All popups have been processed for the given ids
// It is now time to set the final value with valid partners ids.
validPartners = _.uniq(validPartners);
@@ -117,14 +117,14 @@ var FieldMany2ManyTagsEmail = M2MTags.extend({
*/
_render: function () {
var self = this;
var def = $.Deferred();
var _super = this._super.bind(this);
if (this.record.specialData[this.name].invalidPartnerIds.length) {
def = this._checkEmailPopup();
} else {
def.resolve();
}
return def.then(function () {
return new Promise(function (resolve, reject) {
if (self.record.specialData[self.name].invalidPartnerIds.length) {
resolve(self._checkEmailPopup());
} else {
resolve();
}
}).then(function () {
return _super.apply(self, arguments);
});
},
@@ -511,7 +511,7 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
* @see {mail.Manager.Notification} for the receipt of 'toggle_star'
* notification after this rpc.
*
* @return {$.Promise}
* @return {Promise}
*/
toggleStarStatus: function () {
return this._rpc({
@@ -150,18 +150,18 @@ var AbstractThread = Class.extend(Mixins.EventDispatcherMixin, {
* Mark the thread as read, which resets the unread counter to 0. This is
* only performed if the unread counter is not 0.
*
* @returns {$.Promise}
* @returns {Promise}
*/
markAsRead: function () {
if (this._unreadCounter > 0) {
return this._markAsRead();
}
return $.when();
return Promise.resolve();
},
/**
* Post a message on this thread
*
* @returns {$.Promise} resolved with the message object to be sent to the
* @returns {Promise} resolved with the message object to be sent to the
* server
*/
postMessage: function () {
@@ -200,22 +200,22 @@ var AbstractThread = Class.extend(Mixins.EventDispatcherMixin, {
* Mark the thread as read
*
* @private
* @returns {$.Promise}
* @returns {Promise}
*/
_markAsRead: function () {
this.resetUnreadCounter();
return $.when();
return Promise.resolve();
},
/**
* Post a message on this thread
*
* @abstract
* @private
* @returns {$.Promise} resolved with the message object to be sent to the
* @returns {Promise} resolved with the message object to be sent to the
* server
*/
_postMessage: function () {
return $.when();
return Promise.resolve();
},
/**
* Warn views (e.g. discuss app, thread window, etc.) to update visually
@@ -70,7 +70,7 @@ var Channel = SearchableThread.extend(ThreadTypingMixin, {
this._isMyselfModerator = data.is_moderator;
this._lastMessageDate = undefined;
this._members = data.members || [];
// Deferred that is resolved on fetched members of this channel.
// Promise that is resolved on fetched members of this channel.
this._membersDef = undefined;
// number of messages that are 'needaction', which is equivalent to the
// number of messages in this channel that are in inbox.
@@ -129,19 +129,21 @@ var Channel = SearchableThread.extend(ThreadTypingMixin, {
* @pverride
*/
detach: function () {
this._super.apply(this, arguments);
this._rpc({
model: 'mail.channel',
method: 'channel_minimize',
args: [this.getUUID(), true],
}, {
shadow: true,
var self = this;
return this._super.apply(this, arguments).then(function () {
self._rpc({
model: 'mail.channel',
method: 'channel_minimize',
args: [self.getUUID(), true],
}, {
shadow: true,
});
});
},
/**
* Force fetch members of the channel
*
* @returns {$.Promise<Object[]>} resolved with list of channel listeners
* @returns {Promise<Object[]>} resolved with list of channel listeners
*/
forceFetchMembers: function () {
this._membersDef = undefined;
@@ -186,7 +188,7 @@ var Channel = SearchableThread.extend(ThreadTypingMixin, {
/**
* Get listeners of a channel
*
* @returns {$.Promise<Array<Object[]>>} resolved with list of list of
* @returns {Promise<Object[]>} resolved with list of list of
* channel listeners.
*/
getMentionPartnerSuggestions: function () {
@@ -302,7 +304,7 @@ var Channel = SearchableThread.extend(ThreadTypingMixin, {
* Unsubscribes from channel
*
* @abstract
* @returns {$.Promise} resolve when unsubscribed
* @returns {Promise} resolve when unsubscribed
*/
unsubscribe: function () {},
/**
@@ -381,19 +383,19 @@ var Channel = SearchableThread.extend(ThreadTypingMixin, {
*
* @override
* @private
* @returns {$.Promise} resolved when message has been marked as read
* @returns {Promise} resolved when message has been marked as read
*/
_markAsRead: function () {
var superDef = this._super.apply(this, arguments);
var seenDef = this._notifySeen();
return $.when(superDef, seenDef);
return Promise.all([superDef, seenDef]);
},
/**
* @override {mail.model.ThreadTypingMixin}
* @private
* @param {Object} params
* @param {boolean} params.typing
* @returns {$.Promise}
* @returns {Promise}
*/
_notifyMyselfTyping: function (params) {
return this._rpc({
@@ -424,7 +426,7 @@ var Channel = SearchableThread.extend(ThreadTypingMixin, {
* @override
* @private
* @param {Object} data data related to the new message
* @returns {$.Promise<Object>} resolved when the message has been sent to
* @returns {Promise<Object>} resolved when the message has been sent to
* the server, with the object message that has been sent to the server.
*/
_postMessage: function (data) {
@@ -92,7 +92,7 @@ var DocumentThread = Thread.extend({
* @param {Object} options
* @param {boolean} [options.forceFetch] if true, fetch anyway, as user
* clicked on 'load more'.
* @returns {$.Promise<mail.model.Message[]>}
* @returns {Promise<mail.model.Message[]>}
*/
fetchMessages: function (options) {
var self = this;
@@ -217,14 +217,14 @@ var DocumentThread = Thread.extend({
* Get most up to date messageIDs
*
* @private
* @returns {$.Promise} resolved when message IDs have been fetched and set
* @returns {Promise} resolved when message IDs have been fetched and set
* in the model
*/
_fetchMessageIDs: function () {
var self = this;
var resID = this.getDocumentID();
if (!resID || !this._mustFetchMessageIDs) {
return $.when();
return Promise.resolve();
}
return this._rpc({
model: this.getDocumentModel(),
@@ -239,7 +239,7 @@ var DocumentThread = Thread.extend({
* @private
* @param {Object} options
* @param {boolean} [options.forceFetch]
* @returns {$.Promise} resolved when messages have been fetched + document
* @returns {Promise} resolved when messages have been fetched + document
* thread has updated messages
*/
_fetchMessages: function (options) {
@@ -273,7 +273,7 @@ var DocumentThread = Thread.extend({
});
});
} else {
return $.when();
return Promise.resolve();
}
});
@@ -284,7 +284,7 @@ var DocumentThread = Thread.extend({
*
* @override
* @private
* @returns {$.Promise} resolved when messages have been marked as read on
* @returns {Promise} resolved when messages have been marked as read on
* the server.
*/
_markAsRead: function () {
@@ -299,7 +299,7 @@ var DocumentThread = Thread.extend({
* @override
* @private
* @param {Object} data data related to the new message
* @return {$.Promise<Object>} resolved when the message has been sent to
* @return {Promise<Object>} resolved when the message has been sent to
* the server, with the object message that has been sent to the server.
*/
_postMessage: function (data) {
@@ -34,7 +34,7 @@ var Livechat = TwoUserChannel.extend({
* display of a user that is typing.
*
* @override
* @returns {$.Promise<Array<Object[]>>} resolved with list of list of
* @returns {Promise<Object[]>} resolved with list of list of
* livechat members.
*/
getMentionPartnerSuggestions: function () {
@@ -59,7 +59,7 @@ var Mailbox = SearchableThread.extend({
* channel, only keep the lastest message of this document/channel in the
* previews.
*
* @returns {$.Promise<Object[]>}
* @returns {Promise<Object[]>}
*/
getMessagePreviews: function () {
var self = this;
@@ -119,7 +119,7 @@ var Mailbox = SearchableThread.extend({
* makes only sense for 'Inbox'.
*
* @param {Array} domain
* @return {$.Promise} resolved when all messages have been marked as read
* @return {Promise} resolved when all messages have been marked as read
* on the server
*/
markAllMessagesAsRead: function (domain) {
@@ -133,7 +133,7 @@ var Mailbox = SearchableThread.extend({
},
});
}
return $.when();
return Promise.resolve();
},
//--------------------------------------------------------------------------
@@ -239,11 +239,11 @@ var ThreadTypingMixin = {
* @private
* @param {Object} params
* @param {boolean} params.typing whether we are typing something or not
* @returns {$.Promise} resolved if the server is notified, rejected
* @returns {Promise} resolved if the server is notified, rejected
* otherwise
*/
_notifyMyselfTyping: function (params) {
return $.when();
return Promise.resolve();
},
/**
* Warn views that the list of users that are currently typing on this
@@ -56,7 +56,7 @@ var MultiUserChannel = Channel.extend({
* Unsubscribes from channel
*
* @override
* @returns {$.Promise} resolve when unsubscribed
* @returns {Promise} resolve when unsubscribed
*/
unsubscribe: function () {
return this._rpc({
@@ -49,7 +49,7 @@ var SearchableThread = Thread.extend({
}
var cache = this._getCache(domain);
if (cache.loaded) {
return $.when(cache.messages);
return Promise.all(cache.messages);
} else {
return this._fetchMessages(domain);
}
@@ -159,7 +159,7 @@ var SearchableThread = Thread.extend({
* @private
* @param {Array} [pDomain] filter on the messages of the channel
* @param {boolean} [loadMore] Whether it should load more message
* @return {$.Promise<mail.model.Message[]>} resolved with list of messages
* @return {Promise<mail.model.Message[]>} resolved with list of messages
*/
_fetchMessages: function (pDomain, loadMore) {
var self = this;
@@ -58,12 +58,13 @@ var Thread = AbstractThread.extend(ServicesMixin, {
* of this thread. Otherwise unfold it while detaching it.
* @param {boolean} [options.passively=false] if set, if the thread window
* will be created passively.
* @returns {Promise}
*/
detach: function (options) {
options = options || {};
this._detached = true;
this._folded = options.keepFoldState ? this._folded : false;
this._warnUpdatedWindowState({
return this._warnUpdatedWindowState({
passively: options.passively,
});
},
@@ -79,10 +80,10 @@ var Thread = AbstractThread.extend(ServicesMixin, {
* `this.LIMIT` number of messages at a time.
*
* @abstract
* @returns {$.Promise<mail.model.Message[]>}
* @returns {Promise<mail.model.Message[]>}
*/
fetchMessages: function () {
return $.when([]);
return Promise.resolve([]);
},
/**
* Updates the folded state of the thread. Must be overriden to reflect
@@ -110,10 +111,10 @@ var Thread = AbstractThread.extend(ServicesMixin, {
* By default, a thread has not listener.
*
* @abstract
* @returns {$.Promise<Array<Object[]>>}
* @returns {Promise<Array<Object[]>>}
*/
getMentionPartnerSuggestions: function () {
return $.when([]);
return Promise.resolve([]);
},
/**
* Returns the information required to render the preview of this channel.
@@ -287,10 +288,10 @@ var Thread = AbstractThread.extend(ServicesMixin, {
/**
* @abstract
* @private
* @return {$.Promise}
* @return {Promise}
*/
_fetchMessages: function () {
return $.when();
return Promise.resolve();
},
/**
* Replace character representations of emojis by their unicode
@@ -330,7 +331,7 @@ var Thread = AbstractThread.extend(ServicesMixin, {
* @abstract
* @private
* @param {Object} data
* @returns {$.Promise<Object>} resolved with the message object to be sent
* @returns {Promise<Object>} resolved with the message object to be sent
* to the server
*/
_postMessage: function (data) {
@@ -355,7 +356,7 @@ var Thread = AbstractThread.extend(ServicesMixin, {
messageData.subject = data.subject;
}
return this._super.apply(this, arguments).then(function () {
return $.when(messageData);
return messageData;
});
},
/**
@@ -374,10 +375,11 @@ var Thread = AbstractThread.extend(ServicesMixin, {
* @private
* @param {Object} [options={}]
* @param {boolean} [options.passively=false]
* @returns {Promise}
*/
_warnUpdatedWindowState: function (options) {
options = options || {};
this.call('mail_service', 'updateThreadWindow', this.getID(), options);
return this.call('mail_service', 'updateThreadWindow', this.getID(), options);
},
});
@@ -15,7 +15,7 @@ var TwoUserChannel = Channel.extend({
* Unpin from two-user thread
*
* @override
* @returns {$.Promise} resolve when unsubscribed
* @returns {Promise} resolve when unsubscribed
*/
unsubscribe: function () {
return this._rpc({
@@ -182,7 +182,7 @@ MailManager.include({
resModel: resModel,
});
}
this._super.apply(this, arguments);
return this._super.apply(this, arguments);
},
/**
* Updates the thread windows related to document threads.
@@ -80,13 +80,14 @@ var MailManager = AbstractService.extend({
* the unread_counter of channel.
* @param {boolean} [options.silent] whether it should inform in the mailBus
* of the newly created message.
* @returns {mail.model.Message} message object
* @returns {Promise<mail.model.Message>}
*/
addMessage: function (data, options) {
options = options || {};
var message = this.getMessage(data.id);
var prom = Promise.resolve();
if (!message) {
message = this._addNewMessage(data, options);
prom = this._addNewMessage(data, options);
} else {
if (data.moderation_status === 'accepted') {
message.setModerationStatus('accepted', {
@@ -97,7 +98,7 @@ var MailManager = AbstractService.extend({
this._addMessageToThreads(message, options);
}
}
return message;
return prom;
},
/**
* Creates a channel, can be either a true channel or a DM chat based on
@@ -105,7 +106,7 @@ var MailManager = AbstractService.extend({
*
* @param {integer|string} name id of partner (in case of dm) or name
* @param {string} type ['dm_chat', 'public', 'private']
* @returns {$.Promise<integer>} resolved with ID of the newly created
* @returns {Promise<integer>} resolved with ID of the newly created
* channel
*/
createChannel: function (name, type) {
@@ -150,7 +151,7 @@ var MailManager = AbstractService.extend({
* Returns the content that will be shown in the mail navbar dropdown
*
* @param {mail.model.Channel[]} channels
* @returns {$.Promise<Object[]>} resolved with list of channel previews
* @returns {Promise<Object[]>} resolved with list of channel previews
*/
getChannelPreviews: function (channels) {
var self = this;
@@ -227,23 +228,22 @@ var MailManager = AbstractService.extend({
*
* @param {string} [filter=undefined] specify 'chat' or 'channels' to only
* get previews for that type of threads
* @returns {$.Promise<Object[]>} resolved with list of objects that have a
* @returns {Promise<Object[]>} resolved with list of objects that have a
* valid format for rendering a messaging menu preview items.
*/
getSystrayPreviews: function (filter) {
var self = this;
var defs = [];
var channelDef = this._getSystrayChannelPreviews(filter);
var inboxDef = this._getSystrayInboxPreviews(filter);
var failureDef = this._getSystrayMailFailurePreviews(filter);
defs = defs.concat([channelDef, inboxDef, failureDef]);
return $.when.apply($, defs)
.then(function (previewsChannel, previewsInbox, previewsFailure) {
return Promise.all([channelDef, inboxDef, failureDef])
.then(function (result) { //previewsChannel, previewsInbox, previewsFailure
// order: failures > inbox > channel, each group must be sorted
previewsChannel = self._sortPreviews(previewsChannel);
previewsInbox = self._sortPreviews(previewsInbox);
previewsFailure = self._sortPreviews(previewsFailure);
var previewsChannel = self._sortPreviews(result[0]);
var previewsInbox = self._sortPreviews(result[1]);
var previewsFailure = self._sortPreviews(result[2]);
return _.union(previewsFailure, previewsInbox, previewsChannel);
});
},
@@ -295,7 +295,7 @@ var MailManager = AbstractService.extend({
* This is the case when it has fetched the initial state from the server,
* by means of the route 'mail/init_messaging'
*
* @returns {$.Promise}
* @returns {Promise}
*/
isReady: function () {
return this._isReady;
@@ -306,24 +306,24 @@ var MailManager = AbstractService.extend({
*
* @param {integer} channelID
* @param {Object} [options] options to be passed on channel add
* @returns {$.Promise<integer>} resolved with channelID joined
* @returns {Promise<integer>} resolved with channelID joined
*/
joinChannel: function (channelID, options) {
var def;
var prom;
var channel = this.getChannel(channelID);
if (channel) {
// channel already joined
def = $.when(channelID);
prom = Promise.resolve(channelID);
} else {
def = this._joinAndAddChannel(channelID, options);
prom = this._joinAndAddChannel(channelID, options);
}
return def;
return prom;
},
/**
* Mark messages as read
*
* @param {Array} messageIDs list of messages IDs
* @returns {$.Promise} resolved when messages have been marked as read on
* @returns {Promise} resolved when messages have been marked as read on
* the server.
*/
markMessagesAsRead: function (messageIDs) {
@@ -341,7 +341,7 @@ var MailManager = AbstractService.extend({
args: [ids],
});
} else {
return $.when();
return Promise.resolve();
}
},
/**
@@ -396,19 +396,19 @@ var MailManager = AbstractService.extend({
*
* @param {string} searchVal
* @param {integer} limit max number of found partners in the response
* @returns {$.Promise<Object[]>} list of found partners (matching
* @returns {Promise<Object[]>} list of found partners (matching
* 'searchVal')
*/
searchPartner: function (searchVal, limit) {
var def = $.Deferred();
var self = this;
var partners = this._searchPartnerPrefetch(searchVal, limit);
if (!partners.length) {
def = this._searchPartnerFetch(searchVal, limit);
} else {
def = $.when(partners);
}
return def.then(function (partners) {
return new Promise(function (resolve, reject) {
if (!partners.length) {
resolve(self._searchPartnerFetch(searchVal, limit));
} else {
resolve(partners);
}
}).then(function (partners) {
var suggestions = _.map(partners, function (partner) {
return {
id: partner.id,
@@ -422,7 +422,7 @@ var MailManager = AbstractService.extend({
/**
* Unstars all messages from all channels
*
* @returns {$.Promise}
* @returns {Promise}
*/
unstarAll: function () {
return this._rpc({
@@ -448,11 +448,13 @@ var MailManager = AbstractService.extend({
* @param {string} [data.state] e.g. 'open', 'folded'
* @param {Object|integer} [options=undefined]
* @param {boolean} [options.silent=false]
* @returns {integer} the ID of the newly or already existing channel
* @returns {Promise<integer>} resolves with the ID of the newly or already
* existing channel
*/
_addChannel: function (data, options) {
options = typeof options === 'object' ? options : {};
var channel = this.getChannel(data.id);
var proms = [];
if (!channel) {
channel = this._makeChannel(data, options);
if (channel.getType() === 'dm_chat') {
@@ -462,14 +464,16 @@ var MailManager = AbstractService.extend({
if (data.last_message) {
// channel_info in mobile, necessary for showing channel
// preview in mobile
this.addMessage(data.last_message);
proms.push(this.addMessage(data.last_message));
}
this._sortThreads();
if (!options.silent) {
this._mailBus.trigger('new_channel', channel);
this._mailBus.trigger('new_channel', channel, proms);
}
}
return channel.getID();
return Promise.all(proms).then(function () {
return channel.getID();
});
},
/**
* Add a new mailbox
@@ -516,21 +520,23 @@ var MailManager = AbstractService.extend({
* @private
* @param {Object} data
* @param {Object} options
* @returns {mail.model.Message}
* @return {Promise<mail.model.Message>}
*/
_addNewMessage: function (data, options) {
var self = this;
var message = this._makeMessage(data);
// Keep the array ordered by ID when inserting the new message
var index = _.sortedIndex(this._messages, message, function (msg) {
return msg.getID();
});
this._messages.splice(index, 0, message);
this._addNewMessagePostprocessThread(message, options);
this._addMessageToThreads(message, options);
if (!options.silent) {
this._mailBus.trigger('new_message', message);
}
return message;
return this._addNewMessagePostprocessThread(message, options).then(function () {
self._addMessageToThreads(message, options);
if (!options.silent) {
self._mailBus.trigger('new_message', message);
}
return message;
});
},
/**
* For newly added message, postprocess threads linked to this message
@@ -541,9 +547,11 @@ var MailManager = AbstractService.extend({
* @param {Array} [options.domain]
* @param {boolean} [options.incrementUnread]
* @param {boolean} [options.showNotification]
* @returns {Promise}
*/
_addNewMessagePostprocessThread: function (message, options) {
var self = this;
var proms = [];
_.each(message.getThreadIDs(), function (threadID) {
var thread = self.getThread(threadID);
if (thread) {
@@ -553,6 +561,7 @@ var MailManager = AbstractService.extend({
!message.isSystemNotification()
) {
if (thread.isTwoUserThread() && options.showNotification) {
var prom = Promise.resolve();
if (
!self._isDiscussOpen() &&
!config.device.isMobile &&
@@ -560,17 +569,21 @@ var MailManager = AbstractService.extend({
) {
// automatically open thread window
// while keeping it unread
thread.detach({ passively: true });
}
var query = { isVisible: false };
self._mailBus.trigger('is_thread_bottom_visible', thread, query);
if (!self.call('bus_service', 'isOdooFocused') || !query.isVisible) {
self._notifyIncomingMessage(message);
prom = thread.detach({ passively: true });
}
proms.push(prom);
prom.then(function () {
var query = { isVisible: false };
self._mailBus.trigger('is_thread_bottom_visible', thread, query);
if (!self.call('bus_service', 'isOdooFocused') || !query.isVisible) {
self._notifyIncomingMessage(message);
}
});
}
}
}
});
return Promise.all(proms);
},
/**
* Create a Channel (other than a DM)
@@ -578,7 +591,7 @@ var MailManager = AbstractService.extend({
* @private
* @param {string} name
* @param {string} type
* @returns {$.Promise<integer>} ID of the created channel
* @returns {Promise<integer>} ID of the created channel
*/
_createChannel: function (name, type) {
var context = _.extend({ isMobile: config.device.isMobile }, session.user_context);
@@ -595,7 +608,7 @@ var MailManager = AbstractService.extend({
*
* @private
* @param {string} name
* @returns {$.Promise<integer>} ID of the created channel
* @returns {Promise<integer>} ID of the created channel
*/
_createDMChat: function (name) {
var context = _.extend({ isMobile: config.device.isMobile }, session.user_context);
@@ -610,7 +623,7 @@ var MailManager = AbstractService.extend({
/**
* @private
* @param {integer[]} channelIDs
* @returns {$.Promise<Object[]>} resolved with list of channel preview
* @returns {Promise<Object[]>} resolved with list of channel preview
*/
_fetchChannelPreviews: function (channelIDs) {
return this._rpc({
@@ -652,7 +665,7 @@ var MailManager = AbstractService.extend({
*
* @private
* @param {mail.model.Channel[]} channels
* @returns {$.Deferred<Object[]>} resolved with list of channel previews
* @returns {Promise<Object[]>} resolved with list of channel previews
*/
_getChannelPreviews: function (channels) {
var self = this;
@@ -667,19 +680,22 @@ var MailManager = AbstractService.extend({
});
fetchDef = this._fetchChannelPreviews(ids);
} else {
fetchDef = $.when();
fetchDef = Promise.resolve();
}
return fetchDef.then(function (fetchedPreviews) {
// add fetched messages
var proms = [];
_.each(fetchedPreviews, function (fetchedPreview) {
if (fetchedPreview.last_message) {
self.addMessage(fetchedPreview.last_message);
proms.push(self.addMessage(fetchedPreview.last_message));
}
// mark the channel as previewed, so that we do not need to
// fetch preview again
var channel = self.getChannel(fetchedPreview.id);
channel.markAsPreviewed();
});
return Promise.all(proms);
}).then(function () {
return _.map(channels, function (channel) {
return channel.getPreview();
});
@@ -694,7 +710,7 @@ var MailManager = AbstractService.extend({
* the documentID is omitted for a model preview, whereas it is set for a
* preview of a failure related to a document.
*
* @returns {$.Promise<Object[]>} resolved with previews of mail failures
* @returns {Promise<Object[]>} resolved with previews of mail failures
*/
_getMailFailurePreviews: function () {
// items = list of objects:
@@ -754,7 +770,7 @@ var MailManager = AbstractService.extend({
* @private
* @param {string|undefined} [filter] the filter to apply on channel
* previews in systray messaging menu
* @returns {$.Promise<Object[]>} resolved with object valid for template
* @returns {Promise<Object[]>} resolved with object valid for template
* mail.Preview on channel previews, based on selected filter
*/
_getSystrayChannelPreviews: function (filter) {
@@ -796,7 +812,7 @@ var MailManager = AbstractService.extend({
*
* @private
* @param {string|undefined} [filter]
* @returns {$.Deferred<Object[]>} resolved with valid objects for template
* @returns {Promise<Object[]>} resolved with valid objects for template
* mail.Preview on inbox message previews
*/
_getSystrayInboxPreviews: function (filter) {
@@ -806,7 +822,7 @@ var MailManager = AbstractService.extend({
var inbox = this.getMailbox('inbox');
return inbox.getMessagePreviews();
} else {
return $.when([]);
return Promise.resolve([]);
}
},
/**
@@ -820,7 +836,7 @@ var MailManager = AbstractService.extend({
if (filter === 'mailbox_inbox' || !filter) {
return this._getMailFailurePreviews();
} else {
return $.when([]);
return Promise.resolve([]);
}
},
/**
@@ -863,7 +879,7 @@ var MailManager = AbstractService.extend({
* @private
* @param {integer|string} channelID
* @param {Object} options options passed on channel add
* @returns {$.Promise<integer>} channelID of joined and added channel
* @returns {Promise<integer>} channelID of joined and added channel
*/
_joinAndAddChannel: function (channelID, options) {
var self = this;
@@ -941,7 +957,7 @@ var MailManager = AbstractService.extend({
this._toNotifyChannelFetchedIDs = [];
if (_.isEmpty(channelIDs)) {
// no channel to notify fetched
return $.when();
return Promise.resolve();
}
return this._rpc({
model: 'mail.channel',
@@ -1098,7 +1114,7 @@ var MailManager = AbstractService.extend({
* @private
* @param {string} searchVal
* @param {integer} limit
* @returns {$.Promise<Object[]>} fetched partners matching 'searchVal'
* @returns {Promise<Object[]>} fetched partners matching 'searchVal'
*/
_searchPartnerFetch: function (searchVal, limit) {
return this._rpc({
@@ -1194,12 +1210,17 @@ var MailManager = AbstractService.extend({
* (e.g. list of public channel data are stored in 'channel_channel')
* @param {Object[]} [data.channel_slots[i] list of data of channel of type
* `i`
* @returns {Promise}
*/
_updateChannelsFromServer: function (data) {
var self = this;
var proms = [];
_.each(data.channel_slots, function (channels) {
_.each(channels, self._addChannel.bind(self));
_.each(channels, function (channel) {
proms.push(self._addChannel(channel));
});
});
return Promise.all(proms);
},
/**
* Update commands from mail data fetched from the server
@@ -1221,11 +1242,12 @@ var MailManager = AbstractService.extend({
* @param {Array<Object[]>} result.mention_partner_suggestions list of
* suggestions.
* @param {integer} result.menu_id the menu ID of discuss app
* @returns {Promise}
*/
_updateInternalStateFromServer: function (result) {
// commands are needed for channel instantiation
this._updateCommandsFromServer(result);
this._updateChannelsFromServer(result);
var prom = this._updateChannelsFromServer(result);
this._updateModerationSettingsFromServer(result);
this._updateMailboxesFromServer(result);
this._updateMailFailuresFromServer(result);
@@ -1233,6 +1255,8 @@ var MailManager = AbstractService.extend({
this._mentionPartnerSuggestions = result.mention_partner_suggestions;
this._discussMenuID = result.menu_id;
return prom;
},
/**
* Update the mailboxes with mail data fetched from server, namely 'Inbox',
@@ -101,12 +101,12 @@ MailManager.include({
channelAlreadyInCache = !!this.getChannel(messageData.channel_ids[0]);
def = this.joinChannel(messageData.channel_ids[0], { autoswitch: false });
} else {
def = $.when();
def = Promise.resolve();
}
def.then(function () {
// don't increment unread if channel wasn't in cache yet as
// its unread counter has just been fetched
self.addMessage(messageData, {
return self.addMessage(messageData, {
showNotification: true,
incrementUnread: channelAlreadyInCache
});
@@ -179,18 +179,19 @@ MailManager.include({
_handleNeedactionNotification: function (messageData) {
var self = this;
var inbox = this.getMailbox('inbox');
var message = this.addMessage(messageData, {
this.addMessage(messageData, {
incrementUnread: true,
showNotification: true,
}).then(function (message) {
inbox.incrementMailboxCounter();
_.each(message.getThreadIDs(), function (threadID) {
var channel = self.getChannel(threadID);
if (channel) {
channel.incrementNeedactionCounter();
}
});
self._mailBus.trigger('update_needaction', inbox.getMailboxCounter());
});
inbox.incrementMailboxCounter();
_.each(message.getThreadIDs(), function (threadID) {
var channel = self.getChannel(threadID);
if (channel) {
channel.incrementNeedactionCounter();
}
});
this._mailBus.trigger('update_needaction', inbox.getMailboxCounter());
},
/**
* Called when an activity record has been updated on the server
@@ -381,8 +382,10 @@ MailManager.include({
* @param {Object} data.message data of message
*/
_handlePartnerMessageModeratorNotification: function (data) {
this.addMessage(data.message);
this._mailBus.trigger('update_moderation_counter');
var self = this;
this.addMessage(data.message).then(function () {
self._mailBus.trigger('update_moderation_counter');
});
},
/**
* On receiving a notification that is specific to a user
@@ -81,7 +81,7 @@ MailManager.include({
var self = this;
var partnerIDs = data.partnerIDs;
if (_.isEmpty(partnerIDs)) {
return $.when();
return Promise.resolve();
}
return this._rpc({
model: 'res.partner',
@@ -94,6 +94,7 @@ MailManager.include({
* is not open yet, and do nothing otherwise.
* @param {boolean} [options.keepFoldState=false] if set to true, keep the
* fold state of the thread
* @returns {Promise}
*/
openThreadWindow: function (threadID, options) {
var self = this;
@@ -108,10 +109,10 @@ MailManager.include({
return;
}
var threadWindow = this._getThreadWindow(threadID);
var def = $.when();
var prom = Promise.resolve();
if (!threadWindow) {
thread.isCreatingWindow = true;
def = thread.fetchMessages().then(function () {
prom = thread.fetchMessages().then(function () {
threadWindow = self._makeNewThreadWindow(thread, options);
self._placeNewThreadWindow(threadWindow, options.passively);
return threadWindow.appendTo($(self.THREAD_WINDOW_APPENDTO));
@@ -126,12 +127,12 @@ MailManager.include({
) {
thread.markAsRead();
}
}).fail(function () {
thread.isCreatingWindow = false;
}).guardedCatch(function () {
// thread window could not be open, which may happen due to
// access error while fetching messages to the document.
// abort opening the thread window in this case.
thread.close();
}).always(function () {
thread.isCreatingWindow = false;
});
} else if (!options.passively) {
@@ -139,7 +140,7 @@ MailManager.include({
this._makeThreadWindowVisible(threadWindow);
}
}
def.then(function () {
return prom.then(function () {
threadWindow.updateVisualFoldState();
});
},
@@ -155,14 +156,16 @@ MailManager.include({
*/
updateThreadWindow: function (threadID, options) {
var thread = this.getThread(threadID);
var prom = Promise.resolve();
if (thread) {
if (thread.isDetached()) {
_.extend(options, { keepFoldState: true });
this.openThreadWindow(threadID, options);
prom = this.openThreadWindow(threadID, options);
} else {
this._closeThreadWindow(threadID);
}
}
return prom;
},
//--------------------------------------------------------------------------
@@ -429,7 +432,7 @@ MailManager.include({
*
* @private
* @param {integer} partnerID
* @returns {$.Promise<integer>} resolved with ID of the DM chat
* @returns {Promise<integer>} resolved with ID of the DM chat
*/
_openAndDetachDMChat: function (partnerID) {
return this._rpc({
@@ -647,10 +650,16 @@ MailManager.include({
*
* @private
* @param {mail.model.Channel} channel
* @param {Promise[]} proms used to synchronize async operations on the
* channel (like the rendering of its window)
*/
_onNewChannel: function (channel) {
_onNewChannel: function (channel, proms) {
if (channel.isDetached()) {
this.openThreadWindow(channel.getID(), { keepFoldState: true, passively: true });
var prom = this.openThreadWindow(channel.getID(), {
keepFoldState: true,
passively: true,
});
proms.push(prom);
} else {
this._closeThreadWindow(channel.getID());
}
@@ -20,7 +20,7 @@ var ActivityMenu = Widget.extend({
'show.bs.dropdown': '_onActivityMenuShow',
},
willStart: function () {
return $.when(this.call('mail_service', 'isReady'));
return Promise.resolve(this.call('mail_service', 'isReady'));
},
start: function () {
this._$activitiesPreview = this.$('.o_mail_systray_dropdown_items');
@@ -30,7 +30,7 @@ var MessagingMenu = Widget.extend({
* @override
*/
willStart: function () {
return $.when(this._super.apply(this, arguments), this.call('mail_service', 'isReady'));
return Promise.all([this._super.apply(this, arguments), this.call('mail_service', 'isReady')]);
},
/**
* @override
@@ -112,7 +112,7 @@ var MessagingMenu = Widget.extend({
},
/**
* @private
* @returns {$.Promise<Object[]>} resolved with list of previews that are
* @returns {Promise<Object[]>} resolved with list of previews that are
* compatible with the 'mail.Preview' template.
*/
_getPreviews: function () {
+7 -7
View File
@@ -57,7 +57,7 @@ var ThreadField = AbstractField.extend({
var def1 = this._threadWidget.appendTo(this.$el);
var def2 = this._super.apply(this, arguments);
return this.alive($.when(def1, def2)).then(function () {
return this.alive(Promise.all([def1, def2])).then(function () {
// unwrap the thread to remove an unnecessary level on div
self.setElement(self._threadWidget.$el);
var mailBus = self.call('mail_service', 'getMailBus');
@@ -86,7 +86,7 @@ var ThreadField = AbstractField.extend({
/**
* @param {Object} message
* @param {integer[]} message.partner_ids
* @return {$.Promise}
* @return {Promise}
*/
postMessage: function (message) {
var self = this;
@@ -95,9 +95,9 @@ var ThreadField = AbstractField.extend({
if (message.partner_ids.length) {
self.trigger_up('reload_mail_fields', { followers: true });
}
})
.fail(function () {
}).guardedCatch(function () {
self.do_notify(_t("Sending Error"), _t("Your message has not been sent."));
return Promise.reject();
});
},
@@ -109,7 +109,7 @@ var ThreadField = AbstractField.extend({
* @private
* @param {Object} [options]
* @param {boolean} [options.forceFetch]
* @return {$.Deferred}
* @return {Promise}
*/
_fetchAndRenderThread: function (options) {
var self = this;
@@ -117,7 +117,7 @@ var ThreadField = AbstractField.extend({
var thread = new CreateModeDocumentThread();
options = { isCreateMode: true };
self._threadWidget.render(thread, options);
return $.when();
return Promise.resolve();
} else {
var fetchDef = this.dp.add(this._documentThread.fetchMessages(options));
return fetchDef.then(function () {
@@ -134,7 +134,7 @@ var ThreadField = AbstractField.extend({
/**
* @override
* @private
* @returns {$.Deferred}
* @returns {Promise}
*/
_render: function () {
return this._fetchAndRenderThread();
+5 -5
View File
@@ -248,16 +248,16 @@ var ThreadWidget = Widget.extend({
*/
removeMessageAndRender: function (messageID, thread, options) {
var self = this;
var done = $.Deferred();
this.$('.o_thread_message[data-message-id="' + messageID + '"]')
return new Promise(function (resolve, reject) {
self.$('.o_thread_message[data-message-id="' + messageID + '"]')
.fadeOut({
done: function () {
self.render(thread, options);
done.resolve();
resolve();
},
duration: 200,
});
return done;
});
},
/**
* Scroll to the bottom of the thread
@@ -273,7 +273,7 @@ var ThreadWidget = Widget.extend({
* @param {boolean} [options.onlyIfNecessary]
*/
scrollToMessage: function (options) {
var $target = this.$('.o_thread_message[data-message-id="' + options.msgID + '"]');
var $target = this.$('.o_thread_message[data-message-id="' + options.messageID + '"]');
if (options.onlyIfNecessary) {
var delta = $target.parent().height() - $target.height();
var offset = delta < 0 ?
@@ -95,7 +95,7 @@ var AbstractThreadWindow = Widget.extend({
var def = this._threadWidget.replace(this.$('.o_thread_window_content')).then(function () {
self._threadWidget.$el.on('scroll', self, self._debouncedOnScroll);
});
return $.when(this._super(), def);
return Promise.all([this._super(), def]);
},
/**
* @override
@@ -83,7 +83,7 @@ var ThreadWindow = AbstractThreadWindow.extend({
}
this._updateOutOfOfficeReadMoreLessButton();
return $.when(superDef, composerDef);
return Promise.all([superDef, composerDef]);
},
//--------------------------------------------------------------------------
@@ -241,7 +241,7 @@ var ThreadWindow = AbstractThreadWindow.extend({
autoFocus: true,
source: function (request, response) {
self.call('mail_service', 'searchPartner', request.term, 10)
.done(response);
.then(response);
},
select: function (event, ui) {
// remember partner ID so that we can replace this window
@@ -19,7 +19,7 @@ var ActivityModel = AbstractModel.extend({
* @override
* @param {Object} params
* @param {Array[]} params.domain
* @returns {Deferred}
* @returns {Promise}
*/
load: function (params) {
this.domain = params.domain;
@@ -31,7 +31,7 @@ var ActivityModel = AbstractModel.extend({
* @param {any} handle
* @param {Object} params
* @param {Array[]} params.domain
* @returns {Deferred}
* @returns {Promise}
*/
reload: function (handle, params) {
if (params && 'domain' in params) {
@@ -48,7 +48,7 @@ var ActivityModel = AbstractModel.extend({
* Fetch activity data.
*
* @private
* @returns {Deferred}
* @returns {Promise}
*/
_fetchData: function () {
var self = this;
@@ -5,8 +5,8 @@ var AbstractRenderer = require('web.AbstractRenderer');
var core = require('web.core');
var field_registry = require('web.field_registry');
var QWeb = core.qweb;
var _t = core._t;
var QWeb = core.qweb;
var ActivityRenderer = AbstractRenderer.extend({
className: 'o_activity_view',
@@ -125,21 +125,22 @@ var ActivityRenderer = AbstractRenderer.extend({
};
var KanbanActivity = field_registry.get('kanban_activity');
var widget = new KanbanActivity(self, "activity_ids", record, {});
widget.appendTo($td);
// replace clock by closest deadline
var $date = $('<div>');
var formated_date = moment(activity_group.o_closest_deadline).format('ll');
var current_year = (new Date()).getFullYear();
if (formated_date.endsWith(current_year)) { // Dummy logic to remove year (only if current year), we will maybe need to improve it
formated_date = formated_date.slice(0, -4);
formated_date = formated_date.replace(/( |,)*$/g, "");
}
$date
.text(formated_date)
.addClass('o_closest_deadline');
$td.find('a')
.empty()
.append($date);
widget.appendTo($td).then(function() {
// replace clock by closest deadline
var $date = $('<div>');
var formated_date = moment(activity_group.o_closest_deadline).format('ll');
var current_year = (new Date()).getFullYear();
if (formated_date.endsWith(current_year)) { // Dummy logic to remove year (only if current year), we will maybe need to improve it
formated_date = formated_date.slice(0, -4);
formated_date = formated_date.replace(/( |,)*$/g, "");
}
$date
.text(formated_date)
.addClass('o_closest_deadline');
$td.find('a')
.empty()
.append($date);
});
return $td;
});
var $tr = $('<tr/>', {class: 'o_data_row'})
+32 -32
View File
@@ -162,19 +162,19 @@ QUnit.module('activity view', {
}
});
QUnit.test('activity view: simple activity rendering', function (assert) {
QUnit.test('activity view: simple activity rendering', async function (assert) {
assert.expect(7);
var activity = createView({
var activity = await createView({
View: ActivityView,
model: 'task',
data: this.data,
arch: '<activity string="Task"/>',
mockRPC: function(route, args) {
if (args.method === 'get_activity_data') {
return $.when(ACTIVITY_DATA);
return Promise.resolve(ACTIVITY_DATA);
}
return this._super(route, args);
}
},
});
assert.containsOnce(activity, 'table',
@@ -194,16 +194,16 @@ QUnit.test('activity view: simple activity rendering', function (assert) {
activity.destroy();
});
QUnit.test('activity view: no content rendering', function (assert) {
QUnit.test('activity view: no content rendering', async function (assert) {
assert.expect(2);
var activity = createView({
var activity = await createView({
View: ActivityView,
model: 'task',
data: this.data,
arch: '<activity string="Task"/>',
mockRPC: function (route, args) {
if (args.method === 'get_activity_data') {
return $.when({ activity_types: [] });
return Promise.resolve({ activity_types: [] });
}
return this._super(route, args);
},
@@ -218,20 +218,20 @@ QUnit.test('activity view: no content rendering', function (assert) {
activity.destroy();
});
QUnit.test('activity view: batch send mail on activity', function (assert) {
QUnit.test('activity view: batch send mail on activity', async function (assert) {
assert.expect(6);
var activity = createView({
var activity = await createView({
View: ActivityView,
model: 'task',
data: this.data,
arch: '<activity string="Task"/>',
mockRPC: function(route, args) {
if (args.method === 'activity_send_mail'){
assert.step(args.args);
return $.when();
assert.step(JSON.stringify(args.args));
return Promise.resolve();
}
if (args.method === 'get_activity_data') {
return $.when(ACTIVITY_DATA);
return Promise.resolve(ACTIVITY_DATA);
}
return this._super(route, args);
},
@@ -250,16 +250,16 @@ QUnit.test('activity view: batch send mail on activity', function (assert) {
testUtils.dom.click(activity.$('table thead tr:first th:nth-child(2) span:nth-child(2) i.fa-ellipsis-v'));
testUtils.dom.click(activity.$('table thead tr:first th:nth-child(2) span:nth-child(2) .dropdown-menu.show .o_send_mail_template:nth-child(2):contains(Task: Reception Acknowledgment)'));
assert.verifySteps([
[[13, 30], 9], //send mail template 9 on tasl 13 and 30
[[13, 30], 8] //send mail template 8 on tasl 13 and 30
'[[13,30],9]', // send mail template 9 on tasks 13 and 30
'[[13,30],8]', // send mail template 8 on tasks 13 and 30
]);
activity.destroy();
});
QUnit.test('activity view: activity widget', function (assert) {
QUnit.test('activity view: activity widget', async function (assert) {
assert.expect(16);
var activity = createView({
var activity = await createView({
View: ActivityView,
model: 'task',
data: this.data,
@@ -268,16 +268,16 @@ QUnit.test('activity view: activity widget', function (assert) {
if (args.method === 'activity_send_mail'){
assert.deepEqual([[30],8],args.args, "Should send template 8 on record 30");
assert.step('activity_send_mail');
return $.when();
return Promise.resolve();
}
if (args.method === 'action_feedback_schedule_next'){
assert.deepEqual([[3]],args.args, "Should execute action_feedback_schedule_next on activity 3 only ");
assert.equal(args.kwargs.feedback, "feedback2");
assert.step('action_feedback_schedule_next');
return $.when({serverGeneratedAction: true});
return Promise.resolve({serverGeneratedAction: true});
}
if (args.method === 'get_activity_data') {
return $.when(ACTIVITY_DATA);
return Promise.resolve(ACTIVITY_DATA);
}
return this._super(route, args);
},
@@ -305,12 +305,12 @@ QUnit.test('activity view: activity widget', function (assert) {
assert.step("Unexpected action");
}
},
}
},
});
var today = activity.$('table tbody tr:first td:nth-child(2).today');
var dropdown = today.find('.dropdown-menu.o_activity');
testUtils.dom.click(today.find('.o_closest_deadline'));
await testUtils.dom.click(today.find('.o_closest_deadline'));
assert.hasClass(dropdown,'show', "dropdown should be displayed");
assert.ok(dropdown.find('.o_activity_color_today:contains(Today)').length, "Title should be today");
assert.ok(dropdown.find('.o_activity_title_entry[data-activity-id="2"]:first div:contains(template8)').length,
@@ -318,20 +318,20 @@ QUnit.test('activity view: activity widget', function (assert) {
assert.ok(dropdown.find('.o_activity_title_entry[data-activity-id="2"]:eq(1) div:contains(template9)').length,
"template9 should be available");
testUtils.dom.click(dropdown.find('.o_activity_title_entry[data-activity-id="2"]:first .o_activity_template_preview'));
testUtils.dom.click(dropdown.find('.o_activity_title_entry[data-activity-id="2"]:first .o_activity_template_send'));
await testUtils.dom.click(dropdown.find('.o_activity_title_entry[data-activity-id="2"]:first .o_activity_template_preview'));
await testUtils.dom.click(dropdown.find('.o_activity_title_entry[data-activity-id="2"]:first .o_activity_template_send'));
var overdue = activity.$('table tbody tr:first td:nth-child(3).overdue');
testUtils.dom.click(overdue.find('.o_closest_deadline'));
await testUtils.dom.click(overdue.find('.o_closest_deadline'));
dropdown = overdue.find('.dropdown-menu.o_activity');
assert.notOk(dropdown.find('.o_activity_title div div div:first span').length,
"No template should be available");
testUtils.dom.click(dropdown.find('.o_schedule_activity'));
testUtils.dom.click(overdue.find('.o_closest_deadline'));
testUtils.dom.click(dropdown.find('.o_mark_as_done'));
await testUtils.dom.click(dropdown.find('.o_schedule_activity'));
await testUtils.dom.click(overdue.find('.o_closest_deadline'));
await testUtils.dom.click(dropdown.find('.o_mark_as_done'));
dropdown.find('#activity_feedback').val("feedback2");
testUtils.dom.click(dropdown.find('.o_activity_popover_done_next'));
await testUtils.dom.click(dropdown.find('.o_activity_popover_done_next'));
assert.verifySteps([
"do_action_compose",
"activity_send_mail",
@@ -342,10 +342,10 @@ QUnit.test('activity view: activity widget', function (assert) {
activity.destroy();
});
QUnit.test('activity view: no group_by_menu and no time_range_menu', function (assert) {
QUnit.test('activity view: no group_by_menu and no time_range_menu', async function (assert) {
assert.expect(4);
var actionManager = createActionManager({
var actionManager = await createActionManager({
actions: [{
id: 1,
name: 'Task Action',
@@ -365,13 +365,13 @@ QUnit.test('activity view: no group_by_menu and no time_range_menu', function (a
if (args.method === 'get_activity_data') {
assert.deepEqual(args.kwargs.context, {lang: 'zz_ZZ'},
'The context should have been passed');
return $.when(ACTIVITY_DATA);
return Promise.resolve(ACTIVITY_DATA);
}
return this._super(route, args);
},
});
actionManager.doAction(1);
await actionManager.doAction(1);
assert.containsN(actionManager, '.o_search_options .o_dropdown button:visible', 2,
"only two elements should be available in view search");
File diff suppressed because it is too large Load Diff
@@ -20,68 +20,64 @@ QUnit.module('Discuss in mobile', {
},
});
QUnit.test('mobile basic rendering', function (assert) {
QUnit.test('mobile basic rendering', async function (assert) {
// This is a very basic first test for the client action. However, with
// the chat_service, it is hard to override RPCs (for instance, the
// /mail/client_action route is always called when the test suite is
// launched), and we must wait for this RPC to be done before starting to
// test the interface. This should be refactored to facilitate the testing.
assert.expect(16);
var done = assert.async();
createDiscuss({
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
debug: true,
}).then(function (discuss) {
// test basic rendering in mobile
assert.containsOnce(discuss, '.o_mail_discuss_content .o_mail_no_content',
"should display the no content message");
assert.containsOnce(discuss, '.o_mail_mobile_tabs',
"should have rendered the tabs");
assert.hasClass(discuss.$('.o_mail_mobile_tab[data-type=mailbox_inbox]'),'active',
"should be in inbox tab");
assert.containsOnce(discuss, '.o_mail_discuss_mobile_mailboxes_buttons:visible',
"inbox/starred buttons should be visible");
assert.hasClass(discuss.$('.o_mail_discuss_mobile_mailboxes_buttons .o_mailbox_inbox_item[data-type=mailbox_inbox]'),'btn-primary',
"should be in inbox");
assert.hasClass($('.o_mail_discuss_button_dm_chat'),'d-none',
"should have invisible button 'New Message'");
assert.hasClass($('.o_mail_discuss_button_multi_user_channel'),'d-none',
"should have invisible button 'New Channel'");
// move to 'Chat' tab
testUtils.dom.click(discuss.$('.o_mail_mobile_tab[data-type=dm_chat]'));
assert.hasClass(discuss.$('.o_mail_mobile_tab[data-type=dm_chat]'),'active',
"should be in 'Chat' tab");
assert.containsNone(discuss, '.o_mail_discuss_content .o_mail_no_content',
"should display the no content message");
assert.strictEqual($('.o_mail_discuss_button_dm_chat').length, 1,
"should have a button to open DM chat in 'Chat' tab");
assert.doesNotHaveClass($('.o_mail_discuss_button_dm_chat'), 'd-none',
"should be visible in 'Chat' tab");
assert.hasClass($('.o_mail_discuss_button_multi_user_channel'),'d-none',
"should have invisible button 'New Channel' in 'Chat' tab");
testUtils.dom.click($('.o_mail_discuss_button_dm_chat'));
assert.containsOnce(discuss, '.o_mail_add_thread input:visible',
"should display the input to add a channel");
// move to 'Channels' tab
testUtils.dom.click(discuss.$('.o_mail_mobile_tab[data-type=multi_user_channel]'));
assert.hasClass($('.o_mail_discuss_button_dm_chat'),'d-none',
"should have invisible button 'New Message' in 'Channels' tab");
assert.doesNotHaveClass($('.o_mail_discuss_button_multi_user_channel'), 'd-none',
"should have visible button 'New Channel' in 'Channels' tab");
testUtils.dom.click($('.o_mail_discuss_button_multi_user_channel'));
assert.containsOnce(discuss, '.o_mail_add_thread input:visible',
"should display the input to add a channel");
discuss.destroy();
done();
});
// test basic rendering in mobile
assert.containsOnce(discuss, '.o_mail_discuss_content .o_mail_no_content',
"should display the no content message");
assert.containsOnce(discuss, '.o_mail_mobile_tabs',
"should have rendered the tabs");
assert.hasClass(discuss.$('.o_mail_mobile_tab[data-type=mailbox_inbox]'),'active',
"should be in inbox tab");
assert.containsOnce(discuss, '.o_mail_discuss_mobile_mailboxes_buttons:visible',
"inbox/starred buttons should be visible");
assert.hasClass(discuss.$('.o_mail_discuss_mobile_mailboxes_buttons .o_mailbox_inbox_item[data-type=mailbox_inbox]'),'btn-primary',
"should be in inbox");
assert.hasClass($('.o_mail_discuss_button_dm_chat'),'d-none',
"should have invisible button 'New Message'");
assert.hasClass($('.o_mail_discuss_button_multi_user_channel'),'d-none',
"should have invisible button 'New Channel'");
// move to 'Chat' tab
await testUtils.dom.click(discuss.$('.o_mail_mobile_tab[data-type=dm_chat]'));
assert.hasClass(discuss.$('.o_mail_mobile_tab[data-type=dm_chat]'),'active',
"should be in 'Chat' tab");
assert.containsNone(discuss, '.o_mail_discuss_content .o_mail_no_content',
"should display the no content message");
assert.strictEqual($('.o_mail_discuss_button_dm_chat').length, 1,
"should have a button to open DM chat in 'Chat' tab");
assert.doesNotHaveClass($('.o_mail_discuss_button_dm_chat'), 'd-none',
"should be visible in 'Chat' tab");
assert.hasClass($('.o_mail_discuss_button_multi_user_channel'),'d-none',
"should have invisible button 'New Channel' in 'Chat' tab");
await testUtils.dom.click($('.o_mail_discuss_button_dm_chat'));
assert.containsOnce(discuss, '.o_mail_add_thread input:visible',
"should display the input to add a channel");
// move to 'Channels' tab
await testUtils.dom.click(discuss.$('.o_mail_mobile_tab[data-type=multi_user_channel]'));
assert.hasClass($('.o_mail_discuss_button_dm_chat'),'d-none',
"should have invisible button 'New Message' in 'Channels' tab");
assert.doesNotHaveClass($('.o_mail_discuss_button_multi_user_channel'), 'd-none',
"should have visible button 'New Channel' in 'Channels' tab");
await testUtils.dom.click($('.o_mail_discuss_button_multi_user_channel') );
assert.containsOnce(discuss, '.o_mail_add_thread input:visible',
"should display the input to add a channel");
discuss.destroy();
});
QUnit.test('on_{attach/detach}_callback', function (assert) {
@@ -145,7 +145,7 @@ QUnit.test('moderator: moderated channel with pending moderation message', funct
data: this.data,
services: this.services,
})
.then(function (discuss) {
.then(async function (discuss) {
var $moderationBox = discuss.$(
'.o_mail_discuss_item' +
'[data-thread-id="mailbox_moderation"]');
@@ -156,7 +156,7 @@ QUnit.test('moderator: moderated channel with pending moderation message', funct
"the mailbox counter of the moderation mailbox should display '1'");
// 1. go to moderation mailbox
testUtils.dom.click($moderationBox);
await testUtils.dom.click($moderationBox);
var $message = discuss.$('.o_thread_message');
// check message
assert.strictEqual($message.length, 1,
@@ -210,7 +210,7 @@ QUnit.test('moderator: moderated channel with pending moderation message', funct
'the moderate button "Discard" should be invisible by default');
// click on message moderation checkbox
testUtils.dom.click($message.find('.moderation_checkbox'));
await testUtils.dom.click($message.find('.moderation_checkbox'));
assert.ok($message.find('.moderation_checkbox').prop('checked'),
"the moderation checkbox should become checked after click");
// check select all (disabled) / unselect all buttons (enabled)
@@ -227,7 +227,7 @@ QUnit.test('moderator: moderated channel with pending moderation message', funct
'the moderate button "Discard" should become visible');
// 2. go to channel 'general'
testUtils.dom.click(discuss.$('.o_mail_discuss_item[data-thread-id="1"]'));
await testUtils.dom.click(discuss.$('.o_mail_discuss_item[data-thread-id="1"]'));
$message = discuss.$('.o_thread_message');
// check correct message
assert.strictEqual($message.length, 1,
@@ -289,12 +289,12 @@ QUnit.test('moderator: accept pending moderation message', function (assert) {
return this._super.apply(this, arguments);
},
})
.then(function (discuss) {
.then(async function (discuss) {
// 1. go to moderation box
var $moderationBox = discuss.$(
'.o_mail_discuss_item' +
'[data-thread-id="mailbox_moderation"]');
testUtils.dom.click($moderationBox);
await testUtils.dom.click($moderationBox);
// check there is a message to moderate
var $message = discuss.$('.o_thread_message');
assert.strictEqual($message.length, 1,
@@ -304,7 +304,7 @@ QUnit.test('moderator: accept pending moderation message', function (assert) {
assert.strictEqual($message.find('.moderation_checkbox').length, 1,
"the message should have a moderation checkbox");
// accept the message pending moderation
testUtils.dom.click(discuss.$('.o_thread_message_moderation[data-decision="accept"]'));
await testUtils.dom.click(discuss.$('.o_thread_message_moderation[data-decision="accept"]'));
assert.verifySteps(['moderate']);
// stop the fadeout animation and immediately remove the element
@@ -313,7 +313,7 @@ QUnit.test('moderator: accept pending moderation message', function (assert) {
"should now have no message displayed in moderation box");
// 2. go to channel 'general'
testUtils.dom.click(discuss.$('.o_mail_discuss_item[data-thread-id="1"]'));
await testUtils.dom.click(discuss.$('.o_mail_discuss_item[data-thread-id="1"]'));
$message = discuss.$('.o_thread_message');
// check message is there and has no moderate checkbox
assert.strictEqual($message.length, 1,
@@ -379,12 +379,12 @@ QUnit.test('moderator: reject pending moderation message (reject with explanatio
return this._super.apply(this, arguments);
},
})
.then(function (discuss) {
.then(async function (discuss) {
// 1. go to moderation box
var $moderationBox = discuss.$(
'.o_mail_discuss_item' +
'[data-thread-id="mailbox_moderation"]');
testUtils.dom.click($moderationBox);
await testUtils.dom.click($moderationBox);
// check there is a message to moderate
var $message = discuss.$('.o_thread_message');
assert.strictEqual($message.length, 1,
@@ -394,7 +394,7 @@ QUnit.test('moderator: reject pending moderation message (reject with explanatio
assert.strictEqual($message.find('.moderation_checkbox').length, 1,
"the message should have a moderation checkbox");
// reject the message pending moderation
testUtils.dom.click(discuss.$('.o_thread_message_moderation[data-decision="reject"]'));
await testUtils.dom.click(discuss.$('.o_thread_message_moderation[data-decision="reject"]'));
// check reject dialog prompt
assert.strictEqual($('.modal-dialog').length, 1,
@@ -419,7 +419,7 @@ QUnit.test('moderator: reject pending moderation message (reject with explanatio
"should have a send button on the reject dialog");
// send mesage
testUtils.dom.click($('.modal-footer button'));
await testUtils.dom.click($('.modal-footer button'));
assert.verifySteps(['moderate']);
// // stop the fadeout animation and immediately remove the element
@@ -428,7 +428,7 @@ QUnit.test('moderator: reject pending moderation message (reject with explanatio
"should now have no message displayed in moderation box");
// 2. go to channel 'general'
testUtils.dom.click(discuss.$('.o_mail_discuss_item[data-thread-id="1"]'));
await testUtils.dom.click(discuss.$('.o_mail_discuss_item[data-thread-id="1"]'));
assert.containsNone(discuss, '.o_thread_message')
discuss.destroy();
@@ -482,12 +482,12 @@ QUnit.test('moderator: discard pending moderation message (reject without explan
return this._super.apply(this, arguments);
},
})
.then(function (discuss) {
.then(async function (discuss) {
// 1. go to moderation box
var $moderationBox = discuss.$(
'.o_mail_discuss_item' +
'[data-thread-id="mailbox_moderation"]');
testUtils.dom.click($moderationBox);
await testUtils.dom.click($moderationBox);
// check there is a message to moderate
var $message = discuss.$('.o_thread_message');
assert.strictEqual($message.length, 1,
@@ -497,7 +497,7 @@ QUnit.test('moderator: discard pending moderation message (reject without explan
assert.strictEqual($message.find('.moderation_checkbox').length, 1,
"the message should have a moderation checkbox");
// discard the message pending moderation
testUtils.dom.click(discuss.$('.o_thread_message_moderation[data-decision="discard"]'));
await testUtils.dom.click(discuss.$('.o_thread_message_moderation[data-decision="discard"]'));
// check discard dialog prompt
assert.strictEqual($('.modal-dialog').length, 1,
@@ -513,7 +513,7 @@ QUnit.test('moderator: discard pending moderation message (reject without explan
"should have a cancel button in the dialog for discard");
// discard mesage
testUtils.dom.click($('.modal-footer button.btn-primary'));
await testUtils.dom.click($('.modal-footer button.btn-primary'));
assert.verifySteps(['moderate']);
// stop the fadeout animation and immediately remove the element
@@ -522,7 +522,7 @@ QUnit.test('moderator: discard pending moderation message (reject without explan
"should now have no message displayed in moderation box");
// 2. go to channel 'general'
testUtils.dom.click(discuss.$('.o_mail_discuss_item[data-thread-id="1"]'));
await testUtils.dom.click(discuss.$('.o_mail_discuss_item[data-thread-id="1"]'));
assert.containsNone(discuss, '.o_thread_message')
discuss.destroy();
@@ -534,7 +534,7 @@ QUnit.test('author: send message in moderated channel', function (assert) {
assert.expect(4);
var done = assert.async();
var messagePostDef = $.Deferred();
var messagePostDef = testUtils.makeTestPromise();
this.data.initMessaging = {
channel_slots: {
@@ -554,7 +554,7 @@ QUnit.test('author: send message in moderated channel', function (assert) {
params: {},
data: this.data,
services: this.services,
mockRPC: function (route, args) {
mockRPC: async function (route, args) {
if (args.method === 'message_post') {
var message = {
id: 100,
@@ -572,9 +572,10 @@ QUnit.test('author: send message in moderated channel', function (assert) {
};
var notification = [metaData, notifData];
objectDiscuss.call('bus_service', 'trigger', 'notification', [notification]);
await testUtils.nextTick();
messagePostDef.resolve();
return $.when(message.id);
return Promise.resolve(message.id);
}
return this._super.apply(this, arguments);
},
@@ -582,14 +583,14 @@ QUnit.test('author: send message in moderated channel', function (assert) {
partner_id: 2,
},
})
.then(function (discuss) {
.then(async function (discuss) {
objectDiscuss = discuss;
// go to channel 'general'
testUtils.dom.click(discuss.$('.o_mail_discuss_item[data-thread-id="1"]'));
await testUtils.dom.click(discuss.$('.o_mail_discuss_item[data-thread-id="1"]'));
// post a message
discuss.$('.o_composer_input textarea').first().val("some text");
testUtils.dom.click(discuss.$('.o_composer_send button'));
await testUtils.dom.click(discuss.$('.o_composer_send button'));
messagePostDef
.then(function () {
@@ -646,10 +647,10 @@ QUnit.test('author: sent message accepted in moderated channel', function (asser
partner_id: 2,
},
})
.then(function (discuss) {
.then(async function (discuss) {
// go to channel 'general'
testUtils.dom.click(discuss.$('.o_mail_discuss_item[data-thread-id="1"]'));
await testUtils.dom.click(discuss.$('.o_mail_discuss_item[data-thread-id="1"]'));
// check message is pending
var $message = discuss.$('.o_thread_message');
assert.strictEqual($message.length, 1,
@@ -674,7 +675,7 @@ QUnit.test('author: sent message accepted in moderated channel', function (asser
var metaData = [dbName, 'mail.channel'];
var notification = [metaData, messageData];
discuss.call('bus_service', 'trigger', 'notification', [notification]);
await testUtils.nextTick();
// check message is accepted
$message = discuss.$('.o_thread_message');
assert.strictEqual($message.length, 1,
@@ -727,10 +728,10 @@ QUnit.test('author: sent message rejected in moderated channel', function (asser
partner_id: 2,
},
})
.then(function (discuss) {
.then(async function (discuss) {
// go to channel 'general'
testUtils.dom.click(discuss.$('.o_mail_discuss_item[data-thread-id="1"]'));
await testUtils.dom.click(discuss.$('.o_mail_discuss_item[data-thread-id="1"]'));
// check message is pending
var $message = discuss.$('.o_thread_message');
assert.strictEqual($message.length, 1,
@@ -751,7 +752,7 @@ QUnit.test('author: sent message rejected in moderated channel', function (asser
var metaData = [dbName, 'res.partner'];
var notification = [metaData, notifData];
discuss.call('bus_service', 'trigger', 'notification', [notification]);
await testUtils.nextTick();
// // check no message
assert.containsNone(discuss, '.o_thread_message',
"message should be removed from channel after reject");
@@ -761,12 +762,11 @@ QUnit.test('author: sent message rejected in moderated channel', function (asser
});
});
QUnit.test('no crash when load-more fetching "accepted" message twice', function (assert) {
QUnit.test('no crash when load-more fetching "accepted" message twice', async function (assert) {
// This tests requires discuss not loading more messages due to having less
// messages to fetch than available height. This justifies we simply do not
// patch FETCH_LIMIT to 1, as it would detect that more messages could fit
// the empty space (it behaviour is linked to "auto load more").
var done = assert.async();
assert.expect(2);
var FETCH_LIMIT = Thread.prototype._FETCH_LIMIT;
@@ -799,7 +799,7 @@ QUnit.test('no crash when load-more fetching "accepted" message twice', function
};
var count = 0;
createDiscuss({
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
@@ -811,33 +811,32 @@ QUnit.test('no crash when load-more fetching "accepted" message twice', function
count++;
if (count === 1) {
// inbox message_fetch
return $.when([]);
return Promise.resolve([]);
}
// general message_fetch
return $.when(messageData);
return Promise.resolve(messageData);
}
return this._super.apply(this, arguments);
},
})
.then(function (discuss) {
var $general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.strictEqual($general.length, 1,
"should have the channel item with id 1");
assert.strictEqual($general.attr('title'), 'general',
"should have the title 'general'");
// click on general
$general.click();
// simulate search
discuss.trigger_up('search', {
domains: [['author_id', '=', 100]],
});
discuss.destroy();
done();
});
var $general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.strictEqual($general.length, 1,
"should have the channel item with id 1");
assert.strictEqual($general.attr('title'), 'general',
"should have the title 'general'");
// click on general
await testUtils.dom.click($general);
// simulate search
discuss.trigger_up('search', {
domains: [['author_id', '=', 100]],
});
await testUtils.nextTick();
discuss.destroy();
});
});
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -153,15 +153,14 @@ QUnit.module('Discuss (Typing Notifications)', {
}
});
QUnit.test('notify is typing', function (assert) {
QUnit.test('notify is typing', async function (assert) {
assert.expect(11);
var done = assert.async();
// this is to track the step that we are testing in the `notify_typing`
// mocked RPC
var step = 1;
createDiscuss({
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
@@ -188,44 +187,42 @@ QUnit.test('notify is typing', function (assert) {
return this._super.apply(this, arguments);
},
session: { partner_id: this.myPartnerID },
}).then(function (discuss) {
var $general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.strictEqual($general.length, 1,
"should have the channel item with id 1");
assert.hasAttrValue($general, 'title', 'general',
"should have the title 'general'");
// click on general
testUtils.dom.click($general);
var $input = discuss.$('textarea.o_composer_text_field').first();
assert.ok($input.length, "should display a composer input");
// STEP 1: current user is typing something
step = 1;
$input.focus();
testUtils.fields.editInput($input, '1');
assert.verifySteps(['notify_typing']);
// STEP 2: current user clears input
step = 2;
testUtils.fields.editInput($input, '');
assert.verifySteps(['notify_typing', 'notify_typing']);
discuss.destroy();
done();
});
var $general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.strictEqual($general.length, 1,
"should have the channel item with id 1");
assert.hasAttrValue($general, 'title', 'general',
"should have the title 'general'");
// click on general
await testUtils.dom.click($general);
var $input = discuss.$('textarea.o_composer_text_field').first();
assert.ok($input.length, "should display a composer input");
// STEP 1: current user is typing something
step = 1;
$input.focus();
testUtils.fields.editInput($input, '1');
assert.verifySteps(['notify_typing']);
// STEP 2: current user clears input
step = 2;
testUtils.fields.editInput($input, '');
assert.verifySteps(['notify_typing']);
discuss.destroy();
});
QUnit.test('receive is typing notification', function (assert) {
QUnit.test('receive is typing notification', async function (assert) {
assert.expect(10);
var self = this;
createDiscuss({
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
@@ -235,7 +232,7 @@ QUnit.test('receive is typing notification', function (assert) {
// when receiving an 'is typing' notification, fetch the list of
// members of this channel if we haven't done yet.
if (args.method === 'channel_fetch_listeners') {
return $.when([
return Promise.resolve([
{ id: self.myPartnerID, name: self.myName },
{ id: 42, name: "Someone" },
]);
@@ -243,67 +240,68 @@ QUnit.test('receive is typing notification', function (assert) {
return this._super.apply(this, arguments);
},
session: { partner_id: this.myPartnerID },
}).then(function (discuss) {
var $general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.strictEqual($general.length, 1,
"should have the channel item with id 1");
assert.hasAttrValue($general, 'title', 'general',
"should have the title 'general'");
// pick 1st composer (basic), not 2nd composer (extended, hidden)
var $composer = discuss.$('.o_thread_composer').first();
// click on general
testUtils.dom.click($general);
assert.containsNone($general, '.o_mail_thread_typing_icon',
"should no longer show someone is typing in sidebar of discuss");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(), "",
"should no longer show someone is typing on the composer of active thread");
self.simulateIsTyping({
channelID: 1,
isTyping: true,
partnerID: 42,
widget: discuss,
});
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.containsOnce($general, '.o_mail_thread_typing_icon',
"should have a thread typing icon next to general icon in the sidebar");
assert.hasAttrValue($general.find('.o_mail_thread_typing_icon'), 'title',
"Someone is typing...",
"should show who is typing on hover on this thread typing icon");
assert.containsOnce($composer, '.o_composer_thread_typing',
"should show typing info on the composer of active thread");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(),
"Someone is typing...",
"should show who is typing on the composer of active thread");
self.simulateIsTyping({
channelID: 1,
isTyping: false,
partnerID: 42,
widget: discuss,
});
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.containsNone($general, '.o_mail_thread_typing_icon',
"should no longer show someone is typing in sidebar of discuss");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(), "",
"should no longer show someone is typing on the composer of active thread");
discuss.destroy();
});
var $general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.strictEqual($general.length, 1,
"should have the channel item with id 1");
assert.hasAttrValue($general, 'title', 'general',
"should have the title 'general'");
// pick 1st composer (basic), not 2nd composer (extended, hidden)
var $composer = discuss.$('.o_thread_composer').first();
// click on general
await testUtils.dom.click($general);
assert.containsNone($general, '.o_mail_thread_typing_icon',
"should no longer show someone is typing in sidebar of discuss");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(), "",
"should no longer show someone is typing on the composer of active thread");
self.simulateIsTyping({
channelID: 1,
isTyping: true,
partnerID: 42,
widget: discuss,
});
await testUtils.nextMicrotaskTick();
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.containsOnce($general, '.o_mail_thread_typing_icon',
"should have a thread typing icon next to general icon in the sidebar");
assert.hasAttrValue($general.find('.o_mail_thread_typing_icon'), 'title',
"Someone is typing...",
"should show who is typing on hover on this thread typing icon");
assert.containsOnce($composer, '.o_composer_thread_typing',
"should show typing info on the composer of active thread");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(),
"Someone is typing...",
"should show who is typing on the composer of active thread");
self.simulateIsTyping({
channelID: 1,
isTyping: false,
partnerID: 42,
widget: discuss,
});
await testUtils.nextMicrotaskTick();
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.containsNone($general, '.o_mail_thread_typing_icon',
"should no longer show someone is typing in sidebar of discuss");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(), "",
"should no longer show someone is typing on the composer of active thread");
discuss.destroy();
});
QUnit.test('receive message of someone that was typing something', function (assert) {
QUnit.test('receive message of someone that was typing something', async function (assert) {
assert.expect(6);
var self = this;
createDiscuss({
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
@@ -313,7 +311,7 @@ QUnit.test('receive message of someone that was typing something', function (ass
// when receiving an 'is typing' notification, fetch the list of
// members of this channel if we haven't done yet.
if (args.method === 'channel_fetch_listeners') {
return $.when([
return Promise.resolve([
{ id: self.myPartnerID, name: self.myName },
{ id: 42, name: "Someone" },
]);
@@ -321,62 +319,63 @@ QUnit.test('receive message of someone that was typing something', function (ass
return this._super.apply(this, arguments);
},
session: { partner_id: this.myPartnerID },
}).then(function (discuss) {
// click on general channel
var $general = discuss.$('.o_mail_discuss_sidebar .o_mail_discuss_item[data-thread-id=1]');
testUtils.dom.click($general);
// pick 1st composer (basic), not 2nd composer (extended, hidden)
var $composer = discuss.$('.o_thread_composer').first();
self.simulateIsTyping({
channelID: 1,
isTyping: true,
partnerID: 42,
widget: discuss,
});
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.containsOnce($general, '.o_mail_thread_typing_icon',
"should have a thread typing icon next to general icon in the sidebar");
assert.hasAttrValue($general.find('.o_mail_thread_typing_icon'), 'title',
"Someone is typing...",
"should show who is typing on hover on this thread typing icon");
assert.containsOnce($composer, '.o_composer_thread_typing',
"should show typing info on the composer of active thread");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(),
"Someone is typing...",
"should show who is typing on the composer of active thread");
// Simulate receive message from the person typing something
var messageData = {
author_id: [42, 'Someone'],
body: "<p>test</p>",
channel_ids: [1],
id: 101,
model: 'mail.channel',
res_id: 1,
};
var notification = [[false, 'mail.channel', 1], messageData];
discuss.call('bus_service', 'trigger', 'notification', [notification]);
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.containsNone($general, '.o_mail_thread_typing_icon',
"should no longer show someone is typing in sidebar of discuss, after receiving message");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(), "",
"should no longer show someone is typing on the composer of active thread, after receiving message");
discuss.destroy();
});
// click on general channel
var $general = discuss.$('.o_mail_discuss_sidebar .o_mail_discuss_item[data-thread-id=1]');
await testUtils.dom.click($general);
// pick 1st composer (basic), not 2nd composer (extended, hidden)
var $composer = discuss.$('.o_thread_composer').first();
self.simulateIsTyping({
channelID: 1,
isTyping: true,
partnerID: 42,
widget: discuss,
});
await testUtils.nextMicrotaskTick();
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.containsOnce($general, '.o_mail_thread_typing_icon',
"should have a thread typing icon next to general icon in the sidebar");
assert.hasAttrValue($general.find('.o_mail_thread_typing_icon'), 'title',
"Someone is typing...",
"should show who is typing on hover on this thread typing icon");
assert.containsOnce($composer, '.o_composer_thread_typing',
"should show typing info on the composer of active thread");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(),
"Someone is typing...",
"should show who is typing on the composer of active thread");
// Simulate receive message from the person typing something
var messageData = {
author_id: [42, 'Someone'],
body: "<p>test</p>",
channel_ids: [1],
id: 101,
model: 'mail.channel',
res_id: 1,
};
var notification = [[false, 'mail.channel', 1], messageData];
await discuss.call('bus_service', 'trigger', 'notification', [notification]);
await testUtils.nextTick();
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.containsNone($general, '.o_mail_thread_typing_icon',
"should no longer show someone is typing in sidebar of discuss, after receiving message");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(), "",
"should no longer show someone is typing on the composer of active thread, after receiving message");
discuss.destroy();
});
QUnit.test('do not display myself as typing', function (assert) {
QUnit.test('do not display myself as typing', async function (assert) {
assert.expect(2);
var self = this;
createDiscuss({
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
@@ -386,40 +385,39 @@ QUnit.test('do not display myself as typing', function (assert) {
// when receiving an 'is typing' notification, fetch the list of
// members of this channel if we haven't done yet.
if (args.method === 'channel_fetch_listeners') {
return $.when([
return Promise.resolve([
{ id: self.myPartnerID, name: self.myName },
]);
}
return this._super.apply(this, arguments);
},
session: { partner_id: this.myPartnerID },
}).then(function (discuss) {
// click on general channel
var $general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
testUtils.dom.click($general);
// pick 1st composer (basic), not 2nd composer (extended, hidden)
var $composer = discuss.$('.o_thread_composer').first();
self.simulateIsTyping({
channelID: 1,
isTyping: true,
partnerID: self.myPartnerID,
widget: discuss,
});
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.containsNone($general, '.o_mail_thread_typing_icon',
"should not show current user is typing in sidebar of discuss");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(), "",
"should not show current user is typing on the composer of active thread");
discuss.destroy();
});
// click on general channel
var $general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
await testUtils.dom.click($general);
// pick 1st composer (basic), not 2nd composer (extended, hidden)
var $composer = discuss.$('.o_thread_composer').first();
self.simulateIsTyping({
channelID: 1,
isTyping: true,
partnerID: self.myPartnerID,
widget: discuss,
});
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.containsNone($general, '.o_mail_thread_typing_icon',
"should not show current user is typing in sidebar of discuss");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(), "",
"should not show current user is typing on the composer of active thread");
discuss.destroy();
});
QUnit.test('several users typing something at the same time', function (assert) {
QUnit.test('several users typing something at the same time', async function (assert) {
// The order to display several typing partners name is not random:
// it shows at most 2 typing partners, which are the typing partners that
// have been typing something for the longest time.
@@ -428,7 +426,7 @@ QUnit.test('several users typing something at the same time', function (assert)
assert.expect(10);
var self = this;
createDiscuss({
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
@@ -438,7 +436,7 @@ QUnit.test('several users typing something at the same time', function (assert)
// when receiving an 'is typing' notification, fetch the list of
// members of this channel if we haven't done yet.
if (args.method === 'channel_fetch_listeners') {
return $.when([
return Promise.resolve([
{ id: self.myPartnerID, name: self.myName },
{ id: 42, name: "Someone" },
{ id: 43, name: "Anonymous" },
@@ -448,98 +446,101 @@ QUnit.test('several users typing something at the same time', function (assert)
return this._super.apply(this, arguments);
},
session: { partner_id: this.myPartnerID },
}).then(function (discuss) {
// click on general channel
var $general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
// click on general
testUtils.dom.click($general);
// pick 1st composer (basic), not 2nd composer (extended, hidden)
var $composer = discuss.$('.o_thread_composer').first();
self.simulateIsTyping({
channelID: 1,
isTyping: true,
partnerID: 42,
widget: discuss,
});
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.containsOnce($general, '.o_mail_thread_typing_icon',
"should have a thread typing icon next to general icon in the sidebar");
assert.hasAttrValue($general.find('.o_mail_thread_typing_icon'), 'title',
"Someone is typing...",
"should show someone is typing on hover on this thread typing icon");
assert.containsOnce($composer, '.o_composer_thread_typing',
"should show typing info on the composer of active thread");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(),
"Someone is typing...",
"should show someone is typing on the composer of active thread");
self.simulateIsTyping({
channelID: 1,
isTyping: true,
partnerID: 43,
widget: discuss,
});
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.hasAttrValue($general.find('.o_mail_thread_typing_icon'), 'title',
"Someone and Anonymous are typing...",
"should show someone and anonymous are simultaneously typing on hover on this thread typing icon");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(),
"Someone and Anonymous are typing...",
"should show someone and anonymous are simultaneously typing on the composer of active thread");
self.simulateIsTyping({
channelID: 1,
isTyping: true,
partnerID: 44,
widget: discuss,
});
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.hasAttrValue($general.find('.o_mail_thread_typing_icon'), 'title',
"Someone, Anonymous and more are typing...",
"should show 2 and more users are simultaneously typing on hover on this thread typing icon");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(),
"Someone, Anonymous and more are typing...",
"should show 2 and more users are simultaneously typing on the composer of active thread");
self.simulateIsTyping({
channelID: 1,
isTyping: false,
partnerID: 42,
widget: discuss,
});
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.hasAttrValue($general.find('.o_mail_thread_typing_icon'), 'title',
"Anonymous and Shy Guy are typing...",
"should no longer show than someone is typing on hover on this thread typing icon");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(),
"Anonymous and Shy Guy are typing...",
"should no longer show than someone is typing on the composer of active thread");
discuss.destroy();
});
// click on general channel
var $general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
// click on general
await testUtils.dom.click($general);
// pick 1st composer (basic), not 2nd composer (extended, hidden)
var $composer = discuss.$('.o_thread_composer').first();
self.simulateIsTyping({
channelID: 1,
isTyping: true,
partnerID: 42,
widget: discuss,
});
await testUtils.nextMicrotaskTick();
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.containsOnce($general, '.o_mail_thread_typing_icon',
"should have a thread typing icon next to general icon in the sidebar");
assert.hasAttrValue($general.find('.o_mail_thread_typing_icon'), 'title',
"Someone is typing...",
"should show someone is typing on hover on this thread typing icon");
assert.containsOnce($composer, '.o_composer_thread_typing',
"should show typing info on the composer of active thread");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(),
"Someone is typing...",
"should show someone is typing on the composer of active thread");
self.simulateIsTyping({
channelID: 1,
isTyping: true,
partnerID: 43,
widget: discuss,
});
await testUtils.nextMicrotaskTick();
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.hasAttrValue($general.find('.o_mail_thread_typing_icon'), 'title',
"Someone and Anonymous are typing...",
"should show someone and anonymous are simultaneously typing on hover on this thread typing icon");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(),
"Someone and Anonymous are typing...",
"should show someone and anonymous are simultaneously typing on the composer of active thread");
self.simulateIsTyping({
channelID: 1,
isTyping: true,
partnerID: 44,
widget: discuss,
});
await testUtils.nextMicrotaskTick();
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.hasAttrValue($general.find('.o_mail_thread_typing_icon'), 'title',
"Someone, Anonymous and more are typing...",
"should show 2 and more users are simultaneously typing on hover on this thread typing icon");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(),
"Someone, Anonymous and more are typing...",
"should show 2 and more users are simultaneously typing on the composer of active thread");
self.simulateIsTyping({
channelID: 1,
isTyping: false,
partnerID: 42,
widget: discuss,
});
await testUtils.nextMicrotaskTick();
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.hasAttrValue($general.find('.o_mail_thread_typing_icon'), 'title',
"Anonymous and Shy Guy are typing...",
"should no longer show than someone is typing on hover on this thread typing icon");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(),
"Anonymous and Shy Guy are typing...",
"should no longer show than someone is typing on the composer of active thread");
discuss.destroy();
});
QUnit.test('long typing partner A and in-between short typing partner B', function (assert) {
QUnit.test('long typing partner A and in-between short typing partner B', async function (assert) {
// Let's suppose that A is typing a very long message. If B types a short
// and sends it, it should display that B is typing, remove B from display
// when receiving the message, but still keep A in the typing notification.
assert.expect(8);
var self = this;
createDiscuss({
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
@@ -549,7 +550,7 @@ QUnit.test('long typing partner A and in-between short typing partner B', functi
// when receiving an 'is typing' notification, fetch the list of
// members of this channel if we haven't done yet.
if (args.method === 'channel_fetch_listeners') {
return $.when([
return Promise.resolve([
{ id: self.myPartnerID, name: self.myName },
{ id: 42, name: "A" },
{ id: 43, name: "B" },
@@ -558,76 +559,78 @@ QUnit.test('long typing partner A and in-between short typing partner B', functi
return this._super.apply(this, arguments);
},
session: { partner_id: this.myPartnerID },
}).then(function (discuss) {
// click on general channel
var $general = discuss.$('.o_mail_discuss_sidebar .o_mail_discuss_item[data-thread-id=1]');
testUtils.dom.click($general);
// pick 1st composer (basic), not 2nd composer (extended, hidden)
var $composer = discuss.$('.o_thread_composer').first();
self.simulateIsTyping({
channelID: 1,
isTyping: true,
partnerID: 42,
widget: discuss,
});
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.containsOnce($general, '.o_mail_thread_typing_icon',
"should have a thread typing icon next to general icon in the sidebar");
assert.hasAttrValue($general.find('.o_mail_thread_typing_icon'), 'title',
"A is typing...",
"should show A is typing on hover on this thread typing icon");
assert.containsOnce($composer, '.o_composer_thread_typing',
"should show typing info on the composer of active thread");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(),
"A is typing...",
"should show A is typing on the composer of active thread");
self.simulateIsTyping({
channelID: 1,
isTyping: true,
partnerID: 43,
widget: discuss,
});
// B comes after A in the display, because A is oldest typing partner.
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.hasAttrValue($general.find('.o_mail_thread_typing_icon'), 'title',
"A and B are typing...",
"should show A and B are typing on hover on this thread typing icon");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(),
"A and B are typing...",
"should show A and B are typing on the composer of active thread");
// Simulate receive message from B
var messageData = {
author_id: [43, 'Someone'],
body: "<p>test</p>",
channel_ids: [1],
id: 101,
model: 'mail.channel',
res_id: 1,
};
var notification = [[false, 'mail.channel', 1], messageData];
discuss.call('bus_service', 'trigger', 'notification', [notification]);
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.hasAttrValue($general.find('.o_mail_thread_typing_icon'), 'title',
"A is typing...",
"should show A is still typing on hover on this thread typing icon");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(),
"A is typing...",
"should show A is still typing on the composer of active thread");
discuss.destroy();
});
// click on general channel
var $general = discuss.$('.o_mail_discuss_sidebar .o_mail_discuss_item[data-thread-id=1]');
await testUtils.dom.click($general);
// pick 1st composer (basic), not 2nd composer (extended, hidden)
var $composer = discuss.$('.o_thread_composer').first();
self.simulateIsTyping({
channelID: 1,
isTyping: true,
partnerID: 42,
widget: discuss,
});
await testUtils.nextMicrotaskTick();
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.containsOnce($general, '.o_mail_thread_typing_icon',
"should have a thread typing icon next to general icon in the sidebar");
assert.hasAttrValue($general.find('.o_mail_thread_typing_icon'), 'title',
"A is typing...",
"should show A is typing on hover on this thread typing icon");
assert.containsOnce($composer, '.o_composer_thread_typing',
"should show typing info on the composer of active thread");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(),
"A is typing...",
"should show A is typing on the composer of active thread");
self.simulateIsTyping({
channelID: 1,
isTyping: true,
partnerID: 43,
widget: discuss,
});
await testUtils.nextMicrotaskTick();
// B comes after A in the display, because A is oldest typing partner.
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.hasAttrValue($general.find('.o_mail_thread_typing_icon'), 'title',
"A and B are typing...",
"should show A and B are typing on hover on this thread typing icon");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(),
"A and B are typing...",
"should show A and B are typing on the composer of active thread");
// Simulate receive message from B
var messageData = {
author_id: [43, 'Someone'],
body: "<p>test</p>",
channel_ids: [1],
id: 101,
model: 'mail.channel',
res_id: 1,
};
var notification = [[false, 'mail.channel', 1], messageData];
await discuss.call('bus_service', 'trigger', 'notification', [notification]);
await testUtils.nextTick();
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.hasAttrValue($general.find('.o_mail_thread_typing_icon'), 'title',
"A is typing...",
"should show A is still typing on hover on this thread typing icon");
assert.strictEqual($composer.find('.o_composer_thread_typing').text().trim(),
"A is typing...",
"should show A is still typing on the composer of active thread");
discuss.destroy();
});
QUnit.test('receive is typing notification from unselected thread', function (assert) {
QUnit.test('receive is typing notification from unselected thread', async function (assert) {
assert.expect(7);
this.data.initMessaging.channel_slots.channel_channel.push({
@@ -637,7 +640,7 @@ QUnit.test('receive is typing notification from unselected thread', function (as
});
var self = this;
createDiscuss({
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
@@ -647,7 +650,7 @@ QUnit.test('receive is typing notification from unselected thread', function (as
// when receiving an 'is typing' notification, fetch the list of
// members of this channel if we haven't done yet.
if (args.method === 'channel_fetch_listeners') {
return $.when([
return Promise.resolve([
{ id: self.myPartnerID, name: self.myName },
{ id: 42, name: "Someone" },
]);
@@ -655,44 +658,44 @@ QUnit.test('receive is typing notification from unselected thread', function (as
return this._super.apply(this, arguments);
},
session: { partner_id: this.myPartnerID },
}).then(function (discuss) {
var $general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
var $other = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=2]');
assert.strictEqual($general.length, 1,
"should have the channel item with id 1");
assert.hasAttrValue($general, 'title', 'general',
"should have the title 'general'");
assert.strictEqual($other.length, 1,
"should have the channel item with id 2");
assert.hasAttrValue($other, 'title', 'other',
"should have the title 'other'");
// click on general
testUtils.dom.click($general);
assert.containsNone($general, '.o_mail_thread_typing_icon',
"should no longer show someone is typing in sidebar of discuss");
self.simulateIsTyping({
channelID: 2,
isTyping: true,
partnerID: 42,
widget: discuss,
});
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
$other = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=2]');
assert.containsNone($general, '.o_mail_thread_typing_icon',
"should not have a thread typing icon next to general icon in the sidebar");
assert.containsOnce($other, '.o_mail_thread_typing_icon',
"should have a thread typing icon next to other icon in the sidebar");
discuss.destroy();
});
var $general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
var $other = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=2]');
assert.strictEqual($general.length, 1,
"should have the channel item with id 1");
assert.hasAttrValue($general, 'title', 'general',
"should have the title 'general'");
assert.strictEqual($other.length, 1,
"should have the channel item with id 2");
assert.hasAttrValue($other, 'title', 'other',
"should have the title 'other'");
// click on general
await testUtils.dom.click($general);
assert.containsNone($general, '.o_mail_thread_typing_icon',
"should no longer show someone is typing in sidebar of discuss");
self.simulateIsTyping({
channelID: 2,
isTyping: true,
partnerID: 42,
widget: discuss,
});
await testUtils.nextMicrotaskTick();
$general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
$other = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=2]');
assert.containsNone($general, '.o_mail_thread_typing_icon',
"should not have a thread typing icon next to general icon in the sidebar");
assert.containsOnce($other, '.o_mail_thread_typing_icon',
"should have a thread typing icon next to other icon in the sidebar");
discuss.destroy();
});
});
@@ -87,7 +87,7 @@ QUnit.module('Document Thread', {
},
});
QUnit.test('open a document thread in a thread window', function (assert) {
QUnit.test('open a document thread in a thread window', async function (assert) {
assert.expect(6);
var messagingMenu = new MessagingMenu();
@@ -107,13 +107,13 @@ QUnit.test('open a document thread in a thread window', function (assert) {
return this._super.apply(this, arguments);
},
});
messagingMenu.appendTo($('#qunit-fixture'));
await messagingMenu.appendTo($('#qunit-fixture'));
// toggle the messaging menu and open the documentThread
testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
assert.containsOnce(messagingMenu, '.o_mail_preview',
"should display one preview");
testUtils.dom.click(messagingMenu.$('.o_mail_preview'));
await testUtils.dom.click(messagingMenu.$('.o_mail_preview'));
assert.strictEqual($('.o_thread_window').length, 1,
"should have open the DocumentThread in a thread window");
@@ -125,7 +125,7 @@ QUnit.test('open a document thread in a thread window', function (assert) {
messagingMenu.destroy();
});
QUnit.test('expand a document thread window', function (assert) {
QUnit.test('expand a document thread window', async function (assert) {
assert.expect(4);
var messagingMenu = new MessagingMenu();
@@ -144,13 +144,13 @@ QUnit.test('expand a document thread window', function (assert) {
},
},
});
messagingMenu.appendTo($('#qunit-fixture'));
await messagingMenu.appendTo($('#qunit-fixture'));
// toggle the messaging menu and open the documentThread
testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
assert.containsOnce(messagingMenu, '.o_mail_preview',
"should display one preview");
testUtils.dom.click(messagingMenu.$('.o_mail_preview'));
await testUtils.dom.click(messagingMenu.$('.o_mail_preview'));
assert.strictEqual($('.o_thread_window').length, 1,
"should have open the DocumentThread in a thread window");
@@ -158,12 +158,12 @@ QUnit.test('expand a document thread window', function (assert) {
'Open document', "button should have correct title");
// click to expand
testUtils.dom.click($('.o_thread_window .o_thread_window_expand'));
await testUtils.dom.click($('.o_thread_window .o_thread_window_expand'));
messagingMenu.destroy();
});
QUnit.test('post messages in a document thread window', function (assert) {
QUnit.test('post messages in a document thread window', async function (assert) {
assert.expect(8);
var newMessageBody = 'Some message';
@@ -193,7 +193,7 @@ QUnit.test('post messages in a document thread window', function (assert) {
// add the message to the fake DB
this.data['mail.message'].records.push(newMessage);
this.data['some.res.model'].records[0].message_ids.push(6783);
return $.when(6783);
return Promise.resolve(6783);
}
return this._super.apply(this, arguments);
},
@@ -205,73 +205,74 @@ QUnit.test('post messages in a document thread window', function (assert) {
"should write sent message in local storage, to share info with other tabs");
}
}, true);
messagingMenu.appendTo($('#qunit-fixture'));
await messagingMenu.appendTo($('#qunit-fixture'));
// toggle the messaging menu and open the documentThread
testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
assert.containsOnce(messagingMenu, '.o_mail_preview',
"should display one preview");
testUtils.dom.click(messagingMenu.$('.o_mail_preview'));
await testUtils.dom.click(messagingMenu.$('.o_mail_preview'));
assert.strictEqual($('.o_thread_window').length, 1,
"should have open the DocumentThread in a thread window");
assert.strictEqual($('.o_thread_window .o_thread_message').length, 2,
"should display 2 messages in the thread window");
// post a message
$('.o_thread_window .o_composer_text_field')
.val(newMessageBody)
.trigger($.Event('keydown', {which: $.ui.keyCode.ENTER}));
await testUtils.fields.editAndTrigger($('.o_thread_window .o_composer_text_field'), newMessageBody,
[$.Event('keydown', {which: $.ui.keyCode.ENTER})]);
assert.strictEqual($('.o_thread_window .o_thread_message').length, 3,
"should display 3 messages in the thread window");
messagingMenu.destroy();
});
QUnit.test('open, fold, unfold and close a document thread window', function (assert) {
QUnit.test('open, fold, unfold and close a document thread window', async function (assert) {
assert.expect(8);
var messagingMenu = new MessagingMenu();
testUtils.mock.addMockEnvironment(messagingMenu, {
services: this.services,
data: this.data,
session: this.session,
});
await testUtils.nextTick();
testUtils.mock.intercept(messagingMenu, 'call_service', function (ev) {
if (ev.data.service === 'local_storage' && ev.data.method === 'setItem') {
assert.step(ev.data.args);
assert.step(JSON.stringify(ev.data.args));
}
}, true);
messagingMenu.appendTo($('#qunit-fixture'));
await messagingMenu.appendTo($('#qunit-fixture'));
// toggle the messaging menu and open the documentThread
testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
assert.containsOnce(messagingMenu, '.o_mail_preview',
"should display one preview");
testUtils.dom.click(messagingMenu.$('.o_mail_preview'));
await testUtils.dom.click(messagingMenu.$('.o_mail_preview'));
assert.strictEqual($('.o_thread_window').length, 1,
"should have open the DocumentThread in a thread window");
assert.strictEqual($('.o_thread_window .o_thread_message').length, 2,
"should display 2 messages in the thread window");
// fold and unfold thread window
testUtils.dom.click($('.o_thread_window .o_thread_window_title'));
testUtils.dom.click($('.o_thread_window .o_thread_window_title'));
await testUtils.dom.click($('.o_thread_window .o_thread_window_title'));
await testUtils.nextTick();
await testUtils.dom.click($('.o_thread_window .o_thread_window_title'));
await testUtils.nextTick();
// close thread window
testUtils.dom.click($('.o_thread_window .o_thread_window_close'));
await testUtils.dom.click($('.o_thread_window .o_thread_window_close'));
await testUtils.nextTick();
assert.verifySteps([
['mail.document_threads_state', {"some.res.model_1": {"name": "Some Record", "windowState": "open"}}],
['mail.document_threads_state', {"some.res.model_1": {"name": "Some Record", "windowState": "folded"}}],
['mail.document_threads_state', {"some.res.model_1": {"name": "Some Record", "windowState": "open"}}],
['mail.document_threads_state', {"some.res.model_1": {"name": "Some Record", "windowState": "closed"}}],
'["mail.document_threads_state",{"some.res.model_1":{"name":"Some Record","windowState":"open"}}]',
'["mail.document_threads_state",{"some.res.model_1":{"name":"Some Record","windowState":"folded"}}]',
'["mail.document_threads_state",{"some.res.model_1":{"name":"Some Record","windowState":"open"}}]',
'["mail.document_threads_state",{"some.res.model_1":{"name":"Some Record","windowState":"closed"}}]',
]);
messagingMenu.destroy();
});
QUnit.test('do not open thread window on fetch message failure', function (assert) {
QUnit.test('do not open thread window on fetch message failure', async function (assert) {
// this may happen when the user receives a notification from a document
// that he does not have access rights at the moment.
assert.expect(4);
@@ -284,17 +285,17 @@ QUnit.test('do not open thread window on fetch message failure', function (asser
mockRPC: function (route, args) {
if (args.method === 'read' && args.model === 'some.res.model' && args.args[0][0] === 1) {
assert.step('some.res.model/1/read');
return $.Deferred().reject(); // simulate failure
return Promise.reject(); // simulate failure
}
return this._super.apply(this, arguments);
},
});
messagingMenu.appendTo($('#qunit-fixture'));
await messagingMenu.appendTo($('#qunit-fixture'));
testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
assert.containsOnce(messagingMenu, '.o_mail_preview');
testUtils.dom.click(messagingMenu.$('.o_mail_preview'));
await testUtils.dom.click(messagingMenu.$('.o_mail_preview'));
assert.verifySteps(['some.res.model/1/read']);
assert.strictEqual($('.o_thread_window').length, 0,
"should not have open the DocumentThread in a thread window on fetch messages failure");
@@ -20,16 +20,16 @@ var createViewer = function (params) {
var mockRPC = function (route) {
if (route === '/web/static/lib/pdfjs/web/viewer.html?file=/web/content/1?model%3Dir.attachment') {
return $.when();
return Promise.resolve();
}
if (route === 'https://www.youtube.com/embed/FYqW0Gdwbzk') {
return $.when();
return Promise.resolve();
}
if (route === '/web/content/4?model%3Dir.attachment') {
return $.when();
return Promise.resolve();
}
if (route === '/web/image/6?unique=1&signature=999&model=ir.attachment') {
return $.when();
return Promise.resolve();
}
};
testUtils.mock.addMockEnvironment(parent, {
@@ -51,14 +51,15 @@ var createViewer = function (params) {
$target = $('body');
$target.addClass('debug');
}
viewer.appendTo($target);
// actually destroy the parent when the viewer is destroyed
viewer.destroy = function () {
delete viewer.destroy;
parent.destroy();
};
return viewer;
return viewer.appendTo($target).then(function() {
return viewer;
});
};
QUnit.module('DocumentViewer', {
@@ -74,10 +75,10 @@ QUnit.module('DocumentViewer', {
},
}, function () {
QUnit.test('basic rendering', function (assert) {
QUnit.test('basic rendering', async function (assert) {
assert.expect(7);
var viewer = createViewer({
var viewer = await createViewer({
attachmentID: 1,
attachments: this.attachments,
});
@@ -100,11 +101,11 @@ QUnit.module('DocumentViewer', {
viewer.destroy();
});
QUnit.test('Document Viewer Youtube', function (assert) {
QUnit.test('Document Viewer Youtube', async function (assert) {
assert.expect(3);
var youtubeURL = 'https://www.youtube.com/embed/FYqW0Gdwbzk';
var viewer = createViewer({
var viewer = await createViewer({
attachmentID: 2,
attachments: this.attachments,
mockRPC: function (route) {
@@ -123,10 +124,10 @@ QUnit.module('DocumentViewer', {
viewer.destroy();
});
QUnit.test('Document Viewer html/(txt)', function (assert) {
QUnit.test('Document Viewer html/(txt)', async function (assert) {
assert.expect(2);
var viewer = createViewer({
var viewer = await createViewer({
attachmentID: 4,
attachments: this.attachments,
});
@@ -139,10 +140,10 @@ QUnit.module('DocumentViewer', {
viewer.destroy();
});
QUnit.test('Document Viewer mp4', function (assert) {
QUnit.test('Document Viewer mp4', async function (assert) {
assert.expect(2);
var viewer = createViewer({
var viewer = await createViewer({
attachmentID: 5,
attachments: this.attachments,
});
@@ -155,10 +156,10 @@ QUnit.module('DocumentViewer', {
viewer.destroy();
});
QUnit.test('Document Viewer jpg', function (assert) {
QUnit.test('Document Viewer jpg', async function (assert) {
assert.expect(2);
var viewer = createViewer({
var viewer = await createViewer({
attachmentID: 6,
attachments: this.attachments,
});
@@ -171,10 +172,10 @@ QUnit.module('DocumentViewer', {
viewer.destroy();
});
QUnit.test('is closable by button', function (assert) {
QUnit.test('is closable by button', async function (assert) {
assert.expect(3);
var viewer = createViewer({
var viewer = await createViewer({
attachmentID: 6,
attachments: this.attachments,
});
@@ -184,15 +185,15 @@ QUnit.module('DocumentViewer', {
assert.containsOnce(viewer, '.o_close_btn',
"should have a close button");
testUtils.dom.click(viewer.$('.o_close_btn'));
await testUtils.dom.click(viewer.$('.o_close_btn'));
assert.ok(viewer.isDestroyed(), 'viewer should be destroyed');
});
QUnit.test('is closable by clicking on the wrapper', function (assert) {
QUnit.test('is closable by clicking on the wrapper', async function (assert) {
assert.expect(3);
var viewer = createViewer({
var viewer = await createViewer({
attachmentID: 6,
attachments: this.attachments,
});
@@ -202,7 +203,7 @@ QUnit.module('DocumentViewer', {
assert.containsOnce(viewer, '.o_viewer_img_wrapper',
"should have a wrapper");
testUtils.dom.click(viewer.$('.o_viewer_img_wrapper'));
await testUtils.dom.click(viewer.$('.o_viewer_img_wrapper'));
assert.ok(viewer.isDestroyed(), 'viewer should be destroyed');
});
+13 -13
View File
@@ -183,29 +183,29 @@ MockServer.include({
_performRpc: function (route, args) {
// routes
if (route === '/mail/init_messaging') {
return $.when(this._mockInitMessaging(args));
return Promise.resolve(this._mockInitMessaging(args));
}
// methods
if (args.method === 'channel_fetch_listeners') {
return $.when([]);
return Promise.resolve([]);
}
if (args.method === 'channel_fetch_preview') {
return $.when(this._mockChannelFetchPreview(args));
return Promise.resolve(this._mockChannelFetchPreview(args));
}
if (args.method === 'channel_minimize') {
return $.when();
return Promise.resolve();
}
if (args.method === 'channel_seen') {
return $.when();
return Promise.resolve();
}
if (args.method === 'channel_fetched') {
return $.when();
return Promise.resolve();
}
if (args.method === 'message_fetch') {
return $.when(this._mockMessageFetch(args));
return Promise.resolve(this._mockMessageFetch(args));
}
if (args.method === 'message_format') {
return $.when(this._mockMessageFormat(args));
return Promise.resolve(this._mockMessageFormat(args));
}
if (args.method === 'activity_format') {
var res = this._mockRead(args.model, args.args, args.kwargs);
@@ -217,19 +217,19 @@ MockServer.include({
}
return record;
});
return $.when(res);
return Promise.resolve(res);
}
if (args.method === 'set_message_done') {
return $.when(this._mockSetMessageDone(args));
return Promise.resolve(this._mockSetMessageDone(args));
}
if (args.method === 'moderate') {
return $.when(this._mockModerate(args));
return Promise.resolve(this._mockModerate(args));
}
if (args.method === 'notify_typing') {
return $.when();
return Promise.resolve();
}
if (args.method === 'set_message_done') {
return $.when();
return Promise.resolve();
}
return this._super(route, args);
},
@@ -26,9 +26,9 @@ var Widget = require('web.Widget');
* This is async due to mail_manager/mail_service that needs to be ready.
*
* @param {Object} params
* @return {$.Promise} resolved with the discuss widget
* @return {Promise} resolved with the discuss widget
*/
function createDiscuss(params) {
async function createDiscuss(params) {
var Parent = Widget.extend({
do_push_state: function () {},
});
@@ -49,7 +49,7 @@ function createDiscuss(params) {
parent.destroy();
};
return discuss.appendTo($(selector)).then(function () {
return discuss.appendTo($(selector)).then(function () {
return discuss;
});
}
@@ -38,7 +38,7 @@ QUnit.test('simple set im_status', function (assert) {
parent.destroy();
});
QUnit.test('multi get_im_status', function (assert) {
QUnit.test('multi get_im_status', async function (assert) {
assert.expect(9);
var readCount = 0;
var parent = testUtils.createParent({
@@ -52,7 +52,7 @@ QUnit.test('multi get_im_status', function (assert) {
assert.deepEqual(args.args[0], [2,3]);
assert.deepEqual(args.args[1], ['id', 'im_status']);
readCount++;
return $.when([
return Promise.resolve([
{id: 2, im_status: 'away'},
{id: 3, im_status: 'im_partner'}
]);
@@ -64,22 +64,27 @@ QUnit.test('multi get_im_status', function (assert) {
id: 1,
im_status: 'online',
}]);
await testUtils.nextTick();
assert.strictEqual(parent.call('mail_service', 'getImStatus', { partnerID: 1 }), 'online');
assert.strictEqual(parent.call('mail_service', 'getImStatus', { partnerID: 2 }), undefined);
assert.strictEqual(parent.call('mail_service', 'getImStatus', { partnerID: 3 }), undefined);
this.timeoutMock.runPendingTimeouts();
await testUtils.nextTick();
assert.strictEqual(parent.call('mail_service', 'getImStatus', { partnerID: 1 }), 'online');
assert.strictEqual(parent.call('mail_service', 'getImStatus', { partnerID: 2 }), 'away');
assert.strictEqual(parent.call('mail_service', 'getImStatus', { partnerID: 3 }), 'im_partner');
this.timeoutMock.runPendingTimeouts();
await testUtils.nextTick();
assert.strictEqual(readCount, 1, 'Only one read on partner should have been performed');
parent.destroy();
});
QUnit.test('update loop', function (assert) {
QUnit.test('update loop', async function (assert) {
assert.expect(12);
var readCount = 0;
var parent = testUtils.createParent({
@@ -92,7 +97,7 @@ QUnit.test('update loop', function (assert) {
assert.deepEqual(args.args[0], [1, 2]);
assert.deepEqual(args.args[1], ['id', 'im_status']);
readCount++;
return $.when([
return Promise.resolve([
{"id": 1, "im_status": "online"},
{"id": 2, "im_status": "away"}
]);
@@ -106,11 +111,14 @@ QUnit.test('update loop', function (assert) {
{ id: 2, im_status: 'offline' },
{ id: 3, im_status: 'im_partner' }, //shouldn't be updated !!!!
]);
await testUtils.nextTick();
//_updateImStatusLoop should be running at one second per iteration, lets make a minute pass.
assert.strictEqual(readCount, 0);
this.timeoutMock.addTime(50*1000);
await testUtils.nextTick();
assert.strictEqual(readCount, 0);
this.timeoutMock.addTime(1000);
await testUtils.nextTick();
assert.strictEqual(readCount, 1, 'one call should have been made after 50 seconds' );
assert.strictEqual(parent.call('mail_service', 'getImStatus', { partnerID: 1 }), 'online');
assert.strictEqual(parent.call('mail_service', 'getImStatus', { partnerID: 2 }), 'away');
@@ -118,21 +126,27 @@ QUnit.test('update loop', function (assert) {
//simulate change of focus
//original listener: $(window).on("blur", this._onWindowFocusChange.bind(this, false); + unload, ...
parent.call('mail_service', '_onWindowFocusChange', false); // remove focus from tab
await testUtils.nextTick();
this.timeoutMock.addTime(5*60*1000); // x minutes without focus, no rpc should be done during this time
await testUtils.nextTick();
assert.strictEqual(readCount, 1, 'No more call should have been performed');
//simulate change of focus
//original listener: $(window).on("focus", this._onWindowFocusChange.bind(this, true);
parent.call('mail_service', '_onWindowFocusChange', true); // give focus to tab
await testUtils.nextTick();
var nextUpdateDelay = this.timeoutMock.getNextTimeoutDelay();
await testUtils.nextTick();
assert.strictEqual(nextUpdateDelay, 1000, "next update should be done in maximum one second");
this.timeoutMock.addTime(nextUpdateDelay); // one second should be enough
await testUtils.nextTick();
assert.strictEqual(readCount, 2, 'One more call should have been done once tab focused');
this.timeoutMock.runPendingTimeouts();
await testUtils.nextTick();
parent.destroy();
});
QUnit.test('update status', function (assert) {
QUnit.test('update status', async function (assert) {
// the current solution to look for updatable im_status in dom is not perfect, but waiting for
// ability to include widgets in views, this is the most simple solution
assert.expect(2);
@@ -159,7 +173,7 @@ QUnit.test('update status', function (assert) {
throw new Error(_.str.sprintf('No rpc call should be performed: %s, %s \n %s', args.model, args.method, route));
},
});
statusWidget.appendTo($('#qunit-fixture'));
await statusWidget.appendTo($('#qunit-fixture'));
//Render unknow im_status:
// set initial status
assert.ok(statusWidget.$('.o_updatable_im_status i').hasClass('o_user_online'));
@@ -167,6 +181,7 @@ QUnit.test('update status', function (assert) {
statusWidget.call('mail_service', 'updateImStatus', [
{ id: 1, im_status: 'offline' },
]);
await testUtils.nextTick();
assert.notOk(statusWidget.$('.o_updatable_im_status i').hasClass('o_user_online'));
this.timeoutMock.runPendingTimeouts();
statusWidget.destroy();
@@ -10,7 +10,7 @@ QUnit.module('mail', {
beforeEach: function () {
var self = this;
this.timeoutCDDef = $.Deferred();
this.timeoutCDDef = testUtils.makeTestPromise();
this.patch = function () {
self.ORIGINAL_CCTFO_ON_CD_TIMEOUT = CCThrottleFunctionObject.prototype._onCooldownTimeout;
@@ -36,7 +36,7 @@ QUnit.test('cancel()', function (assert) {
assert.expect(3);
var self = this;
var def = $.Deferred();
var def = testUtils.makeTestPromise();
var step = 1;
var widget = testUtils.createParent({
@@ -52,7 +52,7 @@ QUnit.test('cancel()', function (assert) {
}
step++;
return $.when();
return Promise.resolve();
}
return this._super.apply(this, arguments);
},
@@ -80,7 +80,7 @@ QUnit.test('cancel()', function (assert) {
self.timeoutCDDef.resolve();
});
this.timeoutCDDef.then(function () {
assert.verifySteps(['__rpc__1'],
assert.verifySteps([],
"function should still have been called once after 2nd function call cancelled");
widget.destroy();
done();
@@ -92,8 +92,8 @@ QUnit.test('clear()', function (assert) {
assert.expect(5);
var self = this;
var def1 = $.Deferred();
var def2 = $.Deferred();
var def1 = testUtils.makeTestPromise();
var def2 = testUtils.makeTestPromise();
var step = 1;
var widget = testUtils.createParent({
@@ -110,7 +110,7 @@ QUnit.test('clear()', function (assert) {
}
step++;
return $.when();
return Promise.resolve();
}
return this._super.apply(this, arguments);
},
@@ -135,14 +135,14 @@ QUnit.test('clear()', function (assert) {
}).then(function () {
cctFunc(2);
}).then(function () {
assert.verifySteps(['__rpc__1'],
assert.verifySteps([],
"function should still have been called once (due to long throttle)");
cctFunc.clear();
self.timeoutCDDef.resolve();
def2.resolve();
});
$.when(self.timeoutCDDef, def2).then(function () {
assert.verifySteps(['__rpc__1', '__rpc__2'],
Promise.all([self.timeoutCDDef, def2]).then(function () {
assert.verifySteps(['__rpc__2'],
"function should have been called twice after 'clear' (buffered 2nd function call)");
widget.destroy();
done();
+14 -21
View File
@@ -1,31 +1,27 @@
odoo.define('mail.model.TimerTests', function (require) {
"use strict";
var testUtils = require("web.test_utils");
var Timer = require('mail.model.Timer');
QUnit.module('mail', {}, function () {
QUnit.module('model', {}, function () {
QUnit.module('Timer');
QUnit.test('start()', function (assert) {
var done = assert.async();
QUnit.test('start()', async function (assert) {
assert.expect(2);
var def = $.Deferred();
var func = function () {
def.resolve();
};
var prom = testUtils.makeTestPromise();
var t = new Timer({
duration: 0,
onTimeout: func,
onTimeout: prom.resolve,
});
assert.verifySteps([], "should not have called the function");
t.start();
def.then(function () {
await prom.then(function () {
assert.ok(true, "should have called the function");
done();
});
});
@@ -51,11 +47,10 @@ QUnit.test('clear()', function (assert) {
}, 0);
});
QUnit.test('reset()', function (assert) {
var done = assert.async();
QUnit.test('reset()', async function (assert) {
assert.expect(4);
var def = $.Deferred();
var prom = testUtils.makeTestPromise();
var func = function () {
assert.step('function_called');
};
@@ -67,19 +62,17 @@ QUnit.test('reset()', function (assert) {
t.start();
// Called after 1st timer timeout
await testUtils.nextTick();
assert.verifySteps(['function_called'], "should have called the function once");
t.reset();
// Called after 2nd timer timeout
setTimeout(function () {
assert.verifySteps(['function_called'], "should have called the function once");
t.reset();
// Called after 2nd timer timeout
setTimeout(function () {
def.resolve();
}, 0);
prom.resolve();
}, 0);
def.then(function () {
assert.verifySteps(['function_called', 'function_called'],
await prom.then(function () {
assert.verifySteps(['function_called'],
"should have called the function twice (2nd time from reset)");
done();
});
});
@@ -76,7 +76,7 @@ QUnit.module('ActivityMenu', {
}
});
QUnit.test('activity menu widget: menu with no records', function (assert) {
QUnit.test('activity menu widget: menu with no records', async function (assert) {
assert.expect(1);
var activityMenu = new ActivityMenu();
@@ -84,17 +84,18 @@ QUnit.test('activity menu widget: menu with no records', function (assert) {
services: this.services,
mockRPC: function (route, args) {
if (args.method === 'systray_get_activities') {
return $.when([]);
return Promise.resolve([]);
}
return this._super(route, args);
},
});
activityMenu.appendTo($('#qunit-fixture'));
assert.hasClass(activityMenu.$('.o_no_activity'),'o_no_activity', "should not have instance of widget");
await activityMenu.appendTo($('#qunit-fixture'));
await testUtils.nextTick();
assert.containsOnce(activityMenu, '.o_no_activity');
activityMenu.destroy();
});
QUnit.test('activity menu widget: activity menu with 3 records', function (assert) {
QUnit.test('activity menu widget: activity menu with 3 records', async function (assert) {
assert.expect(10);
var self = this;
var activityMenu = new ActivityMenu();
@@ -102,12 +103,13 @@ QUnit.test('activity menu widget: activity menu with 3 records', function (asser
services: this.services,
mockRPC: function (route, args) {
if (args.method === 'systray_get_activities') {
return $.when(self.data['mail.activity.menu']['records']);
return Promise.resolve(self.data['mail.activity.menu']['records']);
}
return this._super(route, args);
},
});
activityMenu.appendTo($('#qunit-fixture'));
await activityMenu.appendTo($('#qunit-fixture'));
await testUtils.nextTick();
assert.hasClass(activityMenu.$el, 'o_mail_systray_item', 'should be the instance of widget');
// the assertion below has not been replace because there are includes of ActivityMenu that modify the length.
assert.ok(activityMenu.$('.o_mail_preview').length);
@@ -123,34 +125,34 @@ QUnit.test('activity menu widget: activity menu with 3 records', function (asser
context = {
search_default_activities_overdue: 1,
};
testUtils.dom.click(activityMenu.$('.dropdown-toggle'));
await testUtils.dom.click(activityMenu.$('.dropdown-toggle'));
assert.hasClass(activityMenu.$el, 'show', 'ActivityMenu should be open');
testUtils.dom.click(activityMenu.$(".o_activity_filter_button[data-model_name='Issue'][data-filter='overdue']"));
await testUtils.dom.click(activityMenu.$(".o_activity_filter_button[data-model_name='Issue'][data-filter='overdue']"));
assert.doesNotHaveClass(activityMenu.$el, 'show', 'ActivityMenu should be closed');
// case 2: click on "today"
context = {
search_default_activities_today: 1,
};
testUtils.dom.click(activityMenu.$('.dropdown-toggle'));
testUtils.dom.click(activityMenu.$(".o_activity_filter_button[data-model_name='Issue'][data-filter='today']"));
await testUtils.dom.click(activityMenu.$('.dropdown-toggle'));
await testUtils.dom.click(activityMenu.$(".o_activity_filter_button[data-model_name='Issue'][data-filter='today']"));
// case 3: click on "future"
context = {
search_default_activities_upcoming_all: 1,
};
testUtils.dom.click(activityMenu.$('.dropdown-toggle'));
testUtils.dom.click(activityMenu.$(".o_activity_filter_button[data-model_name='Issue'][data-filter='upcoming_all']"));
await testUtils.dom.click(activityMenu.$('.dropdown-toggle'));
await testUtils.dom.click(activityMenu.$(".o_activity_filter_button[data-model_name='Issue'][data-filter='upcoming_all']"));
// case 4: click anywere else
context = {
search_default_activities_overdue: 1,
search_default_activities_today: 1,
};
testUtils.dom.click(activityMenu.$('.dropdown-toggle'));
testUtils.dom.click(activityMenu.$(".o_mail_systray_dropdown_items > div[data-model_name='Issue']"));
await testUtils.dom.click(activityMenu.$('.dropdown-toggle'));
await testUtils.dom.click(activityMenu.$(".o_mail_systray_dropdown_items > div[data-model_name='Issue']"));
activityMenu.destroy();
});
QUnit.test('activity menu widget: activity view icon', function (assert) {
QUnit.test('activity menu widget: activity view icon', async function (assert) {
assert.expect(10);
var self = this;
var activityMenu = new ActivityMenu();
@@ -158,12 +160,13 @@ QUnit.test('activity menu widget: activity view icon', function (assert) {
services: this.services,
mockRPC: function (route, args) {
if (args.method === 'systray_get_activities') {
return $.when(self.data['mail.activity.menu'].records);
return Promise.resolve(self.data['mail.activity.menu'].records);
}
return this._super(route, args);
},
});
activityMenu.appendTo($('#qunit-fixture'));
await activityMenu.appendTo($('#qunit-fixture'));
await testUtils.nextTick();
assert.containsN(activityMenu, '.o_mail_activity_action', 2,
"widget should have 2 activity view icons");
@@ -189,11 +192,11 @@ QUnit.test('activity menu widget: activity view icon', function (assert) {
}, true);
// click on the "Issue" activity icon
testUtils.dom.click(activityMenu.$('.dropdown-toggle'));
testUtils.dom.click(activityMenu.$(".o_mail_activity_action[data-model_name='Issue']"));
await testUtils.dom.click(activityMenu.$('.dropdown-toggle'));
await testUtils.dom.click(activityMenu.$(".o_mail_activity_action[data-model_name='Issue']"));
// click on the "Note" activity icon
testUtils.dom.click(activityMenu.$(".o_mail_activity_action[data-model_name='Note']"));
await testUtils.dom.click(activityMenu.$(".o_mail_activity_action[data-model_name='Note']"));
assert.verifySteps([
'do_action:Issue',
@@ -66,7 +66,7 @@ QUnit.module('MessagingMenu (Mail Failures)', {
}
});
QUnit.test('preview of mail failure', function (assert) {
QUnit.test('preview of mail failure', async function (assert) {
assert.expect(4);
this.data['mail.message'].records = [{
@@ -86,8 +86,8 @@ QUnit.test('preview of mail failure', function (assert) {
data: this.data,
services: this.services,
});
messagingMenu.appendTo($('#qunit-fixture'));
testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
await messagingMenu.appendTo($('#qunit-fixture'));
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
assert.containsOnce(messagingMenu, '.o_mail_preview',
"should display one preview for the mail failure");
@@ -101,7 +101,7 @@ QUnit.test('preview of mail failure', function (assert) {
messagingMenu.destroy();
});
QUnit.test('preview grouped failures by document', function (assert) {
QUnit.test('preview grouped failures by document', async function (assert) {
// If some failures linked to a document refers to a same document, a single
// preview should group all those failures
assert.expect(3);
@@ -142,8 +142,8 @@ QUnit.test('preview grouped failures by document', function (assert) {
data: this.data,
services: this.services,
});
messagingMenu.appendTo($('#qunit-fixture'));
testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
await messagingMenu.appendTo($('#qunit-fixture'));
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
assert.containsN(messagingMenu, '.o_mail_preview', 2,
"should have a two messaging previews for the mail failures");
@@ -156,7 +156,7 @@ QUnit.test('preview grouped failures by document', function (assert) {
messagingMenu.destroy();
});
QUnit.test('preview grouped failures by document model', function (assert) {
QUnit.test('preview grouped failures by document model', async function (assert) {
// If all failures linked to a document model refers to different documents,
// a single preview should group all failures that are linked to this
// document model
@@ -198,8 +198,8 @@ QUnit.test('preview grouped failures by document model', function (assert) {
data: this.data,
services: this.services,
});
messagingMenu.appendTo($('#qunit-fixture'));
testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
await messagingMenu.appendTo($('#qunit-fixture'));
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
assert.containsN(messagingMenu, '.o_mail_preview', 2,
"should have a two messaging previews for the mail failures");
@@ -101,7 +101,7 @@ QUnit.module('MessagingMenu', {
}
});
QUnit.test('messaging menu widget: menu with no records', function (assert) {
QUnit.test('messaging menu widget: menu with no records', async function (assert) {
assert.expect(1);
var messagingMenu = new MessagingMenu();
@@ -109,27 +109,27 @@ QUnit.test('messaging menu widget: menu with no records', function (assert) {
services: this.services,
mockRPC: function (route, args) {
if (args.method === 'message_fetch') {
return $.when([]);
return Promise.resolve([]);
}
return this._super.apply(this, arguments);
}
});
messagingMenu.appendTo($('#qunit-fixture'));
testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
await messagingMenu.appendTo($('#qunit-fixture'));
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
assert.hasClass(messagingMenu.$('.o_no_activity'),'o_no_activity', "should not have instance of widget");
messagingMenu.destroy();
});
QUnit.test('messaging menu widget: messaging menu with 1 record', function (assert) {
QUnit.test('messaging menu widget: messaging menu with 1 record', async function (assert) {
assert.expect(3);
var messagingMenu = new MessagingMenu();
testUtils.mock.addMockEnvironment(messagingMenu, {
services: this.services,
data: this.data,
});
messagingMenu.appendTo($('#qunit-fixture'));
await messagingMenu.appendTo($('#qunit-fixture'));
testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
assert.containsOnce(messagingMenu, '.o_mail_preview',
"should display a preview");
@@ -146,7 +146,7 @@ QUnit.test('messaging menu widget: messaging menu with 1 record', function (asse
messagingMenu.destroy();
});
QUnit.test('messaging menu widget: open inbox for needaction not linked to any document', function (assert) {
QUnit.test('messaging menu widget: open inbox for needaction not linked to any document', async function (assert) {
assert.expect(4);
var messagingMenu = new MessagingMenu();
@@ -157,7 +157,7 @@ QUnit.test('messaging menu widget: open inbox for needaction not linked to any d
partner_id: 1,
},
});
messagingMenu.appendTo($('#qunit-fixture'));
await messagingMenu.appendTo($('#qunit-fixture'));
// Simulate received needaction message without associated document,
// so that we have a message in inbox without a model and a resID
@@ -174,7 +174,7 @@ QUnit.test('messaging menu widget: open inbox for needaction not linked to any d
messagingMenu.call('bus_service', 'trigger', 'notification', notifications);
// Open messaging menu
testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
var $firstChannelPreview =
messagingMenu.$('.o_mail_preview').first();
@@ -190,7 +190,7 @@ QUnit.test('messaging menu widget: open inbox for needaction not linked to any d
assert.step('do_action:' + ev.data.action + ':' + ev.data.options.active_id);
}
}, true);
testUtils.dom.click($firstChannelPreview);
await testUtils.dom.click($firstChannelPreview);
assert.verifySteps(
['do_action:mail.action_discuss:mailbox_inbox'],
"should open Discuss with Inbox");
@@ -198,7 +198,7 @@ QUnit.test('messaging menu widget: open inbox for needaction not linked to any d
messagingMenu.destroy();
});
QUnit.test("messaging menu widget: mark as read on thread preview", function ( assert ) {
QUnit.test("messaging menu widget: mark as read on thread preview", async function ( assert ) {
assert.expect(8);
testUtils.mock.patch(DocumentThread, {
@@ -230,8 +230,8 @@ QUnit.test("messaging menu widget: mark as read on thread preview", function ( a
data: this.data,
});
messagingMenu.appendTo($('#qunit-fixture'));
testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
await messagingMenu.appendTo($('#qunit-fixture'));
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
assert.hasClass(messagingMenu.$el,'o_mail_systray_item',
'should be the instance of widget');
assert.hasClass(messagingMenu.$el,'show',
@@ -247,7 +247,7 @@ QUnit.test("messaging menu widget: mark as read on thread preview", function ( a
assert.containsOnce(messagingMenu, '.o_mail_preview_mark_as_read',
"should have mark as read icon next to preview");
testUtils.dom.click(messagingMenu.$(".o_mail_preview_mark_as_read"));
await testUtils.dom.click(messagingMenu.$(".o_mail_preview_mark_as_read"));
assert.verifySteps(['markedAsRead'],
"the document thread should be marked as read");
@@ -256,7 +256,7 @@ QUnit.test("messaging menu widget: mark as read on thread preview", function ( a
messagingMenu.destroy();
});
QUnit.test('needaction messages in channels should appear, in addition to channel preview', function (assert) {
QUnit.test('needaction messages in channels should appear, in addition to channel preview', async function (assert) {
// Let's suppose a channel whose before-last message (msg1) is a needaction,
// but not the last message (msg2). In that case, the systray messaging
// menu should display the needaction message msg1 in the preview, and the
@@ -295,9 +295,9 @@ QUnit.test('needaction messages in channels should appear, in addition to channe
partner_id: partnerID,
},
});
messagingMenu.appendTo($('#qunit-fixture'));
await messagingMenu.appendTo($('#qunit-fixture'));
testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
assert.containsN(messagingMenu, '.o_mail_preview', 2,
"should display two previews");
@@ -314,7 +314,7 @@ QUnit.test('needaction messages in channels should appear, in addition to channe
messagingMenu.destroy();
});
QUnit.test('preview of message on a document + mark as read', function (assert) {
QUnit.test('preview of message on a document + mark as read', async function (assert) {
assert.expect(7);
this.data.initMessaging = {
@@ -351,11 +351,11 @@ QUnit.test('preview of message on a document + mark as read', function (assert)
partner_id: partnerID,
},
});
messagingMenu.appendTo($('#qunit-fixture'));
await messagingMenu.appendTo($('#qunit-fixture'));
assert.strictEqual(messagingMenu.$('.o_notification_counter').text(), '1',
"should display a counter of 1 on the messaging menu icon");
testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
assert.containsOnce(messagingMenu, '.o_mail_preview',
"should display one preview");
@@ -367,7 +367,7 @@ QUnit.test('preview of message on a document + mark as read', function (assert)
assert.strictEqual(messagingMenu.$('.o_preview_unread .o_last_message_preview').text().replace(/\s/g, ''),
"Demo:*MessageOnDocument*", "should correctly display the preview");
testUtils.dom.click(messagingMenu.$('.o_mail_preview_mark_as_read'));
await testUtils.dom.click(messagingMenu.$('.o_mail_preview_mark_as_read'));
assert.strictEqual(messagingMenu.$('.o_notification_counter').text(), '0',
"should display a counter of 0 on the messaging menu icon");
assert.containsNone(messagingMenu, '.o_mail_preview',
@@ -376,7 +376,7 @@ QUnit.test('preview of message on a document + mark as read', function (assert)
messagingMenu.destroy();
});
QUnit.test('update messaging preview on receiving a new message in channel preview', function (assert) {
QUnit.test('update messaging preview on receiving a new message in channel preview', async function (assert) {
assert.expect(8);
var messagingMenu = new MessagingMenu();
@@ -384,9 +384,9 @@ QUnit.test('update messaging preview on receiving a new message in channel previ
services: this.services,
data: this.data,
});
messagingMenu.appendTo($('#qunit-fixture'));
await messagingMenu.appendTo($('#qunit-fixture'));
testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
assert.containsOnce(messagingMenu, '.o_mail_preview',
"should display a single channel preview");
@@ -410,7 +410,7 @@ QUnit.test('update messaging preview on receiving a new message in channel previ
};
var notification = [[false, 'mail.channel', 1], data];
messagingMenu.call('bus_service', 'trigger', 'notification', [notification]);
await testUtils.nextTick();
assert.containsOnce(messagingMenu, '.o_mail_preview',
"should still display a single channel preview");
assert.strictEqual(messagingMenu.$('.o_preview_name').text().trim(), "general",
@@ -427,7 +427,7 @@ QUnit.test('update messaging preview on receiving a new message in channel previ
messagingMenu.destroy();
});
QUnit.test('preview of inbox message not linked to document + mark as read', function (assert) {
QUnit.test('preview of inbox message not linked to document + mark as read', async function (assert) {
assert.expect(17);
this.data.initMessaging = {
@@ -459,19 +459,16 @@ QUnit.test('preview of inbox message not linked to document + mark as read', fun
},
mockRPC: function (route, args) {
if (args.method === 'set_message_done') {
assert.step({
method: 'set_message_done',
messageIDs: args.args[0],
});
assert.step('method: set_message_done, messageIDs: ' + JSON.stringify(args.args[0]));
}
return this._super.apply(this, arguments);
},
});
messagingMenu.appendTo($('#qunit-fixture'));
await messagingMenu.appendTo($('#qunit-fixture'));
assert.strictEqual(messagingMenu.$('.o_notification_counter').text(), '2',
"should display a counter of 2 on the messaging menu icon");
messagingMenu.$('.dropdown-toggle').click();
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
assert.strictEqual(messagingMenu.$('.o_mail_preview').length, 2,
"should display two previews");
@@ -494,11 +491,9 @@ QUnit.test('preview of inbox message not linked to document + mark as read', fun
assert.strictEqual($preview2.find('.o_last_message_preview').text().replace(/\s/g, ''),
"Demo:*Message2*", "should correctly display the 2nd preview");
$preview1.find('.o_mail_preview_mark_as_read').click();
assert.verifySteps([{
method: 'set_message_done',
messageIDs: [689],
}], "should mark 1st preview as read");
await testUtils.dom.click($preview1.find('.o_mail_preview_mark_as_read'));
assert.verifySteps(['method: set_message_done, messageIDs: [689]'],
"should mark 1st preview as read");
assert.strictEqual(messagingMenu.$('.o_notification_counter').text(), '1',
"should display a counter of 1 on the messaging menu icon after marking one preview as read");
assert.strictEqual(messagingMenu.$('.o_mail_preview').length, 1,
@@ -507,14 +502,9 @@ QUnit.test('preview of inbox message not linked to document + mark as read', fun
"Demo:*Message2*", "preview 2 should be the remaining one");
$preview2 = messagingMenu.$('.o_mail_preview');
$preview2.find('.o_mail_preview_mark_as_read').click();
assert.verifySteps([{
method: 'set_message_done',
messageIDs: [689],
}, {
method: 'set_message_done',
messageIDs: [690],
}], "should mark 2nd preview as read");
await testUtils.dom.click($preview2.find('.o_mail_preview_mark_as_read'));
assert.verifySteps(['method: set_message_done, messageIDs: [690]'],
"should mark 2nd preview as read");
assert.strictEqual(messagingMenu.$('.o_notification_counter').text(), '0',
"should display a counter of 0 on the messaging menu icon after marking both previews as read");
assert.strictEqual(messagingMenu.$('.o_mail_preview').length, 0,
@@ -523,7 +513,7 @@ QUnit.test('preview of inbox message not linked to document + mark as read', fun
messagingMenu.destroy();
});
QUnit.test('grouped preview for needaction messages linked to same document', function (assert) {
QUnit.test('grouped preview for needaction messages linked to same document', async function (assert) {
assert.expect(5);
// simulate two (read) needaction (mention) messages in channel 'general'
@@ -561,9 +551,9 @@ QUnit.test('grouped preview for needaction messages linked to same document', fu
partner_id: 44,
},
});
messagingMenu.appendTo($('#qunit-fixture'));
await messagingMenu.appendTo($('#qunit-fixture'));
messagingMenu.$('.dropdown-toggle').click();
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
var $previews = messagingMenu.$('.o_mail_preview');
assert.strictEqual($previews.length, 2,
@@ -586,7 +576,7 @@ QUnit.test('grouped preview for needaction messages linked to same document', fu
messagingMenu.destroy();
});
QUnit.test("messaging menu widget: channel seen notification", function (assert) {
QUnit.test("messaging menu widget: channel seen notification", async function (assert) {
assert.expect(4);
this.data.initMessaging.channel_slots = {
@@ -606,8 +596,8 @@ QUnit.test("messaging menu widget: channel seen notification", function (assert)
session: { partner_id: 3 },
});
messagingMenu.appendTo($('#qunit-fixture'));
messagingMenu.$('.dropdown-toggle').click();
await messagingMenu.appendTo($('#qunit-fixture'));
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
assert.strictEqual(messagingMenu.$('.o_notification_counter').text(), '1',
"should have correct messaging menu counter (1 unread message in channel)");
@@ -625,7 +615,7 @@ QUnit.test("messaging menu widget: channel seen notification", function (assert)
[['myDB', 'mail.channel', 1], message]
];
messagingMenu.call('bus_service', 'trigger', 'notification', notifications);
await testUtils.nextTick();
assert.strictEqual(messagingMenu.$('.o_notification_counter').text(), '0',
"should no longer have a messaging menu counter (no unread message in channel)");
assert.strictEqual(messagingMenu.$('.o_preview_counter').text().replace(/\s/g, ''), '',
@@ -634,7 +624,7 @@ QUnit.test("messaging menu widget: channel seen notification", function (assert)
messagingMenu.destroy();
});
QUnit.test("messaging menu widget: no traceback when receiving channel_fetched notification with blank thread window", function (assert) {
QUnit.test("messaging menu widget: no traceback when receiving channel_fetched notification with blank thread window", async function (assert) {
assert.expect(3);
this.data.initMessaging.channel_slots = {
@@ -654,12 +644,12 @@ QUnit.test("messaging menu widget: no traceback when receiving channel_fetched n
session: { partner_id: 3 },
});
messagingMenu.appendTo($('#qunit-fixture'));
messagingMenu.$('.dropdown-toggle').click();
await messagingMenu.appendTo($('#qunit-fixture'));
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
assert.containsOnce(messagingMenu, '.o_new_message',
"should have button to open blank thread window");
testUtils.dom.click(messagingMenu.$('.o_new_message'));
await testUtils.dom.click(messagingMenu.$('.o_new_message'));
var $threadWindow = $('.o_thread_window');
assert.strictEqual($threadWindow.length, 1, "should have an open thread window");
assert.ok($threadWindow.hasClass('o_thread_less'), "should be a blank thread window");
@@ -674,11 +664,12 @@ QUnit.test("messaging menu widget: no traceback when receiving channel_fetched n
[['myDB', 'mail.channel', 1], message]
];
messagingMenu.call('bus_service', 'trigger', 'notification', notifications);
await testUtils.nextTick();
messagingMenu.destroy();
});
QUnit.test("messaging menu widget: preview with no message should be undated", function ( assert ) {
QUnit.test("messaging menu widget: preview with no message should be undated", async function ( assert ) {
assert.expect(2);
// remove any message on the channel
@@ -691,9 +682,8 @@ QUnit.test("messaging menu widget: preview with no message should be undated", f
data: this.data,
});
messagingMenu.appendTo($('#qunit-fixture'));
messagingMenu.$('.dropdown-toggle').click();
await messagingMenu.appendTo($('#qunit-fixture'));
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
assert.strictEqual(messagingMenu.$('.o_mail_preview').length, 1,
"should have two previews in messaging menu");
@@ -704,7 +694,7 @@ QUnit.test("messaging menu widget: preview with no message should be undated", f
messagingMenu.destroy();
});
QUnit.test("messaging menu widget: sort dated previews before undated previews", function ( assert ) {
QUnit.test("messaging menu widget: sort dated previews before undated previews", async function ( assert ) {
assert.expect(5);
var dm = {
@@ -722,8 +712,8 @@ QUnit.test("messaging menu widget: sort dated previews before undated previews",
data: this.data,
});
messagingMenu.appendTo($('#qunit-fixture'));
messagingMenu.$('.dropdown-toggle').click();
await messagingMenu.appendTo($('#qunit-fixture'));
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
assert.strictEqual(messagingMenu.$('.o_mail_preview').length, 2,
"should have two previews in messaging menu");
@@ -743,7 +733,7 @@ QUnit.test("messaging menu widget: sort dated previews before undated previews",
messagingMenu.destroy();
});
QUnit.test('global counter with channel previews', function (assert) {
QUnit.test('global counter with channel previews', async function (assert) {
assert.expect(5);
var channels = [{
@@ -812,11 +802,10 @@ QUnit.test('global counter with channel previews', function (assert) {
partner_id: 3,
},
});
messagingMenu.appendTo($('#qunit-fixture'));
await messagingMenu.appendTo($('#qunit-fixture'));
assert.strictEqual(messagingMenu.$('.o_notification_counter').text(), '2',
"should display a counter of 2 on the messaging menu icon");
messagingMenu.$('.dropdown-toggle').click();
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
assert.strictEqual(messagingMenu.$('.o_mail_preview').length, 3,
"should display 3 previews");
@@ -834,7 +823,7 @@ QUnit.test('global counter with channel previews', function (assert) {
messagingMenu.destroy();
});
QUnit.test('messaging menu widget: do not open chat window twice on preview clicked', function (assert) {
QUnit.test('messaging menu widget: do not open chat window twice on preview clicked', async function (assert) {
// This test assumes that a condition for opening chat window is to
// successfully fetch messages beforehand.
assert.expect(4);
@@ -844,7 +833,7 @@ QUnit.test('messaging menu widget: do not open chat window twice on preview clic
// This is necessary `message_fetch` on mailbox_inbox is required to
// display the previews.
var lockMessageFetch = false;
var messageFetchDef = $.Deferred();
var messageFetchDef = testUtils.makeTestPromise();
var messagingMenu = new MessagingMenu();
testUtils.addMockEnvironment(messagingMenu, {
@@ -873,19 +862,20 @@ QUnit.test('messaging menu widget: do not open chat window twice on preview clic
return this._super.apply(this, arguments);
},
});
messagingMenu.appendTo($('#qunit-fixture'));
await messagingMenu.appendTo($('#qunit-fixture'));
// Opening chat window from messaging menu (pending from `messageFetchDef`)
testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
lockMessageFetch = true;
testUtils.dom.click(messagingMenu.$('.o_mail_preview'));
await testUtils.dom.click(messagingMenu.$('.o_mail_preview'));
messageFetchDef.resolve();
await testUtils.nextTick();
assert.strictEqual($('.o_thread_window').length, 1,
"should only display a single chat window");
assert.verifySteps([
'channel_minimize',
'message_fetch',
'channel_minimize',
], "should have fetched messages only once");
messagingMenu.destroy();
@@ -55,7 +55,7 @@ QUnit.module('Basic', {
},
});
QUnit.test('basic rendering thread window', function (assert) {
QUnit.test('basic rendering thread window', async function (assert) {
assert.expect(10);
var parent = this.createParent({
@@ -63,9 +63,13 @@ QUnit.test('basic rendering thread window', function (assert) {
services: this.services,
});
await testUtils.nextTick();
// detach channel 1, so that it opens corresponding thread window.
parent.call('mail_service', 'getChannel', 1).detach();
var result = parent.call('mail_service', 'getChannel', 1);
await testUtils.nextTick();
result.detach();
await testUtils.nextTick();
var $threadWindow = $('.o_thread_window');
assert.strictEqual($threadWindow.length, 1,
"thread window should be open");
@@ -98,7 +102,7 @@ QUnit.test('basic rendering thread window', function (assert) {
parent.destroy();
});
QUnit.test('close thread window using ESCAPE key', function (assert) {
QUnit.test('close thread window using ESCAPE key', async function (assert) {
assert.expect(5);
var parent = this.createParent({
@@ -107,28 +111,31 @@ QUnit.test('close thread window using ESCAPE key', function (assert) {
mockRPC: function (route, args) {
if (args.method === 'channel_fold') {
assert.ok(true, "should call channel_fold");
return $.when();
return Promise.resolve();
}
return this._super.apply(this, arguments);
},
});
await testUtils.nextTick();
// get channel instance to link to thread window
var channel = parent.call('mail_service', 'getChannel', 1);
await testUtils.nextTick();
assert.ok(channel, "there should exist a channel locally with ID 1");
channel.detach();
await testUtils.nextTick();
assert.strictEqual($('.o_thread_window').length, 1,
"there should be a thread window that is opened");
// focus on the thread window and press ESCAPE
testUtils.dom.click($('.o_thread_window .o_composer_text_field'));
await testUtils.dom.click($('.o_thread_window .o_composer_text_field'));
assert.strictEqual(document.activeElement,
$('.o_thread_window .o_composer_text_field')[0],
"thread window's input should now be focused");
var upKeyEvent = $.Event( "keyup", { which: 27 });
$('.o_thread_window .o_composer_text_field').trigger(upKeyEvent);
await testUtils.dom.triggerEvents($('.o_thread_window .o_composer_text_field'), [upKeyEvent]);
assert.strictEqual($('.o_thread_window').length, 0,
"the thread window should be closed");
@@ -136,7 +143,7 @@ QUnit.test('close thread window using ESCAPE key', function (assert) {
parent.destroy();
});
QUnit.test('thread window\'s input can still be focused when the UI is blocked', function (assert) {
QUnit.test('thread window\'s input can still be focused when the UI is blocked', async function (assert) {
assert.expect(2);
var parent = this.createParent({
@@ -147,11 +154,13 @@ QUnit.test('thread window\'s input can still be focused when the UI is blocked',
var $dom = $('#qunit-fixture');
// get channel instance to link to thread window
await testUtils.nextTick();
var channel = parent.call('mail_service', 'getChannel', 1);
await testUtils.nextTick();
channel.detach();
await testUtils.nextTick();
var $input = $('<input/>', {type: 'text'}).appendTo($dom);
testUtils.dom.click($input.focus());
await testUtils.dom.click($input.focus());
assert.strictEqual(document.activeElement, $input[0],
"fake input should be focused");
@@ -160,16 +169,17 @@ QUnit.test('thread window\'s input can still be focused when the UI is blocked',
// makes no sense, this test is just about
// making sure that the code which forces the
// focus on click is not removed
testUtils.dom.click($('.o_thread_window .o_composer_text_field'));
await testUtils.dom.click($('.o_thread_window .o_composer_text_field'));
assert.strictEqual(document.activeElement,
$('.o_thread_window .o_composer_text_field')[0],
"thread window's input should now be focused");
framework.unblockUI();
parent.destroy();
$input.remove();
});
QUnit.test('emoji popover should open correctly in thread windows', function (assert) {
QUnit.test('emoji popover should open correctly in thread windows', async function (assert) {
assert.expect(1);
var parent = this.createParent({
@@ -178,23 +188,20 @@ QUnit.test('emoji popover should open correctly in thread windows', function (as
});
// get channel instance to link to thread window
await testUtils.nextTick();
var channel = parent.call('mail_service', 'getChannel', 1);
await testUtils.nextTick();
channel.detach();
await testUtils.nextTick();
var $emojiButton = $('.o_composer_button_emoji');
testUtils.dom.click($emojiButton.trigger('focusin').focus());
await testUtils.dom.click($emojiButton.trigger('focusin').focus());
var $popover = $('.o_mail_emoji_container');
var done = assert.async();
// Async is needed as the popover focusout hiding is deferred
setTimeout(function () {
assert.ok($popover.is(':visible'), "emoji popover should have stayed opened");
parent.destroy();
done();
}, 0);
assert.ok($popover.is(':visible'), "emoji popover should have stayed opened");
parent.destroy();
});
QUnit.test('do not increase unread counter when receiving message with myself as author', function (assert) {
QUnit.test('do not increase unread counter when receiving message with myself as author', async function (assert) {
assert.expect(4);
var parent = this.createParent({
@@ -204,9 +211,11 @@ QUnit.test('do not increase unread counter when receiving message with myself as
});
// get channel instance to link to thread window
await testUtils.nextTick();
var channel = parent.call('mail_service', 'getChannel', 1);
await testUtils.nextTick();
channel.detach();
await testUtils.nextTick();
var threadWindowHeaderText = $('.o_thread_window_header').text().replace(/\s/g, "");
assert.strictEqual(threadWindowHeaderText, "#general",
@@ -226,6 +235,7 @@ QUnit.test('do not increase unread counter when receiving message with myself as
var notification = [[false, 'mail.channel', 1], messageData];
parent.call('bus_service', 'trigger', 'notification', [notification]);
await testUtils.nextTick();
threadWindowHeaderText = $('.o_thread_window_header').text().replace(/\s/g, "");
assert.strictEqual(threadWindowHeaderText, "#general",
@@ -236,7 +246,7 @@ QUnit.test('do not increase unread counter when receiving message with myself as
parent.destroy();
});
QUnit.test('do not increment unread counter with focus on thread window', function (assert) {
QUnit.test('do not increment unread counter with focus on thread window', async function (assert) {
// 'hard' focus means that the user has clicked on the thread window in
// order to set the focus on it.
assert.expect(2);
@@ -248,14 +258,17 @@ QUnit.test('do not increment unread counter with focus on thread window', functi
});
// get channel instance to link to thread window
await testUtils.nextTick();
var channel = parent.call('mail_service', 'getChannel', 1);
await testUtils.nextTick();
channel.detach();
await testUtils.nextTick();
assert.strictEqual(channel.getUnreadCounter(), 0,
"thread should have unread counter to 0 initially");
// hard focus on thread window composer
testUtils.dom.click($('.o_composer_text_field'));
await testUtils.dom.click($('.o_composer_text_field'));
// simulate receiving message from someone else
var messageData = {
@@ -268,6 +281,7 @@ QUnit.test('do not increment unread counter with focus on thread window', functi
};
var notification = [[false, 'mail.channel', 1], messageData];
parent.call('bus_service', 'trigger', 'notification', [notification]);
await testUtils.nextTick();
assert.strictEqual(channel.getUnreadCounter(), 0,
"thread should not have incremented its unread counter after receiving the message");
@@ -275,7 +289,7 @@ QUnit.test('do not increment unread counter with focus on thread window', functi
parent.destroy();
});
QUnit.test('do not mark as read the newly open thread window from received message', function (assert) {
QUnit.test('do not mark as read the newly open thread window from received message', async function (assert) {
assert.expect(5);
this.data['mail.channel'] = {
@@ -315,7 +329,7 @@ QUnit.test('do not mark as read the newly open thread window from received messa
if (args.method === 'channel_join_and_get_info') {
this.data['mail.channel'].records[0].state = 'open';
this.data['mail.channel'].records[0].is_minimized = true;
return $.when(this.data['mail.channel'].records[0]);
return Promise.resolve(this.data['mail.channel'].records[0]);
}
return this._super.apply(this, arguments);
},
@@ -338,7 +352,7 @@ QUnit.test('do not mark as read the newly open thread window from received messa
// a new message from a new channel.
this.data['mail.channel'].records[0].message_unread_counter++;
parent.call('bus_service', 'trigger', 'notification', [notification]);
await testUtils.nextTick();
var $threadWindow = $('.o_thread_window');
assert.strictEqual($threadWindow.length, 1,
"a thread window should be open after receiving a new message on a new DM chat");
@@ -347,14 +361,14 @@ QUnit.test('do not mark as read the newly open thread window from received messa
assert.strictEqual($threadWindow.find('.o_thread_window_title').text().replace(/\s/g, ''), '#DM(1)',
"open DM chat window should have one unread message");
testUtils.dom.click($threadWindow.find('.o_thread_composer'));
await testUtils.dom.click($threadWindow.find('.o_thread_composer'));
assert.strictEqual($threadWindow.find('.o_thread_window_title').text().replace(/\s/g, ''), '#DM',
"open DM chat window should have message marked as read on composer focus");
parent.destroy();
});
QUnit.test('show document link of message linked to a document', function (assert) {
QUnit.test('show document link of message linked to a document', async function (assert) {
assert.expect(6);
this.data['mail.channel'] = {
@@ -410,8 +424,11 @@ QUnit.test('show document link of message linked to a document', function (asser
"no thread window should be open initially");
// get channel instance to link to thread window
await testUtils.nextTick();
var channel = parent.call('mail_service', 'getChannel', 2);
await testUtils.nextTick();
channel.detach();
await testUtils.nextTick();
var $threadWindow = $('.o_thread_window');
assert.strictEqual($threadWindow.length, 1,
@@ -429,7 +446,7 @@ QUnit.test('show document link of message linked to a document', function (asser
parent.destroy();
});
QUnit.test('do not autofocus chat window on receiving new direct message', function (assert) {
QUnit.test('do not autofocus chat window on receiving new direct message', async function (assert) {
// Receiving a message doesn't make other input loose focus
assert.expect(3);
@@ -473,7 +490,7 @@ QUnit.test('do not autofocus chat window on receiving new direct message', funct
}],
};
var form = testUtils.createView({
var form = await testUtils.createView({
View: FormView,
model: 'partner',
data: this.data,
@@ -487,7 +504,7 @@ QUnit.test('do not autofocus chat window on receiving new direct message', funct
},
mockRPC: function (route, args) {
if (args.method === 'channel_join_and_get_info') {
return $.when(this.data['mail.channel'].records[0]);
return Promise.resolve(this.data['mail.channel'].records[0]);
}
return this._super.apply(this, arguments);
}
@@ -512,7 +529,7 @@ QUnit.test('do not autofocus chat window on receiving new direct message', funct
this.data['mail.message'].records.push(messageData);
var notification = [[false, 'mail.channel', 2], messageData];
form.call('bus_service', 'trigger', 'notification', [notification]);
await testUtils.nextTick();
assert.ok($('.o_thread_window.o_in_home_menu').length,
'Chat window is opened');
@@ -522,7 +539,7 @@ QUnit.test('do not autofocus chat window on receiving new direct message', funct
form.destroy();
});
QUnit.test('do not auto-focus chat window on receiving new message from new DM', function (assert) {
QUnit.test('do not auto-focus chat window on receiving new message from new DM', async function (assert) {
assert.expect(10);
var self = this;
@@ -564,7 +581,7 @@ QUnit.test('do not auto-focus chat window on receiving new message from new DM',
session: { partner_id: 3 },
mockRPC: function (route, args) {
if (args.method === 'channel_join_and_get_info') {
return $.when(_.extend({}, self.data['mail.channel'].records[0], { info: 'join' }));
return Promise.resolve(_.extend({}, self.data['mail.channel'].records[0], { info: 'join' }));
}
if (args.method === 'channel_minimize') {
_.extend(self.data['mail.channel'].records[0], {
@@ -594,6 +611,7 @@ QUnit.test('do not auto-focus chat window on receiving new message from new DM',
this.data['mail.message'].records.push(messageData);
var notification = [[false, 'mail.channel', 2], messageData];
parent.call('bus_service', 'trigger', 'notification', [notification]);
await testUtils.nextTick();
assert.strictEqual($('.o_thread_window').length, 1,
"should have DM window open");
@@ -610,6 +628,7 @@ QUnit.test('do not auto-focus chat window on receiving new message from new DM',
});
notification = [[false, 'res.partner', 3], dmInfo];
parent.call('bus_service', 'trigger', 'notification', [notification]);
await testUtils.nextTick();
assert.strictEqual($('.o_thread_window').length, 1,
"should still have DM window open after receiving DM info from polling");
@@ -621,6 +640,7 @@ QUnit.test('do not auto-focus chat window on receiving new message from new DM',
// simulate receiving detached DM notification (cross-tab synchronization)
notification = [[false, 'res.partner', 3], self.data['mail.channel'].records[0]];
parent.call('bus_service', 'trigger', 'notification', [notification]);
await testUtils.nextTick();
assert.strictEqual($('.o_thread_window').length, 1,
"should still have DM open after receiving detached info from polling");
@@ -632,7 +652,7 @@ QUnit.test('do not auto-focus chat window on receiving new message from new DM',
parent.destroy();
});
QUnit.test('out-of-office status in thread window', function (assert) {
QUnit.test('out-of-office status in thread window', async function (assert) {
assert.expect(1);
this.data = {
'mail.message': {
@@ -655,8 +675,13 @@ QUnit.test('out-of-office status in thread window', function (assert) {
data: this.data,
services: this.services,
});
await testUtils.nextTick();
// detach channel 1, so that it opens corresponding thread window.
parent.call('mail_service', 'getChannel', 1).detach();
var channel = parent.call('mail_service', 'getChannel', 1);
await testUtils.nextTick();
channel.detach();
await testUtils.nextTick();
var $threadWindow = $('.o_thread_window');
assert.containsOnce($threadWindow, '.o_out_of_office_text');
@@ -664,7 +689,7 @@ QUnit.test('out-of-office status in thread window', function (assert) {
parent.destroy();
});
QUnit.test('no out-of-office status in thread window', function (assert) {
QUnit.test('no out-of-office status in thread window', async function (assert) {
assert.expect(1);
this.data = {
'mail.message': {
@@ -687,11 +712,17 @@ QUnit.test('no out-of-office status in thread window', function (assert) {
data: this.data,
services: this.services,
});
await testUtils.nextTick();
// detach channel 1, so that it opens corresponding thread window.
parent.call('mail_service', 'getChannel', 1).detach();
var channel = parent.call('mail_service', 'getChannel', 1);
await testUtils.nextTick();
channel.detach();
await testUtils.nextTick();
var $threadWindow = $('.o_thread_window');
assert.containsNone($threadWindow, '.o_out_of_office_text');
parent.destroy();
});
@@ -53,8 +53,8 @@ QUnit.module('Blank', {
* functions end.
*
* @param {Object} params
* @param {$.Deferred} params.selectDef
* @param {$.Deferred} params.sourceDef
* @param {Promise} params.selectDef
* @param {Promise} params.sourceDef
*/
this.patchAutocomplete = function (params) {
var selectDef = params.selectDef;
@@ -87,7 +87,7 @@ QUnit.module('Blank', {
},
});
QUnit.test('basic rendering blank thread window', function (assert) {
QUnit.test('basic rendering blank thread window', async function (assert) {
assert.expect(5);
var parent = this.createParent({
@@ -97,7 +97,7 @@ QUnit.test('basic rendering blank thread window', function (assert) {
// open blank thread window
parent.call('mail_service', 'openBlankThreadWindow');
await testUtils.nextMicrotaskTick();
assert.strictEqual($('.o_thread_window').length, 1,
"should have a thread window open");
assert.strictEqual($('.o_thread_window_title').text().trim(),
@@ -114,7 +114,7 @@ QUnit.test('basic rendering blank thread window', function (assert) {
parent.destroy();
});
QUnit.test('close blank thread window', function (assert) {
QUnit.test('close blank thread window', async function (assert) {
assert.expect(1);
var parent = this.createParent({
@@ -124,8 +124,9 @@ QUnit.test('close blank thread window', function (assert) {
// open blank thread window
parent.call('mail_service', 'openBlankThreadWindow');
await testUtils.nextMicrotaskTick();
testUtils.dom.click($('.o_thread_window_close'));
await testUtils.dom.click($('.o_thread_window_close'));
assert.strictEqual($('.o_thread_window').length, 0,
"blank thread window should be closed");
@@ -133,10 +134,10 @@ QUnit.test('close blank thread window', function (assert) {
parent.destroy();
});
QUnit.test('fold blank thread window', function (assert) {
QUnit.test('fold blank thread window', async function (assert) {
// This test requires full height of thread windows when they are open.
// (e.g. 400px)
assert.expect(2);
assert.expect(3);
var parent = this.createParent({
data: this.data,
@@ -153,34 +154,37 @@ QUnit.test('fold blank thread window', function (assert) {
// Open blank thread window
parent.call('mail_service', 'openBlankThreadWindow');
await testUtils.nextTick();
assert.containsOnce(document.body, '.o_thread_window');
assert.strictEqual($('.o_thread_window').css('height'), HEIGHT_OPEN,
"blank thread window should be open");
if ($('.o_thread_window').css('height') !== HEIGHT_OPEN) {
console.warn('Assertion above may fail due to too narrow height of browser');
}
testUtils.dom.click($('.o_thread_window_title'));
await testUtils.dom.click($('.o_thread_window_title'));
assert.strictEqual($('.o_thread_window').css('height'), HEIGHT_FOLDED,
"blank thread window should be open");
"blank thread window should be folded");
parent.destroy();
testUtils.mock.unpatch(AbstractThreadWindow);
});
QUnit.test('open new DM chat from blank thread window', function (assert) {
QUnit.test('open new DM chat from blank thread window', async function (assert) {
assert.expect(6);
var done = assert.async();
var self = this;
var selectDef = $.Deferred();
var sourceDef = $.Deferred();
var selectDef = testUtils.makeTestPromise();
var sourceDef = testUtils.makeTestPromise();
this.patchAutocomplete({
selectDef: selectDef,
sourceDef: sourceDef,
});
var def = $.Deferred();
var def = testUtils.makeTestPromise();
this.data['res.partner'] = {
fields: {},
@@ -193,13 +197,13 @@ QUnit.test('open new DM chat from blank thread window', function (assert) {
mockRPC: function (route, args) {
if (args.method === 'im_search') {
def.resolve();
return $.when([
return Promise.resolve([
{ id: 1, name: 'DemoUser1' },
{ id: 2, name: 'DemoUser2', }
]);
}
if (args.method === 'channel_get_and_minimize') {
return $.when({
return Promise.resolve({
channel_type: 'chat',
direct_partner: [{ id: args.args[0][0], name: 'DemoUser1', im_status: '' }],
id: 50,
@@ -213,19 +217,20 @@ QUnit.test('open new DM chat from blank thread window', function (assert) {
// open blank thread window
parent.call('mail_service', 'openBlankThreadWindow');
await testUtils.nextMicrotaskTick();
await testUtils.fields.editAndTrigger($('.o_thread_search_input input'), 'D', 'keydown');
await testUtils.nextTick();
await Promise.all([sourceDef, def]);
await testUtils.nextTick();
assert.strictEqual($('.ui-menu-item a').length, 2,
"should suggest 2 partners for DM");
assert.strictEqual($('.ui-menu-item a').eq(0).text(), "DemoUser1",
"first suggestion should be 'DemoUser1'");
assert.strictEqual($('.ui-menu-item a').eq(1).text(), "DemoUser2",
"second suggestion should be 'DemoUser2'");
$('.o_thread_search_input input').val("D").trigger('keydown');
$.when(sourceDef, def).then(function () {
assert.strictEqual($('.ui-menu-item a').length, 2,
"should suggest 2 partners for DM");
assert.strictEqual($('.ui-menu-item a').eq(0).text(), "DemoUser1",
"first suggestion should be 'DemoUser1'");
assert.strictEqual($('.ui-menu-item a').eq(1).text(), "DemoUser2",
"second suggestion should be 'DemoUser2'");
testUtils.dom.click($('.ui-menu-item a').eq(0));
});
await testUtils.dom.clickFirst($('.ui-menu-item a'),{allowInvisible:true});
await testUtils.nextTick();
selectDef.then(function () {
assert.strictEqual($('.o_thread_window').length, 1,
"should be a single window");
@@ -240,22 +245,22 @@ QUnit.test('open new DM chat from blank thread window', function (assert) {
});
});
QUnit.test('open already detached DM chat from blank thread window', function (assert) {
QUnit.test('open already detached DM chat from blank thread window', async function (assert) {
// when opening an already detach DM chat from the blank thread window,
// the blank thread window should disappear
assert.expect(6);
var done = assert.async();
var self = this;
var selectDef = $.Deferred();
var sourceDef = $.Deferred();
var selectDef = testUtils.makeTestPromise();
var sourceDef = testUtils.makeTestPromise();
this.patchAutocomplete({
selectDef: selectDef,
sourceDef: sourceDef,
});
var def = $.Deferred();
var def = testUtils.makeTestPromise();
this.data['res.partner'] = {
fields: {},
@@ -275,7 +280,7 @@ QUnit.test('open already detached DM chat from blank thread window', function (a
mockRPC: function (route, args) {
if (args.method === 'im_search') {
def.resolve();
return $.when([
return Promise.resolve([
{ id: 1, name: 'DemoUser1' },
]);
}
@@ -283,6 +288,7 @@ QUnit.test('open already detached DM chat from blank thread window', function (a
},
});
await testUtils.nextTick();
assert.strictEqual($('.o_thread_window').length, 1,
"should be a single window");
assert.strictEqual($('.o_thread_window_title').text().trim(), "DemoUser1",
@@ -292,15 +298,16 @@ QUnit.test('open already detached DM chat from blank thread window', function (a
// open blank thread window
parent.call('mail_service', 'openBlankThreadWindow');
await testUtils.nextMicrotaskTick();
assert.strictEqual($('.o_thread_window').length, 2,
"should have two thread windows open");
await testUtils.fields.editAndTrigger($('.o_thread_search_input input'), 'D', 'keydown');
$('.o_thread_search_input input').val("D").trigger('keydown');
$.when(sourceDef, def).then(function () {
testUtils.dom.click($('.ui-menu-item a').eq(0));
});
await Promise.all([sourceDef, def]);
await testUtils.nextTick();
await testUtils.dom.click($('.ui-menu-item a'),{allowInvisible:true});
await testUtils.nextTick();
selectDef.then(function () {
assert.strictEqual($('.o_thread_window').length, 1,
"should now have a single thread window open");
@@ -48,12 +48,15 @@ QUnit.module('Hidden', {
};
},
afterEach: function () {
// remove thread window dropup appended by mail service
var $target = $(this.services.mail_service.prototype.THREAD_WINDOW_APPENDTO);
$target.find('.o_thread_window_dropdown').remove();
// reset thread window append to body
this.services.mail_service.prototype.THREAD_WINDOW_APPENDTO = 'body';
},
});
QUnit.test('hidden thread windows dropdown when not enough horizontal space (version 1)', function (assert) {
QUnit.test('hidden thread windows dropdown when not enough horizontal space (version 1)', async function (assert) {
// This test has hard-set global width, so that it does not depend on
// the width of the screen: at most 2 thread windows visible at the
// same time, and additional thread windows are in hidden while and
@@ -87,9 +90,10 @@ QUnit.test('hidden thread windows dropdown when not enough horizontal space (ver
data: this.data,
services: this.services,
});
await testUtils.nextTick();
parent.call('mail_service', 'getChannel', 0).detach();
parent.call('mail_service', 'getChannel', 1).detach();
await testUtils.nextTick();
var $visibleThreadWindows = $('.o_thread_window:not(.o_thread_window_dropdown, .o_hidden)');
@@ -106,6 +110,7 @@ QUnit.test('hidden thread windows dropdown when not enough horizontal space (ver
// detach a channel so that it exceeds available slots
parent.call('mail_service', 'getChannel', 2).detach();
await testUtils.nextTick();
// update list of visible thread windows
$visibleThreadWindows = $('.o_thread_window:not(.o_thread_window_dropdown, .o_hidden)');
@@ -131,7 +136,7 @@ QUnit.test('hidden thread windows dropdown when not enough horizontal space (ver
testUtils.mock.unpatch(this.services.mail_service);
});
QUnit.test('hidden thread windows dropdown when not enough horizontal space (version 2)', function (assert) {
QUnit.test('hidden thread windows dropdown when not enough horizontal space (version 2)', async function (assert) {
// This is almost the same test as the one before, except there are
// at most 3 thread windows visible, and 2 thread windows are visible
// when it shows the 'hidden thread window' button.
@@ -171,10 +176,11 @@ QUnit.test('hidden thread windows dropdown when not enough horizontal space (ver
data: this.data,
services: this.services,
});
await testUtils.nextTick();
parent.call('mail_service', 'getChannel', 0).detach();
parent.call('mail_service', 'getChannel', 1).detach();
parent.call('mail_service', 'getChannel', 2).detach();
await testUtils.nextTick();
var $visibleThreadWindows = $('.o_thread_window:not(.o_thread_window_dropdown, .o_hidden)');
@@ -193,6 +199,7 @@ QUnit.test('hidden thread windows dropdown when not enough horizontal space (ver
// detach a channel so that it exceeds available slots
parent.call('mail_service', 'getChannel', 3).detach();
await testUtils.nextTick();
// update list of visible thread windows
$visibleThreadWindows = $('.o_thread_window:not(.o_thread_window_dropdown, .o_hidden)');
@@ -84,7 +84,7 @@ QUnit.module('Moderation', {
},
});
QUnit.test('moderator: moderated channel with pending moderation message', function (assert) {
QUnit.test('moderator: moderated channel with pending moderation message', async function (assert) {
assert.expect(5);
this.data.initMessaging = {
@@ -114,9 +114,10 @@ QUnit.test('moderator: moderated channel with pending moderation message', funct
data: this.data,
services: this.services,
});
await testUtils.nextTick();
// detach channel 1, so that it opens corresponding thread window.
parent.call('mail_service', 'getChannel', 1).detach();
await testUtils.nextTick();
assert.strictEqual($('.o_thread_message ').length, 1,
"should display a message in the thread window");
@@ -40,7 +40,7 @@ QUnit.module('Typing', {
* @param {integer} params.partnerID
* @param {Widget} params.widget a widget that can call the bus_service
*/
this.simulateIsTyping = function (params) {
this.simulateIsTyping = async function (params) {
var typingData = {
info: 'typing_status',
partner_id: params.partnerID,
@@ -48,6 +48,7 @@ QUnit.module('Typing', {
};
var notification = [[false, 'mail.channel', params.channelID], typingData];
params.widget.call('bus_service', 'trigger', 'notification', [notification]);
await testUtils.nextMicrotaskTick();
};
this.createParent = function (params) {
@@ -72,7 +73,7 @@ QUnit.module('Typing', {
},
});
QUnit.test('receive typing notification', function (assert) {
QUnit.test('receive typing notification', async function (assert) {
assert.expect(4);
var parent = this.createParent({
@@ -82,22 +83,24 @@ QUnit.test('receive typing notification', function (assert) {
// when receiving an 'is typing' notification, fetch the list of
// members of this channel if we haven't done yet.
if (args.method === 'channel_fetch_listeners') {
return $.when([
return Promise.resolve([
{ id: 42, name: "Someone" },
]);
}
return this._super.apply(this, arguments);
},
});
await testUtils.nextTick();
// detach channel 1, so that it opens corresponding thread window.
parent.call('mail_service', 'getChannel', 1).detach();
await testUtils.nextTick();
var $threadWindow = $('.o_thread_window');
assert.containsNone($threadWindow, '.o_mail_thread_typing_icon',
"should not display an icon that someone is typing something on this thread");
this.simulateIsTyping({
await this.simulateIsTyping({
channelID: 1,
isTyping: true,
partnerID: 42,
@@ -110,7 +113,7 @@ QUnit.test('receive typing notification', function (assert) {
"Someone is typing...",
"should display someone is typing something on hover of this icon");
this.simulateIsTyping({
await this.simulateIsTyping({
channelID: 1,
isTyping: false,
partnerID: 42,
@@ -7,7 +7,7 @@ var createDebugManager = testUtils.createDebugManager;
QUnit.module('Mail DebugManager', {}, function () {
QUnit.test("Manage Messages", function (assert) {
QUnit.test("Manage Messages", async function (assert) {
assert.expect(3);
var debugManager = createDebugManager({
@@ -47,15 +47,16 @@ QUnit.module('Mail DebugManager', {}, function () {
},
modelName: 'testModel',
};
debugManager.update('action', action, view);
await testUtils.nextTick();
await debugManager.update('action', action, view);
var $messageMenu = debugManager.$('a[data-action=getMailMessages]');
assert.strictEqual($messageMenu.length, 1, "should have Manage Message menu item");
assert.strictEqual($messageMenu.text().trim(), "Manage Messages",
"should have correct menu item text");
testUtils.dom.click(debugManager.$('> a')); // open dropdown
testUtils.dom.click($messageMenu);
await testUtils.dom.click(debugManager.$('> a')); // open dropdown
await testUtils.dom.click($messageMenu);
debugManager.destroy();
});