Files
odoo_source/addons/mail/static/tests/discuss_tests.js
T
Mathieu Duckaerts-Antoine 299ebb2cdf [IMP] mail: add moderation on channels
Purpose of this commit is to allow moderation on incoming messages in
discussion channels. On some channels on which moderation is required
messages should be in a pending moderation stage. Moderators can accept
or refuse messages as well as always allow or ban messages coming from
a given set of emails.

Channels now have an option to be moderated. Moderators can be added on
channels. They have access to a specific UI in Discuss to see and take
action on messages waiting for moderation.

Concerning mail.thread message that are pending moderation are not notified.
It means nobody receives a notification about them. Moderation process calls
the notification once the message is validated.

Various features included in this commit :

 * a model is added to store the decision about emails, allow or ban;
 * access rights are updated so that only moderators can modify moderation
   fields on message;
 * specific bus notifications are send to moderated people as well as to
   moderators on incoming emails as well as when a decision is taken;
 * options are added on channels to send explanations to moderated emails;
 * options are added on channels to write and send guidelines explaining
   why and how moderation is performed;
 * a reminder is send daily to moderators with remaining messages to moderate;
 * discuss UI is adapted and a new channel is added below Inbox and Starred
   giving access to moderation tools;
 * chanenl UI is adapted allowing to moderate directly inside channels;

This commit is linked to task ID 29521. Closes #21921.
2018-06-06 16:01:33 +02:00

550 lines
19 KiB
JavaScript

odoo.define('mail.discuss_test', function (require) {
"use strict";
var Discuss = require('mail.chat_discuss');
var ChatManager = require('mail.ChatManager');
var mailTestUtils = require('mail.testUtils');
var Bus = require('web.Bus');
var concurrency = require('web.concurrency');
var SearchView = require('web.SearchView');
var testUtils = require('web.test_utils');
var createBusService = mailTestUtils.createBusService;
var createDiscuss = mailTestUtils.createDiscuss;
QUnit.module('mail', {}, function () {
QUnit.module('Discuss client action', {
beforeEach: function () {
// patch _.debounce and _.throttle to be fast and synchronous
this.underscoreDebounce = _.debounce;
this.underscoreThrottle = _.throttle;
_.debounce = _.identity;
_.throttle = _.identity;
this.data = {
'mail.message': {
fields: {
body: {
string: "Contents",
type: 'html',
},
author_id: {
string: "Author",
relation: 'res.partner',
},
channel_ids: {
string: "Channels",
type: 'many2many',
relation: 'mail.channel',
},
starred: {
string: "Starred",
type: 'boolean',
},
needaction: {
string: "Need Action",
type: 'boolean',
},
starred_partner_ids: {
string: "partner ids",
type: 'integer',
},
model: {
string: "Related Document model",
type: 'char',
},
res_id: {
string: "Related Document ID",
type: 'integer',
},
},
},
};
this.services = [ChatManager, createBusService()];
},
afterEach: function () {
// unpatch _.debounce and _.throttle
_.debounce = this.underscoreDebounce;
_.throttle = this.underscoreThrottle;
}
});
QUnit.test('basic rendering', function (assert) {
assert.expect(5);
var done = assert.async();
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");
assert.strictEqual(discuss.$('.o_mail_chat_content').length, 1,
"should have rendered the content");
assert.strictEqual(discuss.$('.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(34);
var done = assert.async();
var bus = new Bus();
var BusService = createBusService(bus);
var fetchListenersDef = $.Deferred();
var receiveMessageDef = $.Deferred();
this.data.initMessaging = {
channel_slots: {
channel_channel: [{
id: 1,
channel_type: "channel",
name: "general",
}],
},
};
createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: [ChatManager, BusService],
mockRPC: function (route, args) {
if (args.method === 'channel_fetch_listeners') {
fetchListenersDef.resolve();
return $.when([
{id: 1, name: 'Admin'},
{id: 2, name: 'TestUser'},
{id: 3, name: 'DemoUser'}
]);
}
if (args.method === 'message_post') {
var data = {
author_id: ["42", "Me"],
body: args.kwargs.body,
channel_ids: [1],
};
var notification = [[false, 'mail.channel'], data];
bus.trigger('notification', [notification]);
receiveMessageDef.resolve();
return $.when(42);
}
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'");
// click on general
$general.click();
var $input = discuss.$('textarea.o_composer_text_field').first();
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.open').length, 1,
"dropup menu for partner mentions should be open");
var $mentionPropositions = discuss.$('.o_mention_proposition');
assert.strictEqual($mentionPropositions.length, 3,
"should display 3 partner mention propositions");
var $mention1 = $mentionPropositions.first();
var $mention2 = $mentionPropositions.first().next();
var $mention3 = $mentionPropositions.first().next().next();
// correct 1st mention proposition
assert.ok($mention1.hasClass('active'),
"first partner mention should be active");
assert.strictEqual($mention1.data('id'), 1,
"first partner mention should link to the correct partner id");
assert.strictEqual($mention1.find('.o_mention_name').text(), "Admin",
"first partner mention should display the correct partner name");
// correct 2nd mention proposition
assert.notOk($mention2.hasClass('active'),
"second partner mention should not be active");
assert.strictEqual($mention2.data('id'), 2,
"second partner mention should link to the correct partner id");
assert.strictEqual($mention2.find('.o_mention_name').text(), "TestUser",
"second partner mention should display the correct partner name");
// correct 3rd mention proposition
assert.notOk($mention3.hasClass('active'),
"third partner mention should not be active");
assert.strictEqual($mention3.data('id'), 3,
"third partner mention should link to the correct partner id");
assert.strictEqual($mention3.find('.o_mention_name').text(), "DemoUser",
"third partner mention should display the correct partner name");
// check DOWN event
$input.trigger($.Event('keyup', {which: $.ui.keyCode.DOWN}));
assert.notOk($mention1.hasClass('active'),
"first partner mention should not be active");
assert.ok($mention2.hasClass('active'),
"second partner mention should be active");
assert.notOk($mention3.hasClass('active'),
"third partner mention should not be active");
// check UP event
$input.trigger($.Event('keyup', {which: $.ui.keyCode.UP}));
assert.ok($mention1.hasClass('active'),
"first partner mention should be active");
assert.notOk($mention2.hasClass('active'),
"second partner mention should not be active");
assert.notOk($mention3.hasClass('active'),
"third partner mention should not be active");
// check TAB event (full cycle, hence 3 TABs)
$input.trigger($.Event('keyup', {which: $.ui.keyCode.TAB}));
assert.notOk($mention1.hasClass('active'),
"first partner mention should not be active");
assert.ok($mention2.hasClass('active'),
"second partner mention should be active");
assert.notOk($mention3.hasClass('active'),
"third partner mention should not be active");
$input.trigger($.Event('keyup', {which: $.ui.keyCode.TAB}));
assert.notOk($mention1.hasClass('active'),
"first partner mention should not be active");
assert.notOk($mention2.hasClass('active'),
"second partner mention should not be active");
assert.ok($mention3.hasClass('active'),
"third partner mention should be active");
$input.trigger($.Event('keyup', {which: $.ui.keyCode.TAB}));
assert.ok($mention1.hasClass('active'),
"first partner mention should be active");
assert.notOk($mention2.hasClass('active'),
"second partner mention should not be active");
assert.notOk($mention3.hasClass('active'),
"third partner mention should not be active");
// equivalent to $mentionPropositions.find('active').click();
$input.trigger($.Event('keyup', {which: $.ui.keyCode.ENTER}));
assert.strictEqual(discuss.$('.o_mention_proposition').length, 0,
"should not have any partner mention proposition after ENTER");
assert.strictEqual($input.val().trim() , "@Admin",
"should have the correct mention link in the composer input");
// send message
$input.trigger($.Event('keydown', {which: $.ui.keyCode.ENTER}));
receiveMessageDef
.then(concurrency.delay.bind(concurrency, 0))
.then(function () {
assert.strictEqual(discuss.$('.o_thread_message_content').length, 1,
"should display one message with some content");
assert.strictEqual(discuss.$('.o_thread_message_content a').length, 1,
"should contain a link in the message content");
assert.strictEqual(discuss.$('.o_thread_message_content a').text(),
"@Admin", "should have correct mention link in the message content");
// Restore window.getSelection
discuss.destroy();
done();
});
});
});
});
QUnit.test('no crash focusout emoji button', function (assert) {
assert.expect(3);
var done = assert.async();
this.data.initMessaging = {
channel_slots: {
channel_channel: [{
id: 1,
channel_type: "channel",
name: "general",
}],
},
};
createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
})
.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'");
// click on general
$general.click();
discuss.$('.o_composer_button_emoji').focus();
try {
discuss.$('.o_composer_button_emoji').focusout();
assert.ok(true, "should not crash on focusout of emoji button");
} finally {
discuss.destroy();
done();
}
});
});
QUnit.test('older messages are loaded on scroll', function (assert) {
assert.expect(3);
var done = assert.async();
var fetchCount = 0;
var loadMoreDef = $.Deferred();
var msgData = [];
for (var i = 0; i < 35; i++) {
msgData.push({
author_id: ['1', 'Me'],
body: '<p>test ' + i + '</p>',
channel_ids: [1],
id: i,
});
}
this.data.initMessaging = {
channel_slots: {
channel_channel: [{
id: 1,
channel_type: "channel",
name: "general",
static: true,
}],
},
};
this.data['mail.message'].records = msgData;
createDiscuss({
context: {},
data: this.data,
params: {},
services: [ChatManager, createBusService()],
mockRPC: function (route, args) {
if (args.method === 'message_fetch') {
fetchCount++;
// 1st fetch: inbox initial fetch
// 2nd fetch: general initial fetch
// 3rd fetch: general load more
if (fetchCount === 3) {
loadMoreDef.resolve();
}
}
return this._super.apply(this, arguments);
},
}).then(function (discuss) {
var $general = discuss.$('.o_mail_chat_channel_item[data-channel-id=1]');
assert.strictEqual($general.length, 1,
"should have a channel item with id 1");
// switch to 'general'
$general.click();
assert.strictEqual(discuss.$('.o_thread_message').length, 30,
"should display the 30 messages");
// simulate a scroll to top to load more messages
discuss.$('.o_mail_thread').scrollTop(0);
loadMoreDef
.then(concurrency.delay.bind(concurrency, 0))
.then(function () {
assert.strictEqual(discuss.$('.o_thread_message').length, 35,
"all messages should now be loaded");
discuss.destroy();
done();
});
});
});
QUnit.test('"Unstar all" button should reset the starred counter', function (assert) {
assert.expect(2);
var done = assert.async();
var bus = new Bus();
var BusService = createBusService(bus);
var msgData = [];
_.each(_.range(1, 41), function (num) {
msgData.push({
id: num,
body: "<p>test" + num + "</p>",
author_id: ["1", "Me"],
channel_ids: [1],
starred: true,
starred_partner_ids: [1],
}
);
});
this.data.initMessaging = {
channel_slots: {
channel_channel: [{
id: 1,
channel_type: "channel",
name: "general",
}],
},
starred_counter: msgData.length,
};
this.data['mail.message'].records = msgData;
createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: [ChatManager, BusService],
mockRPC: function (route, args) {
if (args.method === 'unstar_all') {
var data = {
message_ids: _.range(1, 41),
starred: false,
type: 'toggle_star',
};
var notification = [[false, 'res.partner'], data];
bus.trigger('notification', [notification]);
return $.when(42);
}
return this._super.apply(this, arguments);
},
session: {partner_id: 1},
})
.then(function (discuss) {
var $starred = discuss.$('.o_mail_chat_sidebar').find('.o_mail_chat_title_starred');
var $starredCounter = $('.o_mail_chat_title_starred > .o_mail_sidebar_needaction');
// Go to Starred channel
$starred.click();
// Test Initial Value
assert.strictEqual($starredCounter.text().trim(), "40", "40 messages should be starred");
// Unstar all and wait 'update_starred'
$('.o_control_panel .o_mail_chat_button_unstar_all').click();
$starredCounter = $('.o_mail_chat_title_starred > .o_mail_sidebar_needaction');
assert.strictEqual($starredCounter.text().trim(), "0",
"All messages should be unstarred");
discuss.destroy();
done();
});
});
QUnit.test('do not crash when destroyed before start is completed', function (assert) {
assert.expect(3);
var discuss;
testUtils.patch(Discuss, {
init: function () {
discuss = this;
this._super.apply(this, arguments);
},
});
createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
mockRPC: function (route, args) {
if (args.method) {
assert.step(args.method);
}
var result = this._super.apply(this, arguments);
if (args.method === 'message_fetch') {
discuss.destroy();
}
return result;
},
});
assert.verifySteps([
"load_views",
"message_fetch"
]);
testUtils.unpatch(Discuss);
});
QUnit.test('do not crash when destroyed between start en end of _renderSearchView', function (assert) {
assert.expect(2);
var discuss;
testUtils.patch(Discuss, {
init: function () {
discuss = this;
this._super.apply(this, arguments);
},
});
var def = $.Deferred();
testUtils.patch(SearchView, {
willStart: function () {
var result = this._super.apply(this, arguments);
return def.then($.when(result));
},
});
createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
mockRPC: function (route, args) {
if (args.method) {
assert.step(args.method);
}
return this._super.apply(this, arguments);
},
});
discuss.destroy();
def.resolve();
assert.verifySteps([
"load_views",
]);
testUtils.unpatch(Discuss);
testUtils.unpatch(SearchView);
});
});
});