summaryrefslogtreecommitdiff
path: root/addons/im_support
diff options
context:
space:
mode:
Diffstat (limited to 'addons/im_support')
-rw-r--r--addons/im_support/__init__.py2
-rw-r--r--addons/im_support/controllers/__init__.py4
-rw-r--r--addons/im_support/controllers/main.py12
-rw-r--r--addons/im_support/static/src/js/discuss.js4
-rw-r--r--addons/im_support/static/tests/helpers/test_utils.js81
-rw-r--r--addons/im_support/static/tests/systray_no_support_tests.js51
-rw-r--r--addons/im_support/static/tests/systray_tests.js342
-rw-r--r--addons/im_support/tests/__init__.py4
-rw-r--r--addons/im_support/tests/test_js.py11
-rw-r--r--addons/im_support/views/assets.xml29
10 files changed, 538 insertions, 2 deletions
diff --git a/addons/im_support/__init__.py b/addons/im_support/__init__.py
index a0fdc10fe11..aa4d0fd63a9 100644
--- a/addons/im_support/__init__.py
+++ b/addons/im_support/__init__.py
@@ -1,2 +1,4 @@
# -*- coding: utf-8 -*-
+
+from . import controllers
from . import models
diff --git a/addons/im_support/controllers/__init__.py b/addons/im_support/controllers/__init__.py
new file mode 100644
index 00000000000..5d4b25db9c0
--- /dev/null
+++ b/addons/im_support/controllers/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import main
diff --git a/addons/im_support/controllers/main.py b/addons/im_support/controllers/main.py
new file mode 100644
index 00000000000..c95e642cdef
--- /dev/null
+++ b/addons/im_support/controllers/main.py
@@ -0,0 +1,12 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import http
+from odoo.http import request
+
+
+class ImSupport(http.Controller):
+
+ @http.route('/im_support/tests', type='http', auth="user")
+ def test_suite(self, mod=None, **kwargs):
+ return request.render('im_support.support_qunit_suite')
diff --git a/addons/im_support/static/src/js/discuss.js b/addons/im_support/static/src/js/discuss.js
index df2526a2a26..0f29370b049 100644
--- a/addons/im_support/static/src/js/discuss.js
+++ b/addons/im_support/static/src/js/discuss.js
@@ -42,9 +42,9 @@ Discuss.include({
if (!channel.available) {
self.basicComposer.do_hide();
}
- $buttonAddAttachment.hide();
+ $buttonAddAttachment.toggleClass('o_hidden', true);
} else {
- $buttonAddAttachment.show();
+ $buttonAddAttachment.toggleClass('o_hidden', false);
}
});
},
diff --git a/addons/im_support/static/tests/helpers/test_utils.js b/addons/im_support/static/tests/helpers/test_utils.js
new file mode 100644
index 00000000000..5f90097a8f1
--- /dev/null
+++ b/addons/im_support/static/tests/helpers/test_utils.js
@@ -0,0 +1,81 @@
+odoo.define('im_support.test_utils', function (require) {
+"use strict";
+
+var supportSession = require('im_support.SupportSession');
+
+var testUtils = require('web.test_utils');
+
+
+/**
+ * Extended version of addMockEnvironment that mocks RPCs done to the Support
+ * server (CORS), and enables a longpolling to the Support server, optionally.
+ *
+ * @param {Widget} widget
+ * @param {Object} params
+ * @param {function} [params.mockSupportRPC]
+ * @param {boolean} [params.enableSupportPoll=false]
+ */
+function addMockSupportEnvironment(widget, params) {
+ // mock CORS RPCs
+ var originalRPC = supportSession.rpc;
+ var defaultMockSupportRPC = function (route, args) {
+ if (route === '/odoo_im_support/get_support_channel') {
+ return $.when({
+ available: true,
+ channel_type: 'livechat',
+ public: 'private',
+ uuid: args.channel_uuid,
+ });
+ }
+ if (route === '/odoo_im_support/fetch_messages') { // fetching history
+ return $.when([]);
+ }
+ if (route === '/odoo_im_support/chat_post') {
+ return $.when();
+ }
+ if (route === '/longpolling/support_poll') {
+ return $.Deferred();
+ }
+ };
+ supportSession.rpc = function (route, args) {
+ var result;
+ if (params.mockSupportRPC) {
+ var _super = this._super;
+ this._super = defaultMockSupportRPC.bind(this, route, args);
+ result = params.mockSupportRPC.apply(this, arguments);
+ this._super = _super;
+ } else {
+ result = defaultMockSupportRPC.apply(this, arguments);
+ }
+ return result;
+ };
+ testUtils.addMockEnvironment(widget, params);
+ var widgetDestroy = widget.destroy;
+ widget.destroy = function () {
+ supportSession.rpc = originalRPC;
+ widgetDestroy.call(this);
+ };
+
+ // intercepts the getItem call to localStorage service for key
+ // 'im_support.poll_timeout', to simulate a pending longpolling connection to
+ // the Support server, such that the initSupport initiates a poll connection
+ if (params.enableSupportPoll) {
+ testUtils.intercept(widget, 'call_service', function (ev) {
+ if (ev.data.service === 'local_storage') {
+ if (ev.data.method === 'getItem' && ev.data.args[0] === 'im_support.poll_timeout') {
+ ev.data.callback(Date.now() + (60 * 1000));
+ // hack to prevent the default mock to handle the request and erase the above value
+ ev.data.callback = function () {};
+ }
+ }
+ }, true);
+ // manually call initSupport (which is supposed to be called at webclient startup)
+ widget.call('chat_manager', 'initSupport');
+ }
+}
+
+return {
+ addMockSupportEnvironment: addMockSupportEnvironment,
+};
+
+});
diff --git a/addons/im_support/static/tests/systray_no_support_tests.js b/addons/im_support/static/tests/systray_no_support_tests.js
new file mode 100644
index 00000000000..d2f50817d8b
--- /dev/null
+++ b/addons/im_support/static/tests/systray_no_support_tests.js
@@ -0,0 +1,51 @@
+odoo.define('im_support.systray_no_support_tests', function (require) {
+"use strict";
+
+/**
+ * The purpose of these tests is to ensure that im_support doesn't have an impact
+ * on the webclient when the support is not available.
+ */
+
+var ChatManager = require('mail.ChatManager');
+var systray = require('mail.systray');
+var mailTestUtils = require('mail.testUtils');
+
+var testUtils = require('web.test_utils');
+
+var createBusService = mailTestUtils.createBusService;
+
+QUnit.module('im_support', {}, function () {
+
+QUnit.module('systray', {
+ beforeEach: function () {
+ this.services = [ChatManager, createBusService()];
+ this.data = {
+ 'mail.message': {
+ fields: {},
+ },
+ };
+ },
+});
+
+QUnit.test('messaging menu does not display the Support channel if not available', function (assert) {
+ // the Support channel should only be displayed if a support_token and a support_origin are
+ // specified in the session, which is not the case for this test
+ assert.expect(1);
+
+ var messagingMenu = new systray.MessagingMenu();
+ testUtils.addMockEnvironment(messagingMenu, {
+ services: this.services,
+ data: this.data,
+ });
+ messagingMenu.appendTo($('#qunit-fixture'));
+
+ messagingMenu.$('.dropdown-toggle').click();
+ assert.strictEqual(messagingMenu.$('.o_mail_channel_preview[data-channel_id=SupportChannel]').length,
+ 0, "should not display the Support channel");
+
+ messagingMenu.destroy();
+});
+
+});
+
+});
diff --git a/addons/im_support/static/tests/systray_tests.js b/addons/im_support/static/tests/systray_tests.js
new file mode 100644
index 00000000000..97d82cf2379
--- /dev/null
+++ b/addons/im_support/static/tests/systray_tests.js
@@ -0,0 +1,342 @@
+odoo.define('im_support.systray_tests', function (require) {
+"use strict";
+
+var supportBus = require('im_support.SupportBus');
+var imSupportTestUtils = require('im_support.test_utils');
+
+var ChatManager = require('mail.ChatManager');
+var ChatWindowManager = require('mail.ChatWindowManager');
+var systray = require('mail.systray');
+var mailTestUtils = require('mail.testUtils');
+
+var testUtils = require('web.test_utils');
+
+var addMockSupportEnvironment = imSupportTestUtils.addMockSupportEnvironment;
+var createBusService = mailTestUtils.createBusService;
+
+QUnit.module('im_support', {}, function () {
+
+QUnit.module('systray', {
+ beforeEach: function () {
+ this.services = [ChatManager, ChatWindowManager, createBusService()];
+ this.supportParams = {
+ db_uuid: 'some_uuid',
+ support_token: 'ABCDEFGHIJ',
+ support_origin: 'something.com',
+ };
+ this.data = {
+ 'mail.message': {
+ fields: {},
+ },
+ };
+ },
+});
+
+QUnit.test('messaging menu displays the Support channel', function (assert) {
+ assert.expect(1);
+
+ var messagingMenu = new systray.MessagingMenu();
+ addMockSupportEnvironment(messagingMenu, {
+ services: this.services,
+ session: this.supportParams,
+ data: this.data,
+ });
+ messagingMenu.appendTo($('#qunit-fixture'));
+
+ messagingMenu.$('.dropdown-toggle').click();
+ assert.strictEqual(messagingMenu.$('.o_mail_navbar_dropdown_bottom .o_mail_channel_preview[data-channel_id=SupportChannel]').length,
+ 1, "should display the Support channel");
+
+ messagingMenu.destroy();
+});
+
+QUnit.test('clicking on Support channel: channel not available', function (assert) {
+ assert.expect(9);
+
+ var messagingMenu = new systray.MessagingMenu();
+ addMockSupportEnvironment(messagingMenu, {
+ mockRPC: function (route, args) {
+ if (!_.str.contains(route, '/static/')) {
+ assert.step(args.method || route);
+ }
+ return this._super.apply(this, arguments);
+ },
+ mockSupportRPC: function (route) {
+ assert.step('cors: ' + route);
+ if (route === '/odoo_im_support/get_support_channel') {
+ return this._super.apply(this, arguments).then(function (supportChannel) {
+ supportChannel.available = false;
+ return supportChannel;
+ });
+ }
+ return this._super.apply(this, arguments);
+ },
+ services: this.services,
+ session: this.supportParams,
+ data: this.data,
+ });
+ messagingMenu.appendTo($('#qunit-fixture'));
+
+ messagingMenu.$('.dropdown-toggle').click();
+ assert.strictEqual(messagingMenu.$('.o_mail_channel_preview[data-channel_id=SupportChannel]').length,
+ 1, "should display the Support channel");
+
+ messagingMenu.$('.o_mail_channel_preview[data-channel_id=SupportChannel]').click();
+ assert.strictEqual($('.o_chat_window').length, 1,
+ "should have open a chat window");
+ assert.strictEqual($('.o_chat_window .o_chat_title').text().trim(), 'Support (offline)',
+ "should display the offline status in the header");
+ assert.strictEqual($('.o_chat_window .o_composer_input').length, 0,
+ "should have no composer");
+
+ assert.verifySteps([
+ '/mail/init_messaging',
+ 'message_fetch',
+ 'cors: /odoo_im_support/get_support_channel',
+ 'cors: /odoo_im_support/fetch_messages',
+ ]);
+
+ messagingMenu.destroy();
+});
+
+QUnit.test('clicking on Support channel: channel available', function (assert) {
+ assert.expect(9);
+
+ var messagingMenu = new systray.MessagingMenu();
+ addMockSupportEnvironment(messagingMenu, {
+ mockRPC: function (route, args) {
+ if (!_.str.contains(route, '/static/')) {
+ assert.step(args.method || route);
+ }
+ return this._super.apply(this, arguments);
+ },
+ mockSupportRPC: function (route) {
+ if (route.split('/')[1] === 'odoo_im_support') {
+ assert.step('cors: ' + route);
+ }
+ return this._super.apply(this, arguments);
+ },
+ services: this.services,
+ session: this.supportParams,
+ data: this.data,
+ });
+ messagingMenu.appendTo($('#qunit-fixture'));
+
+ messagingMenu.$('.dropdown-toggle').click();
+ assert.strictEqual(messagingMenu.$('.o_mail_channel_preview[data-channel_id=SupportChannel]').length,
+ 1, "should display the Support channel");
+
+ messagingMenu.$('.o_mail_channel_preview[data-channel_id=SupportChannel]').click();
+ assert.strictEqual($('.o_chat_window').length, 1,
+ "should have open a chat window");
+ assert.strictEqual($('.o_chat_window .o_chat_title').text().trim(), 'Support',
+ "should display the correct channel title");
+ assert.strictEqual($('.o_chat_window .o_composer_input').length, 1,
+ "should have a composer");
+
+ assert.verifySteps([
+ '/mail/init_messaging',
+ 'message_fetch',
+ 'cors: /odoo_im_support/get_support_channel',
+ 'cors: /odoo_im_support/fetch_messages',
+ ]);
+
+ messagingMenu.destroy();
+});
+
+QUnit.test('post messages in Support channel', function (assert) {
+ assert.expect(8);
+
+ var messagingMenu = new systray.MessagingMenu();
+ addMockSupportEnvironment(messagingMenu, {
+ mockRPC: function (route, args) {
+ if (!_.str.contains(route, '/static/')) {
+ assert.step(args.method || route);
+ }
+ return this._super.apply(this, arguments);
+ },
+ mockSupportRPC: function (route) {
+ if (route.split('/')[1] === 'odoo_im_support') {
+ assert.step('cors: ' + route);
+ }
+ return this._super.apply(this, arguments);
+ },
+ services: this.services,
+ session: this.supportParams,
+ data: this.data,
+ });
+ messagingMenu.appendTo($('#qunit-fixture'));
+
+ messagingMenu.$('.dropdown-toggle').click();
+ assert.strictEqual(messagingMenu.$('.o_mail_channel_preview[data-channel_id=SupportChannel]').length,
+ 1, "should display the Support channel");
+
+ messagingMenu.$('.o_mail_channel_preview[data-channel_id=SupportChannel]').click();
+ assert.strictEqual($('.o_chat_window .o_composer_input').length, 1,
+ "should have a composer");
+
+ $('.o_chat_window .o_composer_input .o_input')
+ .val('some message')
+ .trigger($.Event('keydown', {which: $.ui.keyCode.ENTER}));
+
+ assert.verifySteps([
+ '/mail/init_messaging',
+ 'message_fetch',
+ 'cors: /odoo_im_support/get_support_channel',
+ 'cors: /odoo_im_support/fetch_messages',
+ 'cors: /odoo_im_support/chat_post',
+ ]);
+
+ messagingMenu.destroy();
+});
+
+QUnit.test('fold Support channel', function (assert) {
+ assert.expect(11);
+
+ var messagingMenu = new systray.MessagingMenu();
+ addMockSupportEnvironment(messagingMenu, {
+ mockRPC: function (route, args) {
+ if (!_.str.contains(route, '/static/')) {
+ assert.step(args.method || route);
+ }
+ return this._super.apply(this, arguments);
+ },
+ mockSupportRPC: function (route) {
+ if (route.split('/')[1] === 'odoo_im_support') {
+ assert.step('cors: ' + route);
+ }
+ return this._super.apply(this, arguments);
+ },
+ services: this.services,
+ session: this.supportParams,
+ data: this.data,
+ });
+ testUtils.intercept(messagingMenu, 'call_service', function (ev) {
+ if (ev.data.service === 'local_storage') {
+ assert.step('LocalStorage: ' + ev.data.method + ' ' + ev.data.args);
+ }
+ }, true);
+ messagingMenu.appendTo($('#qunit-fixture'));
+
+ messagingMenu.$('.dropdown-toggle').click();
+ assert.strictEqual(messagingMenu.$('.o_mail_channel_preview[data-channel_id=SupportChannel]').length,
+ 1, "should display the Support channel");
+
+ messagingMenu.$('.o_mail_channel_preview[data-channel_id=SupportChannel]').click();
+ assert.strictEqual($('.o_chat_window').length, 1,
+ "should have open a chat window");
+
+ // fold, re-open and close channel
+ $('.o_chat_window .o_chat_title').click();
+ $('.o_chat_window .o_chat_title').click();
+ $('.o_chat_window .o_chat_window_close').click();
+
+ assert.verifySteps([
+ '/mail/init_messaging',
+ 'message_fetch',
+ 'cors: /odoo_im_support/get_support_channel',
+ 'LocalStorage: setItem im_support.channel_state,open',
+ 'cors: /odoo_im_support/fetch_messages',
+ 'LocalStorage: setItem im_support.channel_state,folded',
+ 'LocalStorage: setItem im_support.channel_state,open',
+ 'LocalStorage: setItem im_support.channel_state,closed',
+ ]);
+
+ messagingMenu.destroy();
+});
+
+QUnit.test('restore Support channel if necessary', function (assert) {
+ assert.expect(5);
+
+ var messagingMenu = new systray.MessagingMenu();
+ addMockSupportEnvironment(messagingMenu, {
+ enableSupportPoll: true,
+ mockRPC: function (route, args) {
+ if (!_.str.contains(route, '/static/')) {
+ assert.step(args.method || route);
+ }
+ return this._super.apply(this, arguments);
+ },
+ mockSupportRPC: function (route) {
+ if (route.split('/')[1] === 'odoo_im_support') {
+ assert.step('cors: ' + route);
+ }
+ return this._super.apply(this, arguments);
+ },
+ services: this.services,
+ session: this.supportParams,
+ data: this.data,
+ });
+ messagingMenu.appendTo($('#qunit-fixture'));
+
+ assert.strictEqual($('.o_chat_window').length, 1,
+ "should have open a chat window");
+
+ assert.verifySteps([
+ '/mail/init_messaging',
+ 'cors: /odoo_im_support/get_support_channel',
+ 'cors: /odoo_im_support/fetch_messages',
+ ]);
+
+ messagingMenu.destroy();
+});
+
+QUnit.test('receive messages in the Support channel', function (assert) {
+ assert.expect(9);
+
+ var supportChannelID;
+
+ var messagingMenu = new systray.MessagingMenu();
+ addMockSupportEnvironment(messagingMenu, {
+ enableSupportPoll: true,
+ mockRPC: function (route, args) {
+ if (!_.str.contains(route, '/static/')) {
+ assert.step(args.method || route);
+ }
+ return this._super.apply(this, arguments);
+ },
+ mockSupportRPC: function (route) {
+ if (route.split('/')[1] === 'odoo_im_support') {
+ assert.step('cors: ' + route);
+ }
+ return this._super.apply(this, arguments);
+ },
+ services: this.services,
+ session: this.supportParams,
+ data: this.data,
+ });
+ messagingMenu.appendTo($('#qunit-fixture'));
+
+ assert.strictEqual($('.o_chat_window').length, 1,
+ "should have open a chat window");
+ assert.strictEqual($('.o_chat_window .o_thread_message').length, 0,
+ "there should be no message in the thread");
+
+ // simulate an incoming message on the supportBus
+ var data = {
+ author_id: [42, 'An operator'],
+ body: 'A message',
+ channel_ids: [supportChannelID],
+ };
+ var notification = [[false, 'mail.channel'], data];
+ supportBus.trigger('notification', [notification]);
+
+ assert.strictEqual($('.o_chat_window .o_thread_message').length, 1,
+ "there should be a new message in the thread");
+ assert.strictEqual($('.o_chat_window .o_thread_message .o_thread_author ').text().trim(),
+ 'An operator', "should correctly display the author");
+ assert.strictEqual($('.o_chat_window .o_thread_message .o_thread_message_content ').text().trim(),
+ 'A message', "message is correct");
+
+ assert.verifySteps([
+ '/mail/init_messaging',
+ 'cors: /odoo_im_support/get_support_channel',
+ 'cors: /odoo_im_support/fetch_messages',
+ ]);
+
+ messagingMenu.destroy();
+});
+
+});
+
+});
diff --git a/addons/im_support/tests/__init__.py b/addons/im_support/tests/__init__.py
new file mode 100644
index 00000000000..b9df3527588
--- /dev/null
+++ b/addons/im_support/tests/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import test_js
diff --git a/addons/im_support/tests/test_js.py b/addons/im_support/tests/test_js.py
new file mode 100644
index 00000000000..0b52ea4a596
--- /dev/null
+++ b/addons/im_support/tests/test_js.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import odoo.tests
+
+
+@odoo.tests.tagged('post_install', '-at_install')
+class IMSupportSuite(odoo.tests.HttpCase):
+
+ def test_im_support_js(self):
+ self.phantom_js('/im_support/tests?mod=web&failfast', "", "", login='admin', timeout=180)
diff --git a/addons/im_support/views/assets.xml b/addons/im_support/views/assets.xml
index 18ecaa37c1e..de7e8289b8d 100644
--- a/addons/im_support/views/assets.xml
+++ b/addons/im_support/views/assets.xml
@@ -12,5 +12,34 @@
<script type="text/javascript" src="/im_support/static/src/js/systray.js"></script>
</xpath>
</template>
+
+ <template id="qunit_suite" name="im_support_tests" inherit_id="web.qunit_suite">
+ <xpath expr="//t[@t-set='head']" position="inside">
+ <script type="text/javascript" src="/im_support/static/tests/systray_no_support_tests.js"></script>
+ </xpath>
+ </template>
+
+ <template id="im_support.support_qunit_suite">
+ <t t-call="web.layout">
+ <t t-set="html_data" t-value="{'style': 'height: 100%;'}"/>
+ <t t-set="title">IM Support Tests</t>
+ <t t-set="head">
+ <script type="text/javascript">
+ odoo.session_info = {
+ support_token: 'ABCDEFGHIJ',
+ support_origin: 'https://something.com'
+ };
+ </script>
+ <t t-call="web.js_tests_assets"/>
+
+ <script type="text/javascript" src="/im_support/static/tests/helpers/test_utils.js"></script>
+
+ <script type="text/javascript" src="/im_support/static/tests/systray_tests.js"></script>
+ </t>
+
+ <div id="qunit"/>
+ <div id="qunit-fixture"/>
+ </t>
+ </template>
</data>
</openerp>