[MERGE] mail,web: fix various bugs from recent JS mail refactoring
Revision on a recent JS refactoring: https://github.com/odoo/odoo/commit/02ec09cb1c3e2d7bc7968f40c18f2208d7f3f498 This merge contains several fixes from the above refactoring commit. It also adds tests on discuss, as one of the purpose of the refactoring was to test this widget.
This commit is contained in:
@@ -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);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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': '<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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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': '<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 {
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
<template id="qunit_mobile_suite" name="mail_mobile_tests" inherit_id="web.qunit_mobile_suite">
|
||||
<xpath expr="//t[@t-set='head']" position="inside">
|
||||
<script type="text/javascript" src="/mail/static/tests/discuss_mobile_tests.js"></script>
|
||||
<script type="text/javascript" src="/mail/static/tests/helpers/mock_server.js"></script>
|
||||
<script type="text/javascript" src="/mail/static/tests/helpers/test_utils.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user