diff --git a/addons/mail/static/src/js/discuss.js b/addons/mail/static/src/js/discuss.js index 032ab1bddef..e93adeaae30 100644 --- a/addons/mail/static/src/js/discuss.js +++ b/addons/mail/static/src/js/discuss.js @@ -852,7 +852,7 @@ var Discuss = AbstractAction.extend(ControlPanelMixin, { // Update control panel before focusing the composer, otherwise // focus is on the searchview - self.set("title", '#' + self._thread.getName()); + self.set("title", self._thread.getTitle()); self._updateControlPanel(); self._updateControlPanelButtons(self._thread); diff --git a/addons/mail/static/src/js/models/threads/channel.js b/addons/mail/static/src/js/models/threads/channel.js index 808e8b7fbca..ff90ca8edfb 100644 --- a/addons/mail/static/src/js/models/threads/channel.js +++ b/addons/mail/static/src/js/models/threads/channel.js @@ -217,20 +217,10 @@ var Channel = SearchableThread.extend(ThreadTypingMixin, { return _.extend(result, { author: lastMessage ? lastMessage.getDisplayedAuthor() : '', body: lastMessage ? mailUtils.parseAndTransform(lastMessage.getBody(), mailUtils.inline) : '', - date: lastMessage ? lastMessage.getDate() : moment(), + date: lastMessage ? lastMessage.getDate() : undefined, isMyselfAuthor: this.hasMessages() && this.getLastMessage().isMyselfAuthor(), }); }, - /** - * Returns the title to display in thread window's headers. - * For channels, the title is prefixed with "#". - * - * @override - * @returns {string|Object} the name of the thread by default (see getName) - */ - getTitle: function () { - return "#" + this._super.apply(this, arguments); - }, /** * Get the UUID of the channel. * This is a string that uniquely links this channel to a server channel. diff --git a/addons/mail/static/src/js/models/threads/multi_user_channel.js b/addons/mail/static/src/js/models/threads/multi_user_channel.js index c1681744127..85d60fc1183 100644 --- a/addons/mail/static/src/js/models/threads/multi_user_channel.js +++ b/addons/mail/static/src/js/models/threads/multi_user_channel.js @@ -25,6 +25,16 @@ var MultiUserChannel = Channel.extend({ // Public //-------------------------------------------------------------------------- + /** + * Returns the title to display in thread window's headers. + * For channels, the title is prefixed with "#". + * + * @override + * @returns {string|Object} the name of the thread by default (see getName) + */ + getTitle: function () { + return "#" + this._super.apply(this, arguments); + }, /** * States whether this thread has the mass mailing setting active or not. * This is a server-side setting, that determine the type of composer that diff --git a/addons/mail/static/src/js/services/mail_manager.js b/addons/mail/static/src/js/services/mail_manager.js index c21089b7095..6a49cdf0183 100644 --- a/addons/mail/static/src/js/services/mail_manager.js +++ b/addons/mail/static/src/js/services/mail_manager.js @@ -1108,8 +1108,9 @@ var MailManager = AbstractService.extend({ * Sort previews * * 1. unread, - * 2. two-user thread, - * 3. date, + * 2. dated previews + * 3. two-user thread, + * 4. date, * * @private * @param {Object[]} previews @@ -1118,13 +1119,10 @@ var MailManager = AbstractService.extend({ _sortPreviews: function (previews) { var res = previews.sort(function (p1, p2) { var unreadDiff = Math.min(1, p2.unreadCounter) - Math.min(1, p1.unreadCounter); + var datedDiff = !!p2.date - !!p1.date; var isTwoUserThreadDiff = p2.isTwoUserThread - p1.isTwoUserThread; - var dateDiff = (!!p2.date - !!p1.date) || - ( - p2.date && - p2.date.diff(p1.date) - ); - return unreadDiff || isTwoUserThreadDiff || dateDiff; + var dateDiff = p2.date && p2.date.diff(p1.date); + return unreadDiff || datedDiff || isTwoUserThreadDiff || dateDiff; }); return res; }, diff --git a/addons/mail/static/tests/discuss_tests.js b/addons/mail/static/tests/discuss_tests.js index 31f479d50cc..e22d24f5002 100644 --- a/addons/mail/static/tests/discuss_tests.js +++ b/addons/mail/static/tests/discuss_tests.js @@ -513,6 +513,85 @@ QUnit.test('@ mention with special chars', function (assert) { }); }); +QUnit.test('@ mention in mailing channel', function (assert) { + assert.expect(8); + var done = assert.async(); + + var fetchListenersDef = $.Deferred(); + + this.data.initMessaging = { + channel_slots: { + channel_channel: [{ + id: 1, + channel_type: "channel", + name: "general", + mass_mailing: true, + }], + }, + }; + + createDiscuss({ + id: 1, + context: {}, + params: {}, + data: this.data, + services: this.services, + mockRPC: function (route, args) { + if (args.method === 'channel_fetch_listeners') { + fetchListenersDef.resolve(); + return $.when([ + {id: 1, name: 'Admin'}, + ]); + } + 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]'); + + // click on general + $general.click(); + // 1st composer: basic composer (hidden), 2nd composer: extended (shown) + var $input = discuss.$('textarea.o_composer_text_field').eq(1); + assert.ok($input.length, "should display a composer input"); + + // Simulate '@' typed by user with mocked Window.getSelection + // Note: focus is needed in order to trigger rpc 'channel_fetch_listeners' + $input.focus(); + $input.val("@"); + $input.trigger('keyup'); + + fetchListenersDef + .then(concurrency.delay.bind(concurrency, 0)) + .then(function () { + assert.strictEqual(discuss.$('.dropup.o_composer_mention_dropdown.show').length, 1, + "dropup menu for partner mentions should be open"); + + var $mention = discuss.$('.o_mention_proposition'); + assert.strictEqual($mention.length, 1, + "should display 1 partner mention proposition"); + + // correct mention proposition + assert.ok($mention.hasClass('active'), + "partner mention should be active"); + assert.strictEqual($mention.data('id'), 1, + "partner mention should link to the correct partner id"); + assert.strictEqual($mention.find('.o_mention_name').text(), "Admin", + "partner mention should display the correct partner name"); + + // equivalent to $mentionPropositions.find('active').click(); + $input.trigger($.Event('keyup', {which: $.ui.keyCode.ENTER})); + + assert.ok($input.is(':focus'), "composer body should have focus"); + assert.notOk(discuss.$('.o_composer_subject').is(':focus')); + + discuss.destroy(); + done(); + }); + }); +}); + QUnit.test('no crash focusout emoji button', function (assert) { assert.expect(3); var done = assert.async(); diff --git a/addons/mail/static/tests/systray/systray_messaging_menu_tests.js b/addons/mail/static/tests/systray/systray_messaging_menu_tests.js index 2df0f7ac522..f67703c4679 100644 --- a/addons/mail/static/tests/systray/systray_messaging_menu_tests.js +++ b/addons/mail/static/tests/systray/systray_messaging_menu_tests.js @@ -625,5 +625,70 @@ QUnit.test("messaging menu widget: channel seen notification", function ( assert messagingMenu.destroy(); }); +QUnit.test("messaging menu widget: preview with no message should be undated", function ( assert ) { + assert.expect(2); + + // remove any message on the channel + this.data['mail.channel'].records[0].channel_message_ids = []; + this.data['mail.message'].records[0].channel_ids = []; + + var messagingMenu = new MessagingMenu(); + testUtils.addMockEnvironment(messagingMenu, { + services: this.services, + data: this.data, + }); + + messagingMenu.appendTo($('#qunit-fixture')); + + messagingMenu.$('.dropdown-toggle').click(); + + assert.strictEqual(messagingMenu.$('.o_mail_preview').length, 1, + "should have two previews in messaging menu"); + + assert.ok(_.isEmpty(messagingMenu.$('.o_mail_preview .o_last_message_date').text().trim()), + "should have preview as undated"); + + messagingMenu.destroy(); +}); + +QUnit.test("messaging menu widget: sort dated previews before undated previews", function ( assert ) { + assert.expect(5); + + var dm = { + id: 2, + name: "DM", + channel_type: "chat", + }; + + this.data['mail.channel'].records.push(dm); + this.data.initMessaging.channel_slots.direct_message = [dm]; + + var messagingMenu = new MessagingMenu(); + testUtils.addMockEnvironment(messagingMenu, { + services: this.services, + data: this.data, + }); + + messagingMenu.appendTo($('#qunit-fixture')); + messagingMenu.$('.dropdown-toggle').click(); + + assert.strictEqual(messagingMenu.$('.o_mail_preview').length, 2, + "should have two previews in messaging menu"); + + var $preview1 = messagingMenu.$('.o_mail_preview').eq(0); + var $preview2 = messagingMenu.$('.o_mail_preview').eq(1); + + assert.strictEqual($preview1.data('preview-id'), 1, + "should have channel preview as the 1st preview (dated preview)"); + assert.notOk(_.isEmpty($preview1.find('.o_last_message_date').text().trim()), + "should have channel preview as dated"); + assert.strictEqual($preview2.data('preview-id'), 2, + "should have DM preview as the 1st preview (non-dated preview)"); + assert.ok(_.isEmpty($preview2.find('.o_last_message_date').text().trim()), + "should have DM preview as undated"); + + messagingMenu.destroy(); +}); + }); }); diff --git a/addons/mail/static/tests/thread_window/blank_thread_window_tests.js b/addons/mail/static/tests/thread_window/blank_thread_window_tests.js index 883494a4ec6..17654290c80 100644 --- a/addons/mail/static/tests/thread_window/blank_thread_window_tests.js +++ b/addons/mail/static/tests/thread_window/blank_thread_window_tests.js @@ -229,7 +229,7 @@ QUnit.test('open new DM chat from blank thread window', function (assert) { selectDef.then(function () { assert.strictEqual($('.o_thread_window').length, 1, "should be a single window"); - assert.strictEqual($('.o_thread_window_title').text().trim(), "#DemoUser1", + assert.strictEqual($('.o_thread_window_title').text().trim(), "DemoUser1", "should have thread window of the DM chat with 'DemoUser1'"); assert.strictEqual($('.o_composer_text_field').length, 1, "should have a composer in the DM chat window"); @@ -285,7 +285,7 @@ QUnit.test('open already detached DM chat from blank thread window', function (a assert.strictEqual($('.o_thread_window').length, 1, "should be a single window"); - assert.strictEqual($('.o_thread_window_title').text().trim(), "#DemoUser1", + assert.strictEqual($('.o_thread_window_title').text().trim(), "DemoUser1", "should have thread window of the DM chat with 'DemoUser1'"); assert.strictEqual($('.o_composer_text_field').length, 1, "should have a composer in the DM chat window"); @@ -304,7 +304,7 @@ QUnit.test('open already detached DM chat from blank thread window', function (a selectDef.then(function () { assert.strictEqual($('.o_thread_window').length, 1, "should now have a single thread window open"); - assert.strictEqual($('.o_thread_window_title').text().trim(), "#DemoUser1", + assert.strictEqual($('.o_thread_window_title').text().trim(), "DemoUser1", "the remaining thread window should be the DM chat with 'DemoUser1'"); self.unpatchAutocomplete();