diff --git a/addons/im_livechat/models/im_livechat_channel.py b/addons/im_livechat/models/im_livechat_channel.py index c9217504cdd..f97c26bd6a8 100644 --- a/addons/im_livechat/models/im_livechat_channel.py +++ b/addons/im_livechat/models/im_livechat_channel.py @@ -184,9 +184,9 @@ class ImLivechatChannel(models.Model): FROM mail_channel c LEFT OUTER JOIN mail_message_mail_channel_rel r ON c.id = r.mail_channel_id LEFT OUTER JOIN mail_message m ON r.mail_message_id = m.id - WHERE m.create_date > ((now() at time zone 'UTC') - interval '30 minutes') - AND c.channel_type = 'livechat' + WHERE c.channel_type = 'livechat' AND c.livechat_operator_id in %s + AND m.create_date > ((now() at time zone 'UTC') - interval '30 minutes') GROUP BY c.livechat_operator_id ORDER BY COUNT(DISTINCT c.id) asc""", (tuple(operators.mapped('partner_id').ids),)) active_channels = self.env.cr.dictfetchall() diff --git a/addons/im_livechat/static/src/js/im_livechat.js b/addons/im_livechat/static/src/js/im_livechat.js index 86ba2e76bd3..000ac6665b9 100644 --- a/addons/im_livechat/static/src/js/im_livechat.js +++ b/addons/im_livechat/static/src/js/im_livechat.js @@ -482,7 +482,7 @@ var Feedback = Widget.extend({ if (reason) { content += " \n" + reason; } - self.trigger('send_message', { content: content }); + self.trigger('send_message', { content: content, isFeedback: true }); }); }, /** diff --git a/addons/website_livechat/__manifest__.py b/addons/website_livechat/__manifest__.py index c53d2bbbb20..10f14ca2196 100644 --- a/addons/website_livechat/__manifest__.py +++ b/addons/website_livechat/__manifest__.py @@ -12,6 +12,7 @@ Allow website visitors to chat with the collaborators. This module also brings a 'application': False, 'auto_install': True, 'data': [ + 'views/assets.xml', 'views/website_livechat.xml', 'views/res_config_settings_views.xml', 'views/website_livechat_view.xml', diff --git a/addons/website_livechat/controllers/__init__.py b/addons/website_livechat/controllers/__init__.py index 757b12a1f17..29bc11c7183 100644 --- a/addons/website_livechat/controllers/__init__.py +++ b/addons/website_livechat/controllers/__init__.py @@ -1,2 +1,3 @@ # -*- coding: utf-8 -*- from . import main +from . import test diff --git a/addons/website_livechat/controllers/test.py b/addons/website_livechat/controllers/test.py new file mode 100644 index 00000000000..0ecabd66690 --- /dev/null +++ b/addons/website_livechat/controllers/test.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.http import Controller, request, route + + +class TestBusController(Controller): + """ + This controller is only useful for test purpose. Bus is unavailable in test mode, but there is no way to know, + at client side, if we are running in test mode or not. This route can be called while running tours to mock + some behaviour in function of the test mode status (activated or not). + + E.g. : To test the livechat and to check there is no duplicates in message displayed in the chatter, + in test mode, we need to mock a 'message added' notification that is normally triggered by the bus. + In Normal mode, the bus triggers itself the notification. + """ + @route('/bus/test_mode_activated', type="json", auth="public") + def is_test_mode_activated(self): + return request.registry.in_test_mode() diff --git a/addons/website_livechat/models/im_livechat_channel.py b/addons/website_livechat/models/im_livechat_channel.py index 6718fd3e48b..3b805b80b7c 100644 --- a/addons/website_livechat/models/im_livechat_channel.py +++ b/addons/website_livechat/models/im_livechat_channel.py @@ -15,6 +15,7 @@ class ImLivechatChannel(models.Model): if not user_id: mail_channel_vals['anonymous_name'] = visitor_sudo.display_name + (' (%s)' % visitor_sudo.country_id.name if visitor_sudo.country_id else '') # As chat requested by the visitor, delete the chat requested by an operator if any to avoid conflicts between two flows + # TODO DBE : Move this into the proper method (open or init mail channel) chat_request_channel = self.env['mail.channel'].sudo().search([('livechat_visitor_id', '=', visitor_sudo.id), ('livechat_active', '=', True)]) for mail_channel in chat_request_channel: mail_channel._close_livechat_session(cancel=True, speaking_with=operator.name) diff --git a/addons/website_livechat/static/tests/tours/website_livechat_common.js b/addons/website_livechat/static/tests/tours/website_livechat_common.js new file mode 100644 index 00000000000..446b1fa8fc9 --- /dev/null +++ b/addons/website_livechat/static/tests/tours/website_livechat_common.js @@ -0,0 +1,165 @@ +odoo.define('website_livechat.tour_common', function(require) { +'use strict'; + +var session = require('web.session'); +var LivechatButton = require('im_livechat.im_livechat').LivechatButton; + +/** + * Alter this method for test purposes. + * + * Fake the notification after sending message + * As bus is not available, it's necessary to add the message in the chatter + in livechat.messages + * + * Add a class to the chatter window after sendFeedback is done + * to force the test to wait until feedback is really done + * (to check afterwards if the livechat session is set to inactive) + * + * Note : this asset is loaded for tests only (rpc call done only during tests) + */ +LivechatButton.include({ + _sendMessage: function (message) { + var self = this; + return this._super.apply(this, arguments).then(function () { + if (message.isFeedback) { + $('div.o_thread_window_header').addClass('feedback_sent'); + } + else { + session.rpc('/bus/test_mode_activated', {}).then(function (in_test_mode) { + if (in_test_mode) { + var notification = [ + self._livechat.getUUID(), + { + 'id': -1, + 'author_id': [0, 'Website Visitor Test'], + 'email_from': 'Website Visitor Test', + 'body': '

' + message.content + '

', + 'is_discussion': true, + 'subtype_id': [1, "Discussions"], + 'date': moment().format('YYYY-MM-DD HH:mm:ss'), + } + ] + self._handleNotification(notification); + } + }); + } + }); + }, +}); + +/******************************* +* Common Steps +*******************************/ + +var startStep = [{ + content: "click on livechat widget", + trigger: "div.o_livechat_button" +}, { + content: "Say hello!", + trigger: "input.o_composer_text_field", + run: "text Hello Sir!" +}, { + content: "Send the message", + trigger: "input.o_composer_text_field", + run: function() { + $('input.o_composer_text_field').trigger($.Event('keydown', {which: $.ui.keyCode.ENTER})); + } +}, { + content: "Verify your message has been typed", + trigger: "div.o_thread_message_content>p:contains('Hello Sir!')" +}, { + content: "Verify there is no duplicates", + trigger: "body", + run: function () { + if ($("div.o_thread_message_content p:contains('Hello Sir!')").length === 1) { + $('body').addClass('no_duplicated_message'); + } + } +}, { + content: "Is your message correctly sent ?", + trigger: 'body.no_duplicated_message' +}]; + +var endDiscussionStep = [{ + content: "Close the chatter", + trigger: "a.o_thread_window_close", + run: function() { + $('a.o_thread_window_close').click(); + } +}]; + +var feedbackStep = [{ + content: "Type a feedback", + trigger: "div.o_livechat_rating_reason > textarea", + run: "text ;-) This was really helpful. Thanks ;-)!" +}, { + content: "Send the feedback", + trigger: "input[type='button'].o_rating_submit_button", +}, { + content: "Check if feedback has been sent", + trigger: "div.o_thread_window_header.feedback_sent", +}, { + content: "Thanks for your feedback", + trigger: "div.o_livechat_rating_box:has(div:contains('Thank you for your feedback'))", +}]; + +var transcriptStep = [{ + content: "Type your email", + trigger: "input[id='o_email']", + run: "text deboul@onner.com" +}, { + content: "Send the conversation to your email address", + trigger: "button.o_email_chat_button", +}, { + content: "Type your email", + trigger: "div.o_livechat_email:has(strong:contains('Conversation Sent'))", +}]; + +var closeStep = [{ + content: "Close the conversation with the x button", + trigger: "a.o_thread_window_close", +}, { + content: "Check that the chat window is closed", + trigger: 'body', + run: function () { + if ($('div.o_livechat_button').length === 1 && !$('div.o_livechat_button').is(':visible')) { + $('body').addClass('tour_success'); + } + } +}, { + content: "Is the Test succeded ?", + trigger: 'body.tour_success' +}]; + +var goodRatingStep = [{ + content: "Send Good Rating", + trigger: "div.o_livechat_rating_choices > img[data-value=10]", +}, { + content: "Check if feedback has been sent", + trigger: "div.o_thread_window_header.feedback_sent", +}, { + content: "Thanks for your feedback", + trigger: "div.o_livechat_rating_box:has(div:contains('Thank you for your feedback'))" +}]; + +var okRatingStep = [{ + content: "Send ok Rating", + trigger: "div.o_livechat_rating_choices > img[data-value=5]", +}]; + +var sadRatingStep = [{ + content: "Send bad Rating", + trigger: "div.o_livechat_rating_choices > img[data-value=1]", +}]; + +return { + 'startStep': startStep, + 'endDiscussionStep': endDiscussionStep, + 'transcriptStep': transcriptStep, + 'feedbackStep': feedbackStep, + 'closeStep': closeStep, + 'goodRatingStep': goodRatingStep, + 'okRatingStep': okRatingStep, + 'sadRatingStep': sadRatingStep, +}; + +}); diff --git a/addons/website_livechat/static/tests/tours/website_livechat_rating.js b/addons/website_livechat/static/tests/tours/website_livechat_rating.js new file mode 100644 index 00000000000..d3339f2b02b --- /dev/null +++ b/addons/website_livechat/static/tests/tours/website_livechat_rating.js @@ -0,0 +1,38 @@ +odoo.define('website_livechat.tour', function(require) { +'use strict'; + +var commonSteps = require("website_livechat.tour_common"); +var tour = require("web_tour.tour"); + +tour.register('website_livechat_complete_flow_tour', { + test: true, + url: '/', +}, [].concat(commonSteps.startStep, commonSteps.endDiscussionStep, commonSteps.okRatingStep, commonSteps.feedbackStep, commonSteps.transcriptStep, commonSteps.closeStep)); + +tour.register('website_livechat_happy_rating_tour', { + test: true, + url: '/', +}, [].concat(commonSteps.startStep, commonSteps.endDiscussionStep, commonSteps.goodRatingStep)); + +tour.register('website_livechat_ok_rating_tour', { + test: true, + url: '/', +}, [].concat(commonSteps.startStep, commonSteps.endDiscussionStep, commonSteps.okRatingStep, commonSteps.feedbackStep)); + +tour.register('website_livechat_sad_rating_tour', { + test: true, + url: '/', +}, [].concat(commonSteps.startStep, commonSteps.endDiscussionStep, commonSteps.sadRatingStep, commonSteps.feedbackStep)); + +tour.register('website_livechat_no_rating_tour', { + test: true, + url: '/', +}, [].concat(commonSteps.startStep, commonSteps.endDiscussionStep, commonSteps.transcriptStep, commonSteps.closeStep)); + +tour.register('website_livechat_no_rating_no_close_tour', { + test: true, + url: '/', +}, [].concat(commonSteps.startStep)); + +return {}; +}); diff --git a/addons/website_livechat/static/tests/tours/website_livechat_request.js b/addons/website_livechat/static/tests/tours/website_livechat_request.js new file mode 100644 index 00000000000..8c4042e3821 --- /dev/null +++ b/addons/website_livechat/static/tests/tours/website_livechat_request.js @@ -0,0 +1,46 @@ +odoo.define('website_livechat.chat_request_tour', function(require) { +'use strict'; + +var commonSteps = require("website_livechat.tour_common"); +var tour = require("web_tour.tour"); + + +var stepWithChatRequestStep = [{ + content: "Answer the chat request!", + trigger: "input.o_composer_text_field", + run: "text Hi ! What a coincidence! I need your help indeed." +}, { + content: "Send the message", + trigger: "input.o_composer_text_field", + run: function() { + $('input.o_composer_text_field').trigger($.Event('keydown', {which: $.ui.keyCode.ENTER})); + } +}, { + content: "Verify your message has been typed", + trigger: "div.o_thread_message_content>p:contains('Hi ! What a coincidence! I need your help indeed.')" +}, { + content: "Verify there is no duplicates", + trigger: "body", + run: function () { + if ($("div.o_thread_message_content p:contains('Hi ! What a coincidence! I need your help indeed.')").length === 1) { + $('body').addClass('no_duplicated_message'); + } + } +}, { + content: "Is your message correctly sent ?", + trigger: 'body.no_duplicated_message' +}]; + + +tour.register('website_livechat_chat_request_part_1_no_close_tour', { + test: true, + url: '/', +}, [].concat(stepWithChatRequestStep)); + +tour.register('website_livechat_chat_request_part_2_end_session_tour', { + test: true, + url: '/', +}, [].concat(commonSteps.endDiscussionStep, commonSteps.okRatingStep, commonSteps.feedbackStep, commonSteps.transcriptStep, commonSteps.closeStep)); + +return {}; +}); diff --git a/addons/website_livechat/tests/__init__.py b/addons/website_livechat/tests/__init__.py new file mode 100644 index 00000000000..df6e5c815ce --- /dev/null +++ b/addons/website_livechat/tests/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +from . import common +from . import test_ui +from . import test_livechat_basic_flow +from . import test_livechat_request diff --git a/addons/website_livechat/tests/common.py b/addons/website_livechat/tests/common.py new file mode 100644 index 00000000000..800dde98b4e --- /dev/null +++ b/addons/website_livechat/tests/common.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, tests + + +class TestLivechatCommon(tests.TransactionCase): + def setUp(self): + super(TestLivechatCommon, self).setUp() + self.base_datetime = fields.Datetime.from_string("2019-11-11 21:30:00") + + self.operator = self.env['res.users'].create({ + 'name': 'Operator Michel', + 'login': 'operator', + 'email': 'operator@example.com', + 'password': "ideboulonate", + 'livechat_username': 'El Deboulonnator', + }) + + self.livechat_channel = self.env['im_livechat.channel'].create({ + 'name': 'The basic channel', + 'user_ids': [(6, 0, [self.operator.id])] + }) + + self.max_sessions_per_operator = 5 + visitor_vals = { + 'lang_id': self.env.ref('base.lang_en').id, + 'country_id': self.env.ref('base.be').id, + 'website_id': self.env.ref('website.default_website').id, + } + self.visitors = self.env['website.visitor'].create([{ + 'lang_id': self.env.ref('base.lang_en').id, + 'country_id': self.env.ref('base.de').id, + 'website_id': self.env.ref('website.default_website').id, + 'partner_id': self.env.ref('base.user_demo').partner_id.id, + }] + [visitor_vals]*self.max_sessions_per_operator) + self.visitor_demo, self.visitor = self.visitors[0], self.visitors[1] + + base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + + self.open_chat_url = base_url + "/im_livechat/get_session" + self.open_chat_params = {'params': { + 'channel_id': self.livechat_channel.id, + 'anonymous_name': "Wrong Name" + }} + + self.send_feedback_url = base_url + "/im_livechat/feedback" + self.leave_session_url = base_url + "/im_livechat/visitor_leave_session" + + # override the get_available_users to return only Michel as available + operators = self.operator + def get_available_users(self): + return operators + self.patch(type(self.env['im_livechat.channel']), '_get_available_users', get_available_users) + + # override the _get_visitor_from_request to return self.visitor + self.target_visitor = self.visitor + def get_visitor_from_request(self_mock, **kwargs): + return self.target_visitor + self.patch(type(self.env['website.visitor']), '_get_visitor_from_request', get_visitor_from_request) + + def _send_message(self, channel, email_from, body, author_id=False): + # As bus is unavailable in test mode, we cannot call /mail/chat_post route to post a message. + # Instead, we post directly the message on the given channel. + channel.with_context(mail_create_nosubscribe=True) \ + .message_post(author_id=author_id, email_from=email_from, body=body, + message_type='comment', subtype_id=self.env.ref('mail.mt_comment').id) + + def _send_rating(self, channel, visitor, rating_value, reason=False): + channel_messages_count = len(channel.message_ids) + + rating_to_emoji = {1: "😞", 5: "😐", 10: "😊"} + self.opener.post(url=self.send_feedback_url, json={'params': { + 'uuid': channel.uuid, + 'rate': rating_value, + 'reason': reason, + }}) + res_model_id = self.env['ir.model'].sudo().search([('model', '=', channel._name)], limit=1).id + rating = self.env['rating.rating'].search([('res_id', '=', channel.id), ('res_model_id', '=', res_model_id)]) + self.assertEqual(rating.rating, rating_value, "The rating is not correct.") + + message = "Rating: %s" % rating_to_emoji[rating_value] + if reason: + message += " \n%s" % reason + + self._send_message(channel, visitor.display_name, message, author_id=False) + self.assertEqual(len(channel.message_ids), channel_messages_count + 1) diff --git a/addons/website_livechat/tests/test_livechat_basic_flow.py b/addons/website_livechat/tests/test_livechat_basic_flow.py new file mode 100644 index 00000000000..aa3fa5a4e0e --- /dev/null +++ b/addons/website_livechat/tests/test_livechat_basic_flow.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import datetime + +from odoo import tests, _ +from odoo.addons.website_livechat.tests.common import TestLivechatCommon + + +@tests.tagged('post_install', '-at_install') +class TestLivechatBasicFlowHttpCase(tests.HttpCase, TestLivechatCommon): + def test_visitor_banner_history(self): + # create visitor history + self.env['website.track'].create([{ + 'page_id': self.env.ref('website.homepage_page').id, + 'visitor_id': self.visitor.id, + 'visit_datetime': self.base_datetime, + }, { + 'page_id': self.env.ref('website.contactus_page').id, + 'visitor_id': self.visitor.id, + 'visit_datetime': self.base_datetime - datetime.timedelta(minutes=10), + }, { + 'page_id': self.env.ref('website.aboutus_page').id, + 'visitor_id': self.visitor.id, + 'visit_datetime': self.base_datetime - datetime.timedelta(minutes=20), + }, { + 'page_id': self.env.ref('website.contactus_page').id, + 'visitor_id': self.visitor.id, + 'visit_datetime': self.base_datetime - datetime.timedelta(minutes=30), + }]) + + handmade_history = "%s (21:10) → %s (21:20) → %s (21:30)" % ( + self.env.ref('website.aboutus_page').name, + self.env.ref('website.contactus_page').name, + self.env.ref('website.homepage_page').name + ) + history = self.env['mail.channel']._get_visitor_history(self.visitor) + + self.assertEqual(history, handmade_history) + + def test_livechat_username(self): + # Open a new live chat + res = self.opener.post(url=self.open_chat_url, json=self.open_chat_params) + self.assertEqual(res.status_code, 200) + channel_1 = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor.id), ('livechat_active', '=', True)], limit=1) + + # Check Channel naming + self.assertEqual(channel_1.name, "%s, %s" % (self.visitor.display_name, self.operator.livechat_username)) + channel_1.unlink() + + # Remove livechat_username + self.operator.livechat_username = False + + # Open a new live chat + res = self.opener.post(url=self.open_chat_url, json=self.open_chat_params) + self.assertEqual(res.status_code, 200) + channel_2 = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor.id), ('livechat_active', '=', True)], limit=1) + + # Check Channel naming + self.assertEqual(channel_2.name, "%s, %s" % (self.visitor.display_name, self.operator.name)) + + def test_basic_flow_with_rating(self): + channel = self._common_basic_flow() + + self._send_rating(channel, self.visitor, 5, "This deboulonnage was fine but not topitop.") + + channel._close_livechat_session() + + self.assertEqual(len(channel.message_ids), 4) + self.assertEqual(channel.message_ids[0].author_id, self.env.ref('base.partner_root'), "Odoobot must be the sender of the 'has left the conversation' message.") + self.assertEqual(channel.message_ids[0].body, "

%s has left the conversation.

" % self.visitor.display_name) + self.assertEqual(channel.livechat_active, False, "The livechat session must be inactive as the visitor sent his feedback.") + + def test_basic_flow_without_rating(self): + channel = self._common_basic_flow() + + # has left the conversation + channel._close_livechat_session() + self.assertEqual(len(channel.message_ids), 3) + self.assertEqual(channel.message_ids[0].author_id, self.env.ref('base.partner_root'), "Odoobot must be the author the message.") + self.assertEqual(channel.message_ids[0].body, "

%s has left the conversation.

" % self.visitor.display_name) + self.assertEqual(channel.livechat_active, False, "The livechat session must be inactive since visitor has left the conversation.") + + def _common_basic_flow(self): + # Open a new live chat + res = self.opener.post(url=self.open_chat_url, json=self.open_chat_params) + self.assertEqual(res.status_code, 200) + + channel = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor.id), ('livechat_active', '=', True)], limit=1) + + # Check Channel and Visitor naming + self.assertEqual(self.visitor.display_name, "%s #%s" % (_("Website Visitor"), self.visitor.id)) + self.assertEqual(channel.name, "%s, %s" % (self.visitor.display_name, self.operator.livechat_username)) + + # Post Message from visitor + self._send_message(channel, self.visitor.display_name, "Message from Visitor") + + self.assertEqual(len(channel.message_ids), 1) + self.assertEqual(channel.message_ids[0].author_id.id, False, "The author of the message is not a partner.") + self.assertEqual(channel.message_ids[0].email_from, self.visitor.display_name, "The sender's email should be the visitor's email.") + self.assertEqual(channel.message_ids[0].body, "

Message from Visitor

") + self.assertEqual(channel.livechat_active, True, "The livechat session must be active as the visitor did not left the conversation yet.") + + # Post message from operator + self._send_message(channel, self.operator.email, "Message from Operator", author_id=self.operator.partner_id.id) + + self.assertEqual(len(channel.message_ids), 2) + self.assertEqual(channel.message_ids[0].author_id, self.operator.partner_id, "The author of the message should be the operator.") + self.assertEqual(channel.message_ids[0].email_from, self.operator.email, "The sender's email should be the operator's email.") + self.assertEqual(channel.message_ids[0].body, "

Message from Operator

") + self.assertEqual(channel.livechat_active, True, "The livechat session must be active as the visitor did not left the conversation yet.") + + return channel diff --git a/addons/website_livechat/tests/test_livechat_request.py b/addons/website_livechat/tests/test_livechat_request.py new file mode 100644 index 00000000000..3e4025e4524 --- /dev/null +++ b/addons/website_livechat/tests/test_livechat_request.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import tests +from odoo.addons.website_livechat.tests.common import TestLivechatCommon + + +@tests.tagged('post_install', '-at_install') +class TestLivechatRequestHttpCase(tests.HttpCase, TestLivechatCommon): + def test_livechat_request_complete_flow(self): + self._clean_livechat_sessions() + + # Send first chat request - Open chat from operator side + channel_1 = self._common_chat_request_flow() + # Visitor Rates the conversation (Good) + self._send_rating(channel_1, self.visitor, 10) + + # Operator Re-Send a chat request + channel_2 = self._common_chat_request_flow() + # Visitor Rates the conversation (Bad) + self._send_rating(channel_2, self.visitor, 1, "Stop bothering me! I hate you %s has left the conversation.

" % self.visitor.display_name) + self.assertEqual(channel.livechat_active, False, "The livechat session must be inactive as the visitor sent his feedback.") + + return channel + + def _clean_livechat_sessions(self): + # clean every possible mail channel linked to the visitor + active_channels = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor.id), ('livechat_active', '=', True)]) + for active_channel in active_channels: + active_channel._close_livechat_session() diff --git a/addons/website_livechat/tests/test_ui.py b/addons/website_livechat/tests/test_ui.py new file mode 100644 index 00000000000..cf8336c1c4f --- /dev/null +++ b/addons/website_livechat/tests/test_ui.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import tests, _ +from odoo.addons.website_livechat.tests.common import TestLivechatCommon + + +@tests.tagged('post_install', '-at_install') +class TestLivechatUI(tests.HttpCase, TestLivechatCommon): + def setUp(self): + super(TestLivechatUI, self).setUp() + self.visitor_tour = self.env['website.visitor'].create({ + 'name': 'Visitor Tour', + 'website_id': self.env.ref('website.default_website').id, + }) + self.target_visitor = self.visitor_tour + + def test_complete_rating_flow_ui(self): + self.start_tour("/", 'website_livechat_complete_flow_tour') + self._check_end_of_rating_tours() + + def test_happy_rating_flow_ui(self): + self.start_tour("/", 'website_livechat_happy_rating_tour') + self._check_end_of_rating_tours() + + def test_ok_rating_flow_ui(self): + self.start_tour("/", 'website_livechat_ok_rating_tour') + self._check_end_of_rating_tours() + + def test_bad_rating_flow_ui(self): + self.start_tour("/", 'website_livechat_sad_rating_tour') + self._check_end_of_rating_tours() + + def test_no_rating_flow_ui(self): + self.start_tour("/", 'website_livechat_no_rating_tour') + channel = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor_tour.id)]) + self.assertEqual(len(channel), 1, "There can only be one channel created for 'Visitor Tour'.") + self.assertEqual(channel.livechat_active, False, 'Livechat must be inactive after closing the chat window.') + + def test_no_rating_no_close_flow_ui(self): + self.start_tour("/", 'website_livechat_no_rating_no_close_tour') + channel = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor_tour.id)]) + self.assertEqual(len(channel), 1, "There can only be one channel created for 'Visitor Tour'.") + self.assertEqual(channel.livechat_active, True, 'Livechat must be active while the chat window is not closed.') + + def test_empty_chat_request_flow_no_rating_no_close_ui(self): + # Open an empty chat request + self.visitor_tour.with_user(self.operator).action_send_chat_request() + chat_request = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor_tour.id), ('livechat_active', '=', True)]) + + # Visitor ask a new livechat session before the operator start to send message in chat request session + self.start_tour("/", 'website_livechat_no_rating_no_close_tour') + + # Visitor's session must be active (gets the priority) + channel = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor_tour.id), ('livechat_active', '=', True)]) + self.assertEqual(len(channel), 1, "There can only be one channel created for 'Visitor Tour'.") + self.assertEqual(channel.livechat_active, True, 'Livechat must be active while the chat window is not closed.') + + # Check that the chat request has been canceled. + chat_request.invalidate_cache() + self.assertEqual(chat_request.livechat_active, False, "The livechat request must be inactive as the visitor started himself a livechat session.") + + def test_chat_request_flow_with_rating_ui(self): + # Open a chat request + self.visitor_tour.with_user(self.operator).action_send_chat_request() + chat_request = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor_tour.id), ('livechat_active', '=', True)]) + + # Operator send a message to the visitor + self._send_message(chat_request, self.operator.email, "Hello my friend !", author_id=self.operator.partner_id.id) + self.assertEqual(len(chat_request.message_ids), 1, "Number of messages incorrect.") + + # Visitor comes to the website and receives the chat request + self.start_tour("/", 'website_livechat_chat_request_part_1_no_close_tour') + + # Check that the current session is the chat request + channel = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor_tour.id), ('livechat_active', '=', True)]) + self.assertEqual(len(channel), 1, "There can only be one channel created for 'Visitor Tour'.") + self.assertEqual(channel, chat_request, "The active livechat session must be the chat request one.") + + # Visitor reload the page and continues the chat with the operator normally + self.start_tour("/", 'website_livechat_chat_request_part_2_end_session_tour') + self._check_end_of_rating_tours() + + def _check_end_of_rating_tours(self): + channel = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor_tour.id)]) + self.assertEqual(len(channel), 1, "There can only be one channel created for 'Visitor Tour'.") + self.assertEqual(channel.livechat_active, False, 'Livechat must be inactive after rating.') diff --git a/addons/website_livechat/views/assets.xml b/addons/website_livechat/views/assets.xml new file mode 100644 index 00000000000..42a6f1bacd1 --- /dev/null +++ b/addons/website_livechat/views/assets.xml @@ -0,0 +1,12 @@ + + + + + +