diff options
Diffstat (limited to 'addons/im_support')
| -rw-r--r-- | addons/im_support/__init__.py | 2 | ||||
| -rw-r--r-- | addons/im_support/controllers/__init__.py | 4 | ||||
| -rw-r--r-- | addons/im_support/controllers/main.py | 12 | ||||
| -rw-r--r-- | addons/im_support/static/src/js/discuss.js | 4 | ||||
| -rw-r--r-- | addons/im_support/static/tests/helpers/test_utils.js | 81 | ||||
| -rw-r--r-- | addons/im_support/static/tests/systray_no_support_tests.js | 51 | ||||
| -rw-r--r-- | addons/im_support/static/tests/systray_tests.js | 342 | ||||
| -rw-r--r-- | addons/im_support/tests/__init__.py | 4 | ||||
| -rw-r--r-- | addons/im_support/tests/test_js.py | 11 | ||||
| -rw-r--r-- | addons/im_support/views/assets.xml | 29 |
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> |
