summaryrefslogtreecommitdiff
path: root/addons/website_livechat/static
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/website_livechat/static
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/website_livechat/static')
-rw-r--r--addons/website_livechat/static/description/icon.pngbin0 -> 8713 bytes
-rw-r--r--addons/website_livechat/static/description/icon.svg1
-rw-r--r--addons/website_livechat/static/src/bugfix/bugfix.js10
-rw-r--r--addons/website_livechat/static/src/bugfix/bugfix.scss6
-rw-r--r--addons/website_livechat/static/src/bugfix/bugfix.xml11
-rw-r--r--addons/website_livechat/static/src/bugfix/bugfix_tests.js18
-rw-r--r--addons/website_livechat/static/src/bugfix/public_bugfix.js25
-rw-r--r--addons/website_livechat/static/src/bugfix/public_bugfix.scss14
-rw-r--r--addons/website_livechat/static/src/bugfix/public_bugfix.xml11
-rw-r--r--addons/website_livechat/static/src/components/discuss/discuss.js31
-rw-r--r--addons/website_livechat/static/src/components/discuss/discuss.xml12
-rw-r--r--addons/website_livechat/static/src/components/discuss/discuss_tests.js269
-rw-r--r--addons/website_livechat/static/src/components/visitor_banner/visitor_banner.js47
-rw-r--r--addons/website_livechat/static/src/components/visitor_banner/visitor_banner.scss90
-rw-r--r--addons/website_livechat/static/src/components/visitor_banner/visitor_banner.xml37
-rw-r--r--addons/website_livechat/static/src/js/website_livechat.editor.js51
-rw-r--r--addons/website_livechat/static/src/legacy/public_livechat.js47
-rw-r--r--addons/website_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler.js32
-rw-r--r--addons/website_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler_tests.js98
-rw-r--r--addons/website_livechat/static/src/models/thread/thread.js45
-rw-r--r--addons/website_livechat/static/src/models/visitor/visitor.js169
-rw-r--r--addons/website_livechat/static/tests/helpers/mock_models.js45
-rw-r--r--addons/website_livechat/static/tests/helpers/mock_server.js78
-rw-r--r--addons/website_livechat/static/tests/tours/website_livechat_common.js165
-rw-r--r--addons/website_livechat/static/tests/tours/website_livechat_rating.js38
-rw-r--r--addons/website_livechat/static/tests/tours/website_livechat_request.js46
26 files changed, 1396 insertions, 0 deletions
diff --git a/addons/website_livechat/static/description/icon.png b/addons/website_livechat/static/description/icon.png
new file mode 100644
index 00000000..7c2a6ea8
--- /dev/null
+++ b/addons/website_livechat/static/description/icon.png
Binary files differ
diff --git a/addons/website_livechat/static/description/icon.svg b/addons/website_livechat/static/description/icon.svg
new file mode 100644
index 00000000..f608435b
--- /dev/null
+++ b/addons/website_livechat/static/description/icon.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="70" height="70" viewBox="0 0 70 70"><defs><path id="a" d="M4 0h61c4 0 5 1 5 5v60c0 4-1 5-5 5H4c-3 0-4-1-4-5V5c0-4 1-5 4-5z"/><linearGradient id="c" x1="100%" x2="0%" y1="0%" y2="100%"><stop offset="0%" stop-color="#CD7690"/><stop offset="100%" stop-color="#CA5377"/></linearGradient></defs><g fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><g mask="url(#b)"><path fill="url(#c)" d="M0 0H70V70H0z"/><path fill="#FFF" fill-opacity=".383" d="M4 1h61c2.667 0 4.333.667 5 2V0H0v3c.667-1.333 2-2 4-2z"/><path fill="#393939" d="M42.103 69H4c-2 0-4-.145-4-4.07V41.395L20 19.14C23.333 15.07 28.333 11 35 11s11.667 7.801 17 17.298v8.14h2V48.65l-2.635 1.09L51 54.754 42.103 69z" opacity=".324"/><path fill="#000" fill-opacity=".383" d="M4 69h61c2.667 0 4.333-1 5-3v4H0v-4c.667 2 2 3 4 3z"/><path fill="#000" d="M35.096 13C25.103 13 18 21.103 18 31.096V35c-1.333 0-2 .667-2 2v8.171C16 48.51 18.662 51 22 51h2c1.333 0 2-.667 2-2V37c0-1.333-.667-2-2-2h-2.979v-3.904c0-7.781 6.294-14.075 14.075-14.075C42.878 17.021 49 23.315 49 31.096V35h-3c-1.333 0-2 .667-2 2v12c0 1.333.667 2 2 2h3c.667 1.333.667 2.667 0 4h-9c0-.667-.328-1-.984-1H36c-.667 0-1 .333-1 1v1c.064.667.398 1 1 1h13c2 0 3-2 3-6 2 0 3-2 3-6v-8c0-1.333-1-2-3-2v-3.904C52 21.103 45.09 13 35.096 13z" opacity=".3"/><path fill="#FFF" d="M35.096 11C25.103 11 18 19.103 18 29.096V33c-1.333 0-2 .667-2 2v8.171C16 46.51 18.662 49 22 49h2c1.333 0 2-.667 2-2V35c0-1.333-.667-2-2-2h-2.979v-3.904c0-7.781 6.294-14.075 14.075-14.075C42.878 15.021 49 21.315 49 29.096V33h-3c-1.333 0-2 .667-2 2v12c0 1.333.667 2 2 2h3c.667 1.333.667 2.667 0 4h-9c0-.667-.328-1-.984-1H36c-.667 0-1 .333-1 1v1c.064.667.398 1 1 1h13c2 0 3-2 3-6 2 0 3-2 3-6v-8c0-1.333-1-2-3-2v-3.904C52 19.103 45.09 11 35.096 11z"/></g></g></svg> \ No newline at end of file
diff --git a/addons/website_livechat/static/src/bugfix/bugfix.js b/addons/website_livechat/static/src/bugfix/bugfix.js
new file mode 100644
index 00000000..b2f0f5e8
--- /dev/null
+++ b/addons/website_livechat/static/src/bugfix/bugfix.js
@@ -0,0 +1,10 @@
+/**
+ * This file allows introducing new JS modules without contaminating other files.
+ * This is useful when bug fixing requires adding such JS modules in stable
+ * versions of Odoo. Any module that is defined in this file should be isolated
+ * in its own file in master.
+ */
+odoo.define('website_livechat/static/src/bugfix/bugfix.js', function (require) {
+'use strict';
+
+});
diff --git a/addons/website_livechat/static/src/bugfix/bugfix.scss b/addons/website_livechat/static/src/bugfix/bugfix.scss
new file mode 100644
index 00000000..c4272e52
--- /dev/null
+++ b/addons/website_livechat/static/src/bugfix/bugfix.scss
@@ -0,0 +1,6 @@
+/**
+* This file allows introducing new styles without contaminating other files.
+* This is useful when bug fixing requires adding new components for instance in
+* stable versions of Odoo. Any style that is defined in this file should be isolated
+* in its own file in master.
+*/
diff --git a/addons/website_livechat/static/src/bugfix/bugfix.xml b/addons/website_livechat/static/src/bugfix/bugfix.xml
new file mode 100644
index 00000000..c17906f7
--- /dev/null
+++ b/addons/website_livechat/static/src/bugfix/bugfix.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates xml:space="preserve">
+
+<!--
+ This file allows introducing new static templates without contaminating other files.
+ This is useful when bug fixing requires adding new components for instance in stable
+ versions of Odoo. Any template that is defined in this file should be isolated
+ in its own file in master.
+-->
+
+</templates>
diff --git a/addons/website_livechat/static/src/bugfix/bugfix_tests.js b/addons/website_livechat/static/src/bugfix/bugfix_tests.js
new file mode 100644
index 00000000..24179c31
--- /dev/null
+++ b/addons/website_livechat/static/src/bugfix/bugfix_tests.js
@@ -0,0 +1,18 @@
+odoo.define('website_livechat/static/src/bugfix/bugfix_tests.js', function (require) {
+'use strict';
+
+/**
+ * This file allows introducing new QUnit test modules without contaminating
+ * other test files. This is useful when bug fixing requires adding new
+ * components for instance in stable versions of Odoo. Any test that is defined
+ * in this file should be isolated in its own file in master.
+ */
+QUnit.module('website_livechat', {}, function () {
+QUnit.module('bugfix', {}, function () {
+QUnit.module('bugfix_tests.js', {
+
+});
+});
+});
+
+});
diff --git a/addons/website_livechat/static/src/bugfix/public_bugfix.js b/addons/website_livechat/static/src/bugfix/public_bugfix.js
new file mode 100644
index 00000000..759fe019
--- /dev/null
+++ b/addons/website_livechat/static/src/bugfix/public_bugfix.js
@@ -0,0 +1,25 @@
+/**
+ * This file allows introducing new JS modules without contaminating other files.
+ * This is useful when bug fixing requires adding such JS modules in stable
+ * versions of Odoo. Any module that is defined in this file should be isolated
+ * in its own file in master.
+ */
+odoo.define('website_livechat/static/src/bugfix/bugfix.js', function (require) {
+'use strict';
+
+const { LivechatButton } = require('im_livechat.legacy.im_livechat.im_livechat');
+
+LivechatButton.include({
+ className: `${LivechatButton.prototype.className} o_bottom_fixed_element`,
+
+ /**
+ * @override
+ */
+ start() {
+ // We trigger a resize to launch the event that checks if this element hides
+ // a button when the page is loaded.
+ $(window).trigger('resize');
+ return this._super(...arguments);
+ },
+});
+});
diff --git a/addons/website_livechat/static/src/bugfix/public_bugfix.scss b/addons/website_livechat/static/src/bugfix/public_bugfix.scss
new file mode 100644
index 00000000..fff015c2
--- /dev/null
+++ b/addons/website_livechat/static/src/bugfix/public_bugfix.scss
@@ -0,0 +1,14 @@
+/**
+* This file allows introducing new styles without contaminating other files.
+* This is useful when bug fixing requires adding new components for instance in
+* stable versions of Odoo. Any style that is defined in this file should be isolated
+* in its own file in master.
+*/
+
+.editor_has_snippets {
+ .o_livechat_button, .o_thread_window {
+ // TODO add this in an edit-mode only file in master (in 14.0 that asset
+ // would be website.assets_wysiwyg...)
+ right: $o-we-sidebar-width !important;
+ }
+}
diff --git a/addons/website_livechat/static/src/bugfix/public_bugfix.xml b/addons/website_livechat/static/src/bugfix/public_bugfix.xml
new file mode 100644
index 00000000..c17906f7
--- /dev/null
+++ b/addons/website_livechat/static/src/bugfix/public_bugfix.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates xml:space="preserve">
+
+<!--
+ This file allows introducing new static templates without contaminating other files.
+ This is useful when bug fixing requires adding new components for instance in stable
+ versions of Odoo. Any template that is defined in this file should be isolated
+ in its own file in master.
+-->
+
+</templates>
diff --git a/addons/website_livechat/static/src/components/discuss/discuss.js b/addons/website_livechat/static/src/components/discuss/discuss.js
new file mode 100644
index 00000000..778b1513
--- /dev/null
+++ b/addons/website_livechat/static/src/components/discuss/discuss.js
@@ -0,0 +1,31 @@
+odoo.define('website_livechat/static/src/components/discuss/discuss.js', function (require) {
+'use strict';
+
+const components = {
+ Discuss: require('mail/static/src/components/discuss/discuss.js'),
+ VisitorBanner: require('website_livechat/static/src/components/visitor_banner/visitor_banner.js'),
+};
+
+components.Discuss.patch('website_livechat/static/src/components/discuss/discuss.js', T =>
+ class extends T {
+
+ /**
+ * @override
+ */
+ _useStoreSelector(props) {
+ const res = super._useStoreSelector(...arguments);
+ const thread = res.thread;
+ const visitor = thread && thread.visitor;
+ return Object.assign({}, res, {
+ visitor,
+ });
+ }
+
+ }
+);
+
+Object.assign(components.Discuss.components, {
+ VisitorBanner: components.VisitorBanner,
+});
+
+});
diff --git a/addons/website_livechat/static/src/components/discuss/discuss.xml b/addons/website_livechat/static/src/components/discuss/discuss.xml
new file mode 100644
index 00000000..af5a1469
--- /dev/null
+++ b/addons/website_livechat/static/src/components/discuss/discuss.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates xml:space="preserve">
+ <t t-inherit="mail.Discuss.content" t-inherit-mode="extension">
+ <xpath expr="//*[hasclass('o_Discuss_thread')]" position="before">
+ <t t-if="discuss.thread.visitor">
+ <VisitorBanner
+ visitorLocalId="discuss.thread.visitor.localId"
+ />
+ </t>
+ </xpath>
+ </t>
+</templates>
diff --git a/addons/website_livechat/static/src/components/discuss/discuss_tests.js b/addons/website_livechat/static/src/components/discuss/discuss_tests.js
new file mode 100644
index 00000000..153e55e4
--- /dev/null
+++ b/addons/website_livechat/static/src/components/discuss/discuss_tests.js
@@ -0,0 +1,269 @@
+odoo.define('website_livechat/static/src/components/discuss/discuss_tests.js', function (require) {
+'use strict';
+
+const {
+ afterEach,
+ beforeEach,
+ start,
+} = require('mail/static/src/utils/test_utils.js');
+
+QUnit.module('website_livechat', {}, function () {
+QUnit.module('components', {}, function () {
+QUnit.module('discuss', {}, function () {
+QUnit.module('discuss_tests.js', {
+ beforeEach() {
+ beforeEach(this);
+
+ this.start = async params => {
+ const { env, widget } = await start(Object.assign({}, params, {
+ autoOpenDiscuss: true,
+ data: this.data,
+ hasDiscuss: true,
+ }));
+ this.env = env;
+ this.widget = widget;
+ };
+ },
+ afterEach() {
+ afterEach(this);
+ },
+});
+
+QUnit.test('rendering of visitor banner', async function (assert) {
+ assert.expect(13);
+
+ this.data['res.country'].records.push({
+ id: 11,
+ code: 'FAKE',
+ });
+ this.data['website.visitor'].records.push({
+ id: 11,
+ country_id: 11,
+ display_name: 'Visitor #11',
+ history: 'Home → Contact',
+ is_connected: true,
+ lang: "English",
+ website: "General website",
+ });
+ this.data['mail.channel'].records.push({
+ channel_type: 'livechat',
+ id: 11,
+ livechat_operator_id: this.data.currentPartnerId,
+ livechat_visitor_id: 11,
+ members: [this.data.currentPartnerId, this.data.publicPartnerId],
+ });
+ await this.start({
+ discuss: {
+ context: {
+ active_id: 'mail.channel_11',
+ },
+ },
+ });
+ assert.containsOnce(
+ document.body,
+ '.o_VisitorBanner',
+ "should have a visitor banner",
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_VisitorBanner_avatar',
+ "should show the visitor avatar in the banner",
+ );
+ assert.strictEqual(
+ document.querySelector('.o_VisitorBanner_avatar').dataset.src,
+ "/mail/static/src/img/smiley/avatar.jpg",
+ "should show the default avatar",
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_VisitorBanner_onlineStatusIcon',
+ "should show the visitor online status icon on the avatar",
+ );
+ assert.strictEqual(
+ document.querySelector('.o_VisitorBanner_country').dataset.src,
+ "/base/static/img/country_flags/FAKE.png",
+ "should show the flag of the country of the visitor",
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_VisitorBanner_visitor',
+ "should show the visitor name in the banner",
+ );
+ assert.strictEqual(
+ document.querySelector('.o_VisitorBanner_visitor').textContent,
+ "Visitor #11",
+ "should have 'Visitor #11' as visitor name",
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_VisitorBanner_language',
+ "should show the visitor language in the banner",
+ );
+ assert.strictEqual(
+ document.querySelector('.o_VisitorBanner_language').textContent,
+ "English",
+ "should have 'English' as language of the visitor",
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_VisitorBanner_website',
+ "should show the visitor website in the banner",
+ );
+ assert.strictEqual(
+ document.querySelector('.o_VisitorBanner_website').textContent,
+ "General website",
+ "should have 'General website' as website of the visitor",
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_VisitorBanner_history',
+ "should show the visitor history in the banner",
+ );
+ assert.strictEqual(
+ document.querySelector('.o_VisitorBanner_history').textContent,
+ "Home → Contact",
+ "should have 'Home → Contact' as history of the visitor",
+ );
+});
+
+QUnit.test('livechat with non-logged visitor should show visitor banner', async function (assert) {
+ assert.expect(1);
+
+ this.data['res.country'].records.push({
+ id: 11,
+ code: 'FAKE',
+ });
+ this.data['website.visitor'].records.push({
+ id: 11,
+ country_id: 11,
+ display_name: 'Visitor #11',
+ history: 'Home → Contact',
+ is_connected: true,
+ lang: "English",
+ website: "General website",
+ });
+ this.data['mail.channel'].records.push({
+ channel_type: 'livechat',
+ id: 11,
+ livechat_operator_id: this.data.currentPartnerId,
+ livechat_visitor_id: 11,
+ members: [this.data.currentPartnerId, this.data.publicPartnerId],
+ });
+ await this.start({
+ discuss: {
+ context: {
+ active_id: 'mail.channel_11',
+ },
+ },
+ });
+ assert.containsOnce(
+ document.body,
+ '.o_VisitorBanner',
+ "should have a visitor banner",
+ );
+});
+
+QUnit.test('livechat with logged visitor should show visitor banner', async function (assert) {
+ assert.expect(2);
+
+ this.data['res.country'].records.push({
+ id: 11,
+ code: 'FAKE',
+ });
+ this.data['res.partner'].records.push({
+ id: 12,
+ name: 'Partner Visitor',
+ });
+ this.data['website.visitor'].records.push({
+ id: 11,
+ country_id: 11,
+ display_name: 'Visitor #11',
+ history: 'Home → Contact',
+ is_connected: true,
+ lang: "English",
+ partner_id: 12,
+ website: "General website",
+ });
+ this.data['mail.channel'].records.push({
+ channel_type: 'livechat',
+ id: 11,
+ livechat_operator_id: this.data.currentPartnerId,
+ livechat_visitor_id: 11,
+ members: [this.data.currentPartnerId, 12],
+ });
+ await this.start({
+ discuss: {
+ context: {
+ active_id: 'mail.channel_11',
+ },
+ },
+ });
+ assert.containsOnce(
+ document.body,
+ '.o_VisitorBanner',
+ "should have a visitor banner",
+ );
+ assert.strictEqual(
+ document.querySelector('.o_VisitorBanner_visitor').textContent,
+ "Partner Visitor",
+ "should have partner name as display name of logged visitor on the visitor banner"
+ );
+});
+
+QUnit.test('livechat without visitor should not show visitor banner', async function (assert) {
+ assert.expect(2);
+
+ this.data['res.partner'].records.push({ id: 11 });
+ this.data['mail.channel'].records.push({
+ channel_type: 'livechat',
+ id: 11,
+ livechat_operator_id: this.data.currentPartnerId,
+ members: [this.data.currentPartnerId, 11],
+ });
+ await this.start({
+ discuss: {
+ context: {
+ active_id: 'mail.channel_11',
+ },
+ },
+ });
+ assert.containsOnce(
+ document.body,
+ '.o_MessageList',
+ "should have a message list",
+ );
+ assert.containsNone(
+ document.body,
+ '.o_VisitorBanner',
+ "should not have any visitor banner",
+ );
+});
+
+QUnit.test('non-livechat channel should not show visitor banner', async function (assert) {
+ assert.expect(2);
+
+ this.data['mail.channel'].records.push({ id: 11, name: "General" });
+ await this.start({
+ discuss: {
+ context: {
+ active_id: 'mail.channel_11',
+ },
+ },
+ });
+ assert.containsOnce(
+ document.body,
+ '.o_MessageList',
+ "should have a message list",
+ );
+ assert.containsNone(
+ document.body,
+ '.o_VisitorBanner',
+ "should not have any visitor banner",
+ );
+});
+
+});
+});
+});
+
+});
diff --git a/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.js b/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.js
new file mode 100644
index 00000000..4d0b95a8
--- /dev/null
+++ b/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.js
@@ -0,0 +1,47 @@
+odoo.define('website_livechat/static/src/components/visitor_banner/visitor_banner.js', function (require) {
+'use strict';
+
+const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
+
+const { Component } = owl;
+
+class VisitorBanner extends Component {
+
+ /**
+ * @override
+ */
+ constructor(...args) {
+ super(...args);
+ useStore(props => {
+ const visitor = this.env.models['website_livechat.visitor'].get(props.visitorLocalId);
+ const country = visitor && visitor.country;
+ return {
+ country: country && country.__state,
+ visitor: visitor ? visitor.__state : undefined,
+ };
+ });
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @returns {website_livechat.visitor}
+ */
+ get visitor() {
+ return this.env.models['website_livechat.visitor'].get(this.props.visitorLocalId);
+ }
+
+}
+
+Object.assign(VisitorBanner, {
+ props: {
+ visitorLocalId: String,
+ },
+ template: 'website_livechat.VisitorBanner',
+});
+
+return VisitorBanner;
+
+});
diff --git a/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.scss b/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.scss
new file mode 100644
index 00000000..9f042d2a
--- /dev/null
+++ b/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.scss
@@ -0,0 +1,90 @@
+// -----------------------------------------------------------------------------
+// Layout
+// -----------------------------------------------------------------------------
+
+.o_VisitorBanner {
+ border-bottom-width: $border-width;
+ border-bottom-style: solid;
+ display: flex;
+ flex: 0 0 auto;
+ padding: map-get($spacers, 4) map-get($spacers, 2);
+}
+
+.o_VisitorBanner_avatar {
+ height: map-get($sizes, 100);
+ width: map-get($sizes, 100);
+ object-fit: cover;
+}
+
+.o_VisitorBanner_avatarContainer {
+ height: $o-mail-thread-avatar-size;
+ width: $o-mail-thread-avatar-size;
+ margin-left: map-get($spacers, 1);
+ margin-right: map-get($spacers, 1);
+ position: relative;
+}
+
+.o_VisitorBanner_country {
+ margin-inline-end: map-get($spacers, 1);
+}
+
+.o_VisitorBanner_history {
+ margin-top: map-get($spacers, 1);
+}
+
+.o_VisitorBanner_historyIcon {
+ margin-inline-end: map-get($spacers, 1);
+}
+
+.o_VisitorBanner_language {
+ margin-inline-end: map-get($spacers, 3);
+}
+
+.o_VisitorBanner_languageIcon {
+ margin-inline-end: map-get($spacers, 1);
+}
+
+.o_VisitorBanner_onlineStatusIcon {
+ @include o-position-absolute($bottom: 0, $right: 0);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-flow: column;
+ width: 1.2em;
+ height: 1.2em;
+ line-height: 1.3em;
+ font-size: x-small;
+}
+
+.o_VisitorBanner_sidebar {
+ display: flex;
+ flex: 0 0 $o-mail-message-sidebar-width;
+ justify-content: center;
+ margin-inline-end: map-get($spacers, 2);
+ max-width: $o-mail-message-sidebar-width;
+}
+
+.o_VisitorBanner_visitor {
+ margin-inline-end: map-get($spacers, 3);
+}
+
+.o_VisitorBanner_websiteIcon {
+ margin-inline-end: map-get($spacers, 1);
+}
+
+// ------------------------------------------------------------------
+// Style
+// ------------------------------------------------------------------
+
+.o_VisitorBanner {
+ background: $white;
+ border-bottom-color: gray('400');
+}
+
+.o_VisitorBanner_onlineStatusIcon {
+ color: $o-enterprise-primary-color;
+}
+
+.o_VisitorBanner_visitor {
+ font-weight: $font-weight-bold;
+}
diff --git a/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.xml b/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.xml
new file mode 100644
index 00000000..b1dc4bd3
--- /dev/null
+++ b/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates xml:space="preserve">
+
+ <t t-name="website_livechat.VisitorBanner" owl="1">
+ <div class="o_VisitorBanner">
+ <div class="o_VisitorBanner_sidebar">
+ <div class="o_VisitorBanner_avatarContainer">
+ <img class="o_VisitorBanner_avatar rounded-circle" t-att-src="visitor.avatarUrl" alt="Avatar"/>
+ <t t-if="visitor.is_connected">
+ <i class="o_VisitorBanner_onlineStatusIcon fa fa-circle" title="Online" role="img" aria-label="Visitor is online"/>
+ </t>
+ </div>
+ </div>
+ <div class="o_VisitorBanner_content">
+ <t t-if="visitor.country">
+ <img class="o_VisitorBanner_country o_country_flag" t-att-src="visitor.country.flagUrl" t-att-alt="visitor.country.code or visitor.country.name"/>
+ </t>
+ <span class="o_VisitorBanner_visitor" t-esc="visitor.nameOrDisplayName"/>
+ <span class="o_VisitorBanner_language">
+ <i class="o_VisitorBanner_languageIcon fa fa-comment-o" aria-label="Lang"/>
+ <t t-esc="visitor.lang"/>
+ </span>
+ <t t-if="visitor.website">
+ <span class="o_VisitorBanner_website">
+ <i class="o_VisitorBanner_websiteIcon fa fa-globe" aria-label="Website"/>
+ <span t-esc="visitor.website"/>
+ </span>
+ </t>
+ <div class="o_VisitorBanner_history">
+ <i class="o_VisitorBanner_historyIcon fa fa-history" aria-label="History"/>
+ <span t-esc="visitor.history"/>
+ </div>
+ </div>
+ </div>
+ </t>
+
+</templates>
diff --git a/addons/website_livechat/static/src/js/website_livechat.editor.js b/addons/website_livechat/static/src/js/website_livechat.editor.js
new file mode 100644
index 00000000..288f2af0
--- /dev/null
+++ b/addons/website_livechat/static/src/js/website_livechat.editor.js
@@ -0,0 +1,51 @@
+odoo.define('website_livechat.editor', function (require) {
+'use strict';
+
+var core = require('web.core');
+var wUtils = require('website.utils');
+var WebsiteNewMenu = require('website.newMenu');
+
+var _t = core._t;
+
+WebsiteNewMenu.include({
+ actions: _.extend({}, WebsiteNewMenu.prototype.actions || {}, {
+ new_channel: '_createNewChannel',
+ }),
+
+ //--------------------------------------------------------------------------
+ // Actions
+ //--------------------------------------------------------------------------
+
+ /**
+ * Asks the user information about a new channel to create, then creates it
+ * and redirects the user to this new channel.
+ *
+ * @private
+ * @returns {Promise} Unresolved if there is a redirection
+ */
+ _createNewChannel: function () {
+ var self = this;
+ return wUtils.prompt({
+ window_title: _t("New Channel"),
+ input: _t("Name"),
+ }).then(function (result) {
+ var name = result.val;
+ if (!name) {
+ return;
+ }
+ return self._rpc({
+ model: 'im_livechat.channel',
+ method: 'create_and_get_website_url',
+ args: [[]],
+ kwargs: {
+ name: name,
+ },
+ }).then(function (url) {
+ window.location.href = url;
+ return new Promise(function () {});
+ });
+ });
+ },
+});
+
+});
diff --git a/addons/website_livechat/static/src/legacy/public_livechat.js b/addons/website_livechat/static/src/legacy/public_livechat.js
new file mode 100644
index 00000000..6bb15735
--- /dev/null
+++ b/addons/website_livechat/static/src/legacy/public_livechat.js
@@ -0,0 +1,47 @@
+odoo.define('website_livechat.legacy.website_livechat.livechat_request', function (require) {
+"use strict";
+
+var utils = require('web.utils');
+var session = require('web.session');
+var LivechatButton = require('im_livechat.legacy.im_livechat.im_livechat').LivechatButton;
+
+
+LivechatButton.include({
+
+ /**
+ * @override
+ * Check if a chat request is opened for this visitor
+ * if yes, replace the session cookie and start the conversation immediately.
+ * Do this before calling super to have everything ready before executing existing start logic.
+ * This is used for chat request mechanism, when an operator send a chat request
+ * from backend to a website visitor.
+ */
+ willStart: function () {
+ if (this.options.chat_request_session) {
+ utils.set_cookie('im_livechat_session', JSON.stringify(this.options.chat_request_session), 60*60);
+ }
+ return this._super();
+ },
+
+ /**
+ * @override
+ * Called when the visitor closes the livechat chatter the first time (first click on X button)
+ * this will deactivate the mail_channel, clean the chat request if any
+ * and allow the operators to send the visitor a new chat request
+ */
+ _onCloseChatWindow: function (ev) {
+ this._super(ev);
+ var cookie = utils.get_cookie('im_livechat_session');
+ if (cookie) {
+ var channel = JSON.parse(cookie);
+ session.rpc('/im_livechat/visitor_leave_session', {uuid: channel.uuid});
+ utils.set_cookie('im_livechat_session', "", -1); // remove cookie
+ }
+ },
+});
+
+return {
+ LivechatButton: LivechatButton,
+};
+
+});
diff --git a/addons/website_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler.js b/addons/website_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler.js
new file mode 100644
index 00000000..d43957ba
--- /dev/null
+++ b/addons/website_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler.js
@@ -0,0 +1,32 @@
+odoo.define('website_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler.js', function (require) {
+'use strict';
+
+const { registerInstancePatchModel } = require('mail/static/src/model/model_core.js');
+
+registerInstancePatchModel('mail.messaging_notification_handler', 'website_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler.js', {
+
+ //----------------------------------------------------------------------
+ // Private
+ //----------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _handleNotificationPartner(data) {
+ const { info } = data;
+ if (info === 'send_chat_request') {
+ this._handleNotificationPartnerChannel(data);
+ const channel = this.env.models['mail.thread'].findFromIdentifyingData({
+ id: data.id,
+ model: 'mail.channel',
+ });
+ this.env.messaging.chatWindowManager.openThread(channel, {
+ makeActive: true,
+ });
+ return;
+ }
+ return this._super(data);
+ },
+});
+
+});
diff --git a/addons/website_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler_tests.js b/addons/website_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler_tests.js
new file mode 100644
index 00000000..4130af64
--- /dev/null
+++ b/addons/website_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler_tests.js
@@ -0,0 +1,98 @@
+odoo.define('website_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler_tests.js', function (require) {
+'use strict';
+
+const {
+ afterEach,
+ afterNextRender,
+ beforeEach,
+ start,
+} = require('mail/static/src/utils/test_utils.js');
+
+const FormView = require('web.FormView');
+const {
+ mock: {
+ intercept,
+ },
+} = require('web.test_utils');
+
+QUnit.module('website_livechat', {}, function () {
+QUnit.module('models', {}, function () {
+QUnit.module('messaging_notification_handler', {}, function () {
+QUnit.module('messaging_notification_handler_tests.js', {
+ beforeEach() {
+ beforeEach(this);
+
+ this.start = async params => {
+ const { env, widget } = await start(Object.assign({}, {
+ data: this.data,
+ }, params));
+ this.env = env;
+ this.widget = widget;
+ };
+ },
+ afterEach() {
+ afterEach(this);
+ },
+});
+
+QUnit.test('should open chat window on send chat request to website visitor', async function (assert) {
+ assert.expect(3);
+
+ this.data['website.visitor'].records.push({
+ id: 11,
+ name: "Visitor #11",
+ });
+ await this.start({
+ data: this.data,
+ hasChatWindow: true,
+ hasView: true,
+ // View params
+ View: FormView,
+ model: 'website.visitor',
+ arch: `
+ <form>
+ <header>
+ <button name="action_send_chat_request" string="Send chat request" class="btn btn-primary" type="button"/>
+ </header>
+ <field name="name"/>
+ </form>
+ `,
+ res_id: 11,
+ });
+ intercept(this.widget, 'execute_action', payload => {
+ this.env.services.rpc({
+ route: '/web/dataset/call_button',
+ params: {
+ args: [payload.data.env.resIDs],
+ kwargs: { context: payload.data.env.context },
+ method: payload.data.action_data.name,
+ model: payload.data.env.model,
+ }
+ });
+ });
+
+ await afterNextRender(() =>
+ document.querySelector('button[name="action_send_chat_request"]').click()
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_ChatWindow',
+ "should have a chat window open after sending chat request to website visitor"
+ );
+ assert.hasClass(
+ document.querySelector('.o_ChatWindow'),
+ 'o-focused',
+ "chat window of livechat should be focused on open"
+ );
+ assert.strictEqual(
+ document.querySelector('.o_ChatWindowHeader_name').textContent,
+ "Visitor #11",
+ "chat window of livechat should have name of visitor in the name"
+ );
+});
+
+});
+});
+});
+
+});
diff --git a/addons/website_livechat/static/src/models/thread/thread.js b/addons/website_livechat/static/src/models/thread/thread.js
new file mode 100644
index 00000000..46d80ca7
--- /dev/null
+++ b/addons/website_livechat/static/src/models/thread/thread.js
@@ -0,0 +1,45 @@
+odoo.define('website_livechat/static/src/models/thread/thread.js', function (require) {
+'use strict';
+
+const {
+ registerClassPatchModel,
+ registerFieldPatchModel,
+} = require('mail/static/src/model/model_core.js');
+const { many2one } = require('mail/static/src/model/model_field.js');
+
+registerClassPatchModel('mail.thread', 'website_livechat/static/src/models/thread/thread.js', {
+
+ //----------------------------------------------------------------------
+ // Public
+ //----------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ convertData(data) {
+ const data2 = this._super(data);
+ if ('visitor' in data) {
+ if (data.visitor) {
+ data2.visitor = [[
+ 'insert',
+ this.env.models['website_livechat.visitor'].convertData(data.visitor)
+ ]];
+ } else {
+ data2.visitor = [['unlink']];
+ }
+ }
+ return data2;
+ },
+
+});
+
+registerFieldPatchModel('mail.thread', 'website_livechat/static/src/models/thread/thread.js', {
+ /**
+ * Visitor connected to the livechat.
+ */
+ visitor: many2one('website_livechat.visitor', {
+ inverse: 'threads',
+ }),
+});
+
+});
diff --git a/addons/website_livechat/static/src/models/visitor/visitor.js b/addons/website_livechat/static/src/models/visitor/visitor.js
new file mode 100644
index 00000000..8c1b417e
--- /dev/null
+++ b/addons/website_livechat/static/src/models/visitor/visitor.js
@@ -0,0 +1,169 @@
+odoo.define('website_livechat/static/src/models/partner/partner.js', function (require) {
+'use strict';
+
+const { registerNewModel } = require('mail/static/src/model/model_core.js');
+const { attr, many2one, one2many } = require('mail/static/src/model/model_field.js');
+
+function factory(dependencies) {
+
+ class Visitor extends dependencies['mail.model'] {
+ //----------------------------------------------------------------------
+ // Public
+ //----------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ static convertData(data) {
+ const data2 = {};
+ if ('country_id' in data) {
+ if (data.country_id) {
+ data2.country = [['insert', {
+ id: data.country_id,
+ code: data.country_code,
+ }]];
+ } else {
+ data2.country = [['unlink']];
+ }
+ }
+ if ('history' in data) {
+ data2.history = data.history;
+ }
+ if ('is_connected' in data) {
+ data2.is_connected = data.is_connected;
+ }
+ if ('lang' in data) {
+ data2.lang = data.lang;
+ }
+ if ('name' in data) {
+ data2.name = data.name;
+ }
+ if ('partner_id' in data) {
+ if (data.partner_id) {
+ data2.partner = [['insert', { id: data.partner_id }]];
+ } else {
+ data2.partner = [['unlink']];
+ }
+ }
+ if ('website' in data) {
+ data2.website = data.website;
+ }
+ return data2;
+ }
+
+ //----------------------------------------------------------------------
+ // Private
+ //----------------------------------------------------------------------
+
+ /**
+ * @private
+ * @returns {string}
+ */
+ _computeAvatarUrl() {
+ if (!this.partner) {
+ return '/mail/static/src/img/smiley/avatar.jpg';
+ }
+ return this.partner.avatarUrl;
+ }
+
+ /**
+ * @private
+ * @returns {mail.country}
+ */
+ _computeCountry() {
+ if (this.partner && this.partner.country) {
+ return [['link', this.partner.country]];
+ }
+ if (this.country) {
+ return [['link', this.country]];
+ }
+ return [['unlink']];
+ }
+
+ /**
+ * @private
+ * @returns {string}
+ */
+ _computeNameOrDisplayName() {
+ if (this.partner) {
+ return this.partner.nameOrDisplayName;
+ }
+ return this.name;
+ }
+ }
+
+ Visitor.fields = {
+ /**
+ * Url to the avatar of the visitor.
+ */
+ avatarUrl: attr({
+ compute: '_computeAvatarUrl',
+ dependencies: [
+ 'partner',
+ 'partnerAvatarUrl',
+ ],
+ }),
+ /**
+ * Country of the visitor.
+ */
+ country: many2one('mail.country', {
+ compute: '_computeCountry',
+ dependencies: [
+ 'country',
+ 'partnerCountry',
+ ],
+ }),
+ /**
+ * Browsing history of the visitor as a string.
+ */
+ history: attr(),
+ /**
+ * Determine whether the visitor is connected or not.
+ */
+ is_connected: attr(),
+ /**
+ * Name of the language of the visitor. (Ex: "English")
+ */
+ lang: attr(),
+ /**
+ * Name of the visitor.
+ */
+ name: attr(),
+ nameOrDisplayName: attr({
+ compute: '_computeNameOrDisplayName',
+ dependencies: [
+ 'name',
+ 'partnerNameOrDisplayName',
+ ],
+ }),
+ /**
+ * Partner linked to this visitor, if any.
+ */
+ partner: many2one('mail.partner'),
+ partnerAvatarUrl: attr({
+ related: 'partner.avatarUrl',
+ }),
+ partnerCountry: many2one('mail.country',{
+ related: 'partner.country',
+ }),
+ partnerNameOrDisplayName: attr({related: 'partner.nameOrDisplayName'}),
+ /**
+ * Threads with this visitor as member
+ */
+ threads: one2many('mail.thread', {
+ inverse: 'visitor',
+ }),
+ /**
+ * Name of the website on which the visitor is connected. (Ex: "Website 1")
+ */
+ website: attr(),
+ };
+
+ Visitor.modelName = 'website_livechat.visitor';
+
+ return Visitor;
+}
+
+registerNewModel('website_livechat.visitor', factory);
+
+});
diff --git a/addons/website_livechat/static/tests/helpers/mock_models.js b/addons/website_livechat/static/tests/helpers/mock_models.js
new file mode 100644
index 00000000..bc407959
--- /dev/null
+++ b/addons/website_livechat/static/tests/helpers/mock_models.js
@@ -0,0 +1,45 @@
+odoo.define('website_livechat/static/tests/helpers/mock_models.js', function (require) {
+'use strict';
+
+const MockModels = require('mail/static/tests/helpers/mock_models.js');
+
+MockModels.patch('website_livechat/static/tests/helpers/mock_models.js', T =>
+ class extends T {
+
+ //----------------------------------------------------------------------
+ // Public
+ //----------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ static generateData() {
+ const data = super.generateData(...arguments);
+ Object.assign(data, {
+ 'website.visitor': {
+ fields: {
+ country_id: { string: "Country", type: 'many2one', relation: 'res.country' },
+ display_name: { string: "Display name", type: 'string' },
+ // Represent the browsing history of the visitor as a string.
+ // To ease testing this allows tests to set it directly instead
+ // of implementing the computation made on server.
+ // This should normally not be a field.
+ history: { string: "History", type: 'string'},
+ is_connected: { string: "Is connected", type: 'boolean' },
+ lang: { string: "Language", type: 'string'},
+ partner_id: {string: "partner", type: "many2one", relation: 'res.partner'},
+ website: { string: "Website", type: 'string' },
+ },
+ records: [],
+ },
+ });
+ Object.assign(data['mail.channel'].fields, {
+ livechat_visitor_id: { string: "Visitor", type: 'many2one', relation: 'website.visitor' },
+ });
+ return data;
+ }
+
+ }
+);
+
+});
diff --git a/addons/website_livechat/static/tests/helpers/mock_server.js b/addons/website_livechat/static/tests/helpers/mock_server.js
new file mode 100644
index 00000000..1383e521
--- /dev/null
+++ b/addons/website_livechat/static/tests/helpers/mock_server.js
@@ -0,0 +1,78 @@
+odoo.define('website_livechat/static/tests/helpers/mock_server.js', function (require) {
+'use strict';
+
+require('im_livechat/static/tests/helpers/mock_server.js'); // ensure mail overrides are applied first
+
+const MockServer = require('web.MockServer');
+
+MockServer.include({
+ /**
+ * Simulate a 'call_button' operation from a view.
+ *
+ * @override
+ */
+ _mockCallButton({ args, kwargs, method, model }) {
+ if (model === 'website.visitor' && method === 'action_send_chat_request') {
+ return this._mockWebsiteVisitorActionSendChatRequest(args[0]);
+ }
+ return this._super(...arguments);
+ },
+ /**
+ * Overrides to add visitor information to livechat channels.
+ *
+ * @override
+ */
+ _mockMailChannelChannelInfo(ids, extra_info) {
+ const channelInfos = this._super(...arguments);
+ for (const channelInfo of channelInfos) {
+ const channel = this._getRecords('mail.channel', [['id', '=', channelInfo.id]])[0];
+ if (channel.channel_type === 'livechat' && channelInfo.livechat_visitor_id) {
+ const visitor = this._getRecords('website.visitor', [['id', '=', channelInfo.livechat_visitor_id]])[0];
+ const country = this._getRecords('res.country', [['id', '=', visitor.country_id]])[0];
+ channelInfo.visitor = {
+ name: visitor.display_name,
+ country_code: country && country.code,
+ country_id: country && country.id,
+ is_connected: visitor.is_connected,
+ history: visitor.history, // TODO should be computed
+ website: visitor.website,
+ lang: visitor.lang,
+ partner_id: visitor.partner_id,
+ }
+ }
+ }
+ return channelInfos;
+ },
+ /**
+ * @private
+ * @param {integer[]} ids
+ */
+ _mockWebsiteVisitorActionSendChatRequest(ids) {
+ const visitors = this._getRecords('website.visitor', [['id', 'in', ids]]);
+ for (const visitor of visitors) {
+ const country = visitor.country_id
+ ? this._getRecords('res.country', [['id', '=', visitor.country_id]])
+ : undefined;
+ const visitor_name = `${visitor.display_name}${country ? `(${country.name})` : ''}`;
+ const members = [this.currentPartnerId];
+ if (visitor.partner_id) {
+ members.push(visitor.partner_id);
+ } else {
+ members.push(this.publicPartnerId);
+ }
+ const livechatId = this._mockCreate('mail.channel', {
+ anonymous_name: visitor_name,
+ channel_type: 'livechat',
+ livechat_operator_id: this.currentPartnerId,
+ members,
+ public: 'private',
+ });
+ // notify operator
+ const channelInfo = this._mockMailChannelChannelInfo([livechatId], 'send_chat_request')[0];
+ const notification = [[false, 'res.partner', this.currentPartnerId], channelInfo];
+ this._widget.call('bus_service', 'trigger', 'notification', [notification]);
+ }
+ },
+});
+
+});
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 00000000..6568a82f
--- /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.legacy.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': '<p>' + message.content + '</p>',
+ '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=5]",
+}, {
+ 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=3]",
+}];
+
+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 00000000..d3339f2b
--- /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 00000000..8c4042e3
--- /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 {};
+});