diff --git a/addons/mail/static/src/js/discuss.js b/addons/mail/static/src/js/discuss.js
index 3b405761169..2142a21da4a 100644
--- a/addons/mail/static/src/js/discuss.js
+++ b/addons/mail/static/src/js/discuss.js
@@ -99,7 +99,7 @@ var PartnerInviteDialog = Dialog.extend({
self.do_notify(_t('New people'), notification);
// Clear the membersDeferred to fetch again the partner
// when getMentionPartnerSuggestions from the chatManager is triggered
- var channel = this.call('chat_manager', 'getChannel', self.channelID);
+ var channel = self.call('chat_manager', 'getChannel', self.channelID);
delete channel.membersDeferred;
});
}
@@ -508,7 +508,7 @@ var Discuss = Widget.extend(ControlPanelMixin, {
*/
_selectMessage: function (messageID) {
this.$el.addClass('o_mail_selection_mode');
- var message = this._getMessage(messageID);
+ var message = this.call('chat_manager', 'getMessage', messageID);;
this.selected_message = message;
var subject = "Re: " + message.record_name;
this.extendedComposer.set_subject(subject);
@@ -903,10 +903,10 @@ var Discuss = Widget.extend(ControlPanelMixin, {
if (def) {
def.then(function (value) {
if (value !== 'granted') {
- self._sendNotification(self, _t('Permission denied'),
+ self.call('bus_service', 'sendNotification', self, _t('Permission denied'),
_t('Odoo will not have the permission to send native notifications on this device.'));
} else {
- self._sendNotification(self, _t('Permission granted'),
+ self.call('bus_service', 'sendNotification', self, _t('Permission granted'),
_t('Odoo has now the permission to send you native notifications on this device.'));
}
});
@@ -963,7 +963,7 @@ var Discuss = Widget.extend(ControlPanelMixin, {
* @private
*/
_onUnsubscribeButtonClicked: function () {
- this._unsubscribe(this.channel);
+ this.call('chat_manager', 'unsubscribe', this.channel);
},
});
diff --git a/addons/mail/static/src/js/services/chat_manager.js b/addons/mail/static/src/js/services/chat_manager.js
index fb4b6672bc6..a58de02365a 100644
--- a/addons/mail/static/src/js/services/chat_manager.js
+++ b/addons/mail/static/src/js/services/chat_manager.js
@@ -352,7 +352,6 @@ var ChatManager = AbstractService.extend({
* => list containing a list of members (cached by chat_manager)
*/
getMentionPartnerSuggestions: function (channel) {
- var self = this;
if (!channel) {
return this.mentionPartnerSuggestions;
}
@@ -365,14 +364,7 @@ var ChatManager = AbstractService.extend({
shadow: true
})
.then(function (members) {
- var suggestions = [];
- _.each(self.mentionPartnerSuggestions, function (partners) {
- suggestions.push(_.filter(partners, function (partner) {
- return !_.findWhere(members, { id: partner.id });
- }));
- });
-
- return [suggestions];
+ return [members];
});
}
return channel.membersDeferred;
@@ -1642,7 +1634,7 @@ var ChatManager = AbstractService.extend({
web_client.set_title_part("_chat", tabTitle);
}
- this._sendNotification(web_client, title, content);
+ this.call('bus_service', 'sendNotification', web_client, title, content);
},
/**
* Removes channel
diff --git a/addons/mail/static/src/js/services/chat_window_manager.js b/addons/mail/static/src/js/services/chat_window_manager.js
index bab6fbf7729..bea6eaf0ace 100644
--- a/addons/mail/static/src/js/services/chat_window_manager.js
+++ b/addons/mail/static/src/js/services/chat_window_manager.js
@@ -387,8 +387,8 @@ var ChatWindowManager = AbstractService.extend({
this.displayState.hiddenSessions = hiddenSessions;
this.displayState.hiddenUnreadCounter = hiddenUnreadCounter;
- if (this.displayState.$hidden_windows_dropdown) {
- this.displayState.$hidden_windows_dropdown.remove();
+ if (this.displayState.$hiddenWindowsDropdown) {
+ this.displayState.$hiddenWindowsDropdown.remove();
}
if (hiddenSessions.length) {
this.displayState.$hiddenWindowsDropdown = this._renderHiddenSessionsDropdown();
@@ -487,8 +487,8 @@ var ChatWindowManager = AbstractService.extend({
self.displayState.hiddenUnreadCounter += session.window.unread_msgs;
}
});
- if (self.displayState.$hidden_windows_dropdown) {
- self.displayState.$hidden_windows_dropdown.html(self.renderHiddenSessionsDropdown().html());
+ if (self.displayState.$hiddenWindowsDropdown) {
+ self.displayState.$hiddenWindowsDropdown.html(self._renderHiddenSessionsDropdown().html());
self._repositionHiddenSessionsDropdown();
}
},
diff --git a/addons/mail/static/tests/chatter_tests.js b/addons/mail/static/tests/chatter_tests.js
index 4ef63b70615..e247c01430a 100644
--- a/addons/mail/static/tests/chatter_tests.js
+++ b/addons/mail/static/tests/chatter_tests.js
@@ -369,6 +369,8 @@ QUnit.test('kanban activity widget with an activity', function (assert) {
QUnit.test('chatter: post, receive and star messages', function (assert) {
var done = assert.async();
assert.expect(28);
+
+ testUtils.patchWindowGetSelection();
var bus = new Bus();
var BusService = createBusService(bus);
@@ -533,7 +535,11 @@ QUnit.test('chatter: post, receive and star messages', function (assert) {
assert.strictEqual($(".o_composer_input a").length, 1, "mention is 'green' in edit mode");
BasicComposer.prototype.MENTION_THROTTLE = mentionThrottle;
+
+ //cleanup
form.destroy();
+ testUtils.unpatchWindowGetSelection();
+
done();
});
});
diff --git a/addons/mail/static/tests/discuss_mobile_tests.js b/addons/mail/static/tests/discuss_mobile_tests.js
index 4e96c87f512..d17364b53ad 100644
--- a/addons/mail/static/tests/discuss_mobile_tests.js
+++ b/addons/mail/static/tests/discuss_mobile_tests.js
@@ -29,21 +29,13 @@ QUnit.test('mobile basic rendering', function (assert) {
assert.expect(9);
var done = assert.async();
- var discuss = createDiscuss({
+ createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
- mockRPC: function (route, args) {
- if (args.method === 'message_fetch') {
- return $.when([]);
- }
- return this._super.apply(this, arguments);
- },
- });
-
- discuss.call('chat_manager', 'isReady').then(function () {
+ }).then(function (discuss) {
// test basic rendering in mobile
assert.strictEqual(discuss.$('.o_mail_chat_mobile_control_panel').length, 1,
"should have rendered a control panel");
diff --git a/addons/mail/static/tests/discuss_tests.js b/addons/mail/static/tests/discuss_tests.js
index 7f48e3b0450..348a6357b5d 100644
--- a/addons/mail/static/tests/discuss_tests.js
+++ b/addons/mail/static/tests/discuss_tests.js
@@ -1,12 +1,18 @@
odoo.define('mail.discuss_test', function (require) {
"use strict";
-var testUtils = require('web.test_utils');
-var Widget = require('web.Widget');
-
+var ChatManager = require('mail.ChatManager');
+var Composers = require('mail.composer');
var mailTestUtils = require('mail.testUtils');
+var concurrency = require('web.concurrency');
+var testUtils = require('web.test_utils');
+
+var BasicComposer = Composers.BasicComposer;
+var createBusService = mailTestUtils.createBusService;
var createDiscuss = mailTestUtils.createDiscuss;
+var patchWindowGetSelection = testUtils.patchWindowGetSelection;
+var unpatchWindowGetSelection = testUtils.unpatchWindowGetSelection;
QUnit.module('mail', {}, function () {
@@ -17,57 +23,125 @@ QUnit.module('Discuss client action', {
fields: {},
},
};
+ this.services = [ChatManager, createBusService()];
},
});
-QUnit.skip('mobile basic rendering', function (assert) {
- // Unfortunately, this test is skipped for now because there is no way to
- // execute the whole test suite in mobile (it only works test by test), so
- // as the client action include for mobile is rejected when we are not in
- // mobile, it isn't possible to test it
- // Moreover, RPCs done by the chat_manager (e.g. message_fetch) should be
- // properly mocked.
- assert.expect(11);
+QUnit.test('basic rendering', function (assert) {
+ assert.expect(5);
+ var done = assert.async();
- var parent = new Widget();
- testUtils.addMockEnvironment(parent, {
- data: this.data,
- archs: {
- 'mail.message,false,search': '',
- },
- config: {device: {isMobile: true}},
- });
-
- var params = {
+ createDiscuss({
id: 1,
context: {},
params: {},
+ data: this.data,
+ services: this.services,
+ })
+ .then(function (discuss) {
+ // test basic rendering
+ var $sidebar = discuss.$('.o_mail_chat_sidebar');
+ assert.strictEqual($sidebar.length, 1,
+ "should have rendered a sidebar");
+
+ var $nocontent = discuss.$('.o_mail_chat_content');
+ assert.strictEqual($nocontent.length, 1,
+ "should have rendered the content");
+ assert.strictEqual($nocontent.find('.o_mail_no_content').length, 1,
+ "should display no content message");
+
+ var $inbox = $sidebar.find('.o_mail_chat_channel_item[data-channel-id=channel_inbox]');
+ assert.strictEqual($inbox.length, 1,
+ "should have the channel item 'channel_inbox' in the sidebar");
+
+ var $starred = $sidebar.find('.o_mail_chat_channel_item[data-channel-id=channel_starred]');
+ assert.strictEqual($starred.length, 1,
+ "should have the channel item 'channel_starred' in the sidebar");
+ discuss.destroy();
+ done();
+ });
+});
+
+QUnit.test('@ mention in channel', function (assert) {
+ assert.expect(9);
+ var done = assert.async();
+
+ this.data.initMessaging = {
+ channel_slots: {
+ channel_channel: [{
+ id: 1,
+ channel_type: "channel",
+ name: "general",
+ }],
+ },
};
- var discuss = createDiscuss(params);
- // test basic rendering in mobile
- assert.equal(discuss.$(".o_mail_chat_mobile_control_panel").length, 1, "Mobile control panel created");
- assert.equal(discuss.$(".o_mail_mobile_tab").length, 4, "Four mobile tabs created");
- assert.equal(discuss.$('.o_mail_chat_content').length, 1, "One default chat content pane created");
- assert.equal(discuss.$(".o_mail_chat_tab_pane").length, 3, "Three mobile tab panes created");
+ // Remove the mention throttle to speed up the test
+ var mentionThrottle = BasicComposer.prototype.MENTION_THROTTLE;
+ BasicComposer.prototype.MENTION_THROTTLE = 1;
- // Inbox
- assert.equal(discuss.activeMobileTab, "channel_inbox", "'channel_inbox' is default active tab");
- assert.ok(discuss.$(".o_channel_inbox_item:nth(0)").hasClass("btn-primary"), "Showing 'Inbox'");
+ var fetchListenersDef = $.Deferred();
- // Starred
- discuss.$(".o_channel_inbox_item[data-type='channel_starred']").click();
- assert.ok(discuss.$(".o_channel_inbox_item:nth(1)").hasClass("btn-primary"), "Clicked on 'Starred'");
+ 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: 2, name: 'TestUser'} ]);
+ }
+ return this._super.apply(this, arguments);
+ },
+ })
+ .then(function (discuss) {
+ var $general = discuss.$('.o_mail_chat_sidebar')
+ .find('.o_mail_chat_channel_item[data-channel-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'");
- assert.ok(discuss.$(".o_mail_chat_content").is(":visible"), "Default main content pane visible");
+ // click on general
+ $general.click();
+ var $input = discuss.$('.o_composer_input').first();
+ assert.ok($input.length, "should display a composer input");
- discuss.$(".o_mail_mobile_tab[data-type='dm']").click();
- assert.equal(discuss.activeMobileTab, "dm", "After click on 'Conversation', is now active tab");
+ // Simulate '@' typed by user with mocked Window.getSelection
+ // Note: focus is needed in order to trigger rpc 'channel_fetch_listeners'
+ $input.focus();
+ $input.text("@");
+ patchWindowGetSelection();
+ $input.trigger('keyup');
- assert.ok(!discuss.$(".o_mail_chat_content").is(":visible"), "none", "'Main' content pane is invisible");
- assert.ok(discuss.$(".o_mail_chat_tab_pane:nth(0)").is(":visible"), "'Conversation' pane is visible");
+ fetchListenersDef
+ .then(concurrency.delay.bind(concurrency, 0))
+ .then(function () {
+ assert.strictEqual(discuss.$('.dropup.o_composer_mention_dropdown.open').length, 1,
+ "dropup menu for partner mentions should be open");
- discuss.destroy();
+ var $mentionProposition = discuss.$('.o_mention_proposition');
+ assert.strictEqual($mentionProposition.length, 1,
+ "should display one partner mention proposition");
+ assert.strictEqual($mentionProposition.data('id'), 2,
+ "partner mention should link to the correct partner id");
+ assert.strictEqual($mentionProposition.find('.o_mention_name').text(), "TestUser",
+ "partner mention should display the correct partner name");
+
+ $mentionProposition.click();
+ assert.strictEqual(discuss.$('.o_mention_proposition').length, 0,
+ "should not have any partner mention proposition after clicking on it");
+ assert.strictEqual($input.find('a').text() , "@TestUser",
+ "should have the correct mention link in the composer input");
+
+ BasicComposer.prototype.MENTION_THROTTLE = mentionThrottle;
+ unpatchWindowGetSelection();
+ discuss.destroy();
+ done();
+ });
+ });
});
});
diff --git a/addons/mail/static/tests/helpers/mock_server.js b/addons/mail/static/tests/helpers/mock_server.js
index 365b067b55a..a2aef300736 100644
--- a/addons/mail/static/tests/helpers/mock_server.js
+++ b/addons/mail/static/tests/helpers/mock_server.js
@@ -5,14 +5,23 @@ var MockServer = require('web.MockServer');
MockServer.include({
/**
+ * Param 'data' may have a key 'initMessaging' which contains
+ * a partial overwrite of the result from mockInitMessaging.
+ *
+ * Note: we must delete this key, so that this is not
+ * handled as a model definition.
+ *
* @override
+ * @param {Object} [data.initMessaging]
*/
- _performRpc: function (route, args) {
- if (route === '/mail/init_messaging') {
- return $.when(this._mockInitMessaging(args));
+ init: function (data, options) {
+ if (data && data.initMessaging) {
+ this.initMessagingData = data.initMessaging;
+ delete data.initMessaging;
}
- return this._super(route, args);
+ this._super.apply(this, arguments);
},
+
/**
* Simulate the '/mail/init_messaging' route
*
@@ -20,7 +29,7 @@ MockServer.include({
* @return {Object}
*/
_mockInitMessaging: function () {
- return {
+ return _.defaults(this.initMessagingData || {}, {
'needaction_inbox_counter': 0,
'starred_counter': 0,
'channel_slots': [],
@@ -28,7 +37,19 @@ MockServer.include({
'mention_partner_suggestions': [],
'shortcodes': [],
'menu_id': false,
- };
+ });
+ },
+ /**
+ * @override
+ */
+ _performRpc: function (route, args) {
+ if (route === '/mail/init_messaging') {
+ return $.when(this._mockInitMessaging(args));
+ }
+ if (args.method === 'message_fetch') {
+ return $.when([]);
+ }
+ return this._super(route, args);
},
});
diff --git a/addons/mail/static/tests/helpers/test_utils.js b/addons/mail/static/tests/helpers/test_utils.js
index e0a8a0a13de..095da27be58 100644
--- a/addons/mail/static/tests/helpers/test_utils.js
+++ b/addons/mail/static/tests/helpers/test_utils.js
@@ -55,24 +55,31 @@ function createBusService(bus) {
return BusService;
}
+/**
+ * Create asynchronously a discuss widget.
+ * This is async due to chat_manager service that needs to be ready.
+ *
+ * @param {Object} params
+ * @return {$.Promise} resolved with the discuss widget
+ */
function createDiscuss(params) {
var Parent = Widget.extend({
do_push_state: function () {},
});
var parent = new Parent();
- testUtils.addMockEnvironment(parent, {
- data: params.data,
+ testUtils.addMockEnvironment(parent, _.extend(params, {
archs: {
'mail.message,false,search': '',
},
- mockRPC: params.mockRPC,
- services: params.services,
- });
+ }));
var discuss = new Discuss(parent, params);
discuss.set_cp_bus(new Widget());
- discuss.appendTo($('#qunit-fixture'));
+ var selector = params.debug ? 'body' : '#qunit-fixture';
+ discuss.appendTo($(selector));
- return discuss;
+ return discuss.call('chat_manager', 'isReady').then(function () {
+ return discuss;
+ });
}
return {
diff --git a/addons/mail/views/mail_templates.xml b/addons/mail/views/mail_templates.xml
index 28d92c9e157..6769462d404 100644
--- a/addons/mail/views/mail_templates.xml
+++ b/addons/mail/views/mail_templates.xml
@@ -54,6 +54,8 @@
+
+
diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js
index a4f6531d075..6f3b3ef4549 100644
--- a/addons/web/static/tests/helpers/test_utils.js
+++ b/addons/web/static/tests/helpers/test_utils.js
@@ -23,6 +23,8 @@ var Widget = require('web.Widget');
var DebouncedField = basic_fields.DebouncedField;
+var originalWindowGetSelection; // cached replaced originalGetSelection
+
/**
* intercepts an event bubbling up the widget hierarchy. The event intercepted
* must be a "custom event", i.e. an event generated by the method 'trigger_up'.
@@ -725,6 +727,50 @@ function patch (target, props) {
});
}
}
+/**
+ * In Phantomjs, there is a crash when calling window.getSelection
+ * in order for the tests to work, for the specific test that uses it, replace
+ * the default window.getSelection by a mock
+ *
+ * usage:
+ * QUnit.test('...',function(done){
+ * testUtils.patchWindowGetSelection();
+ *
+ * // do something that needs to use window.getSelection()
+ * assert.strictEqual(....);
+ *
+ * // restore the original function
+ * testUtils.unpatchWindowGetSelection();
+ *
+ * // finish the test
+ * done();
+ * })
+ */
+
+function patchWindowGetSelection() {
+ originalWindowGetSelection = window.getSelection;
+ window.getSelection = function () {
+ return {
+ removeAllRanges: function () {},
+ addRange: function (range) {},
+ getRangeAt: function (index) {
+ return {
+ startOffset : 0
+ };
+ },
+ anchorNode: {
+ parentNode: {
+ childNodes: [{
+ outerHTML: "@",
+ nodeType: 3,
+ textContent: '@',
+ }],
+ },
+ },
+ };
+ };
+}
+
/**
* Unpatches a given Class or Object.
*
@@ -749,6 +795,14 @@ function unpatch(target) {
delete target.__patchID;
}
+/**
+ * after using patchWindowGetSelection, at the end of your test
+ * you must restore the default function so there is no impact to the other tests
+ */
+function unpatchWindowGetSelection() {
+ window.getSelection = originalWindowGetSelection;
+}
+
// Loading static files cannot be properly simulated when their real content is
// really needed. This is the case for static XML files so we load them here,
// before starting the qunit test suite.
@@ -775,11 +829,13 @@ return $.when(
intercept: intercept,
observe: observe,
patch: patch,
+ patchWindowGetSelection: patchWindowGetSelection,
removeSrcAttribute: removeSrcAttribute,
triggerKeypressEvent: triggerKeypressEvent,
triggerMouseEvent: triggerMouseEvent,
triggerPositionalMouseEvent: triggerPositionalMouseEvent,
unpatch: unpatch,
+ unpatchWindowGetSelection: unpatchWindowGetSelection,
};
});