summaryrefslogtreecommitdiff
path: root/addons/mail
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2018-03-09 16:25:58 +0100
committerChristophe Simonis <chs@odoo.com>2018-03-09 16:25:58 +0100
commit0f57bdba72b592ca097d4cd3da99a75883d8c623 (patch)
tree94eef0c9aaf885c7f717bab5e468ca72b59a219d /addons/mail
parent35f0797002e44ce5673e9fab63710d2764329694 (diff)
parent42659dc70c2b9b124e7e4fdc0449f660c3de5c76 (diff)
[MERGE] forward port branch saas-11.2 up to 42659dc70c2
Diffstat (limited to 'addons/mail')
-rw-r--r--addons/mail/data/mail_shortcode_data.xml17
-rw-r--r--addons/mail/models/mail_activity.py9
-rw-r--r--addons/mail/models/mail_channel.py2
-rw-r--r--addons/mail/static/src/img/smiley/smile_open_mouth.pngbin0 -> 6262 bytes
-rw-r--r--addons/mail/static/src/js/activity.js147
-rw-r--r--addons/mail/static/src/js/composer.js2
-rw-r--r--addons/mail/static/src/js/discuss_mobile.js4
-rw-r--r--addons/mail/static/src/js/document_viewer.js35
-rw-r--r--addons/mail/static/src/less/composer.less4
-rw-r--r--addons/mail/static/src/less/discuss.less8
-rw-r--r--addons/mail/static/src/less/thread.less7
-rw-r--r--addons/mail/static/src/xml/thread.xml27
-rw-r--r--addons/mail/static/tests/chatter_tests.js128
-rw-r--r--addons/mail/static/tests/discuss_mobile_tests.js23
-rw-r--r--addons/mail/static/tests/discuss_tests.js5
-rw-r--r--addons/mail/static/tests/helpers/test_utils.js9
16 files changed, 326 insertions, 101 deletions
diff --git a/addons/mail/data/mail_shortcode_data.xml b/addons/mail/data/mail_shortcode_data.xml
index afd0cf91146..ac648e3c738 100644
--- a/addons/mail/data/mail_shortcode_data.xml
+++ b/addons/mail/data/mail_shortcode_data.xml
@@ -5,18 +5,25 @@
<!-- Smileys -->
<record id="mail_smiley_normal_smile" model="mail.shortcode">
<field name="source">:)</field>
- <field name="unicode_source">😊</field>
- <field name="substitution">&lt;img src='/mail/static/src/img/smiley/normal_smile.png' alt='😊'/&gt;</field>
+ <field name="unicode_source">🙂</field>
+ <field name="substitution">&lt;img src='/mail/static/src/img/smiley/smile.png' alt='🙂'/&gt;</field>
<field name="description">Smile</field>
<field name="shortcode_type">image</field>
</record>
<record id="mail_smiley_smile" model="mail.shortcode">
<field name="source">:D</field>
- <field name="unicode_source">😄</field>
- <field name="substitution">&lt;img src='/mail/static/src/img/smiley/smile.png' alt='😄'/&gt;</field>
+ <field name="unicode_source">😀</field>
+ <field name="substitution">&lt;img src='/mail/static/src/img/smiley/smile_open_mouth.png' alt='😀'/&gt;</field>
<field name="description">Smile with open mouth</field>
<field name="shortcode_type">image</field>
</record>
+ <record id="mail_smiley_smile_eyes" model="mail.shortcode">
+ <field name="source">^^</field>
+ <field name="unicode_source">😊</field>
+ <field name="substitution">&lt;img src='/mail/static/src/img/smiley/normal_smile.png' alt='😊'/&gt;</field>
+ <field name="description">Smile with with smiling eyes</field>
+ <field name="shortcode_type">image</field>
+ </record>
<record id="mail_smiley_laugh" model="mail.shortcode">
<field name="source">xD</field>
<field name="unicode_source">😆</field>
@@ -25,7 +32,7 @@
<field name="shortcode_type">image</field>
</record>
<record id="mail_smiley_tears_of_joy" model="mail.shortcode">
- <field name="source">:-)</field>
+ <field name="source">:joy</field>
<field name="unicode_source">😂</field>
<field name="substitution">&lt;img src='/mail/static/src/img/smiley/tears_of_joy.png' alt='😂'/&gt;</field>
<field name="description">Tears of joy</field>
diff --git a/addons/mail/models/mail_activity.py b/addons/mail/models/mail_activity.py
index 33ebd368b79..c08866fa6a4 100644
--- a/addons/mail/models/mail_activity.py
+++ b/addons/mail/models/mail_activity.py
@@ -358,6 +358,15 @@ class MailActivityMixin(models.AbstractModel):
return [('activity_ids.summary', operator, operand)]
@api.multi
+ def write(self, vals):
+ # Delete activities of archived record.
+ if 'active' in vals and vals['active'] is False:
+ self.env['mail.activity'].sudo().search(
+ [('res_model', '=', self._name), ('res_id', 'in', self.ids)]
+ ).unlink()
+ return super(MailActivityMixin, self).write(vals)
+
+ @api.multi
def unlink(self):
""" Override unlink to delete records activities through (res_model, res_id). """
record_ids = self.ids
diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py
index 5f663963bfa..f94eead0736 100644
--- a/addons/mail/models/mail_channel.py
+++ b/addons/mail/models/mail_channel.py
@@ -56,7 +56,7 @@ class Channel(models.Model):
('channel', 'Channel')],
'Channel Type', default='channel')
description = fields.Text('Description')
- uuid = fields.Char('UUID', size=50, index=True, default=lambda self: '%s' % uuid.uuid4())
+ uuid = fields.Char('UUID', size=50, index=True, default=lambda self: '%s' % uuid.uuid4(), copy=False)
email_send = fields.Boolean('Send messages by email', default=False)
# multi users channel
channel_last_seen_partner_ids = fields.One2many('mail.channel.partner', 'channel_id', string='Last Seen')
diff --git a/addons/mail/static/src/img/smiley/smile_open_mouth.png b/addons/mail/static/src/img/smiley/smile_open_mouth.png
new file mode 100644
index 00000000000..7dcdd3f55fe
--- /dev/null
+++ b/addons/mail/static/src/img/smiley/smile_open_mouth.png
Binary files differ
diff --git a/addons/mail/static/src/js/activity.js b/addons/mail/static/src/js/activity.js
index 37903e3861f..4bb81489789 100644
--- a/addons/mail/static/src/js/activity.js
+++ b/addons/mail/static/src/js/activity.js
@@ -2,7 +2,7 @@ odoo.define('mail.Activity', function (require) {
"use strict";
var AbstractField = require('web.AbstractField');
-var concurrency = require('web.concurrency');
+var BasicModel = require('web.BasicModel');
var core = require('web.core');
var field_registry = require('web.field_registry');
var time = require('web.time');
@@ -12,6 +12,63 @@ var QWeb = core.qweb;
var _t = core._t;
/**
+ * Fetches activities and postprocesses them.
+ *
+ * This standalone function performs an RPC, but to do so, it needs an instance
+ * of a widget that implements the _rpc() function.
+ *
+ * @todo i'm not very proud of the widget instance given in arguments, we should
+ * probably try to do it a better way in the future.
+ *
+ * @param {Widget} self a widget instance that can perform RPCs
+ * @param {Array} ids the ids of activities to read
+ * @return {Deferred<Array>} resolved with the activities
+ */
+function _readActivities(self, ids) {
+ if (!ids.length) {
+ return $.when([]);
+ }
+ return self._rpc({
+ model: 'mail.activity',
+ method: 'read',
+ args: [ids],
+ }).then(function (activities) {
+ // convert create_date and date_deadline to moments
+ _.each(activities, function (activity) {
+ activity.create_date = moment(time.auto_str_to_date(activity.create_date));
+ activity.date_deadline = moment(time.auto_str_to_date(activity.date_deadline));
+ });
+
+ // sort activities by due date
+ activities = _.sortBy(activities, 'date_deadline');
+
+ return activities;
+ });
+}
+
+BasicModel.include({
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Fetches the activities displayed by the activity field widget in form
+ * views.
+ *
+ * @private
+ * @param {Object} record - an element from the localData
+ * @param {string} fieldName
+ * @return {Deferred<Array>} resolved with the activities
+ */
+ _fetchSpecialActivity: function (record, fieldName) {
+ var localID = (record._changes && fieldName in record._changes) ?
+ record._changes[fieldName] :
+ record.data[fieldName];
+ return _readActivities(this, this.localData[localID].res_ids);
+ },
+});
+
+/**
* Set the 'label_delay' entry in activity data according to the deadline date
* @param {Array} activities list of activity Object
* @return {Array} : list of modified activity Object
@@ -44,12 +101,6 @@ var setDelayLabel = function(activities){
};
var AbstractActivityField = AbstractField.extend({
- // inherited
- init: function () {
- this._super.apply(this, arguments);
- this.activities = [];
- },
-
// private
_markActivityDone: function (id, feedback) {
return this._rpc({
@@ -59,33 +110,6 @@ var AbstractActivityField = AbstractField.extend({
kwargs: {feedback: feedback},
});
},
- _readActivities: function () {
- var self = this;
- var missing_ids = _.difference(this.value.res_ids, _.pluck(this.activities, 'id'));
- var fetch_def;
- if (missing_ids.length) {
- fetch_def = this._rpc({
- model: 'mail.activity',
- method: 'read',
- args: [missing_ids],
- });
- }
- return $.when(fetch_def).then(function (results) {
- // convert create_date and date_deadline to moments
- _.each(results, function (activity) {
- activity.create_date = moment(time.auto_str_to_date(activity.create_date));
- activity.date_deadline = moment(time.auto_str_to_date(activity.date_deadline));
- });
-
- // filter out activities that are no longer linked to this record
- self.activities = _.filter(self.activities.concat(results || []), function (activity) {
- return _.contains(self.value.res_ids, activity.id);
- });
-
- // sort activities by due date
- self.activities = _.sortBy(self.activities, 'date_deadline');
- });
- },
_scheduleActivity: function (id, previous_activity_type_id, callback) {
var action = {
type: 'ir.actions.act_window',
@@ -116,41 +140,37 @@ var Activity = AbstractActivityField.extend({
'click .o_activity_unlink': '_onUnlinkActivity',
'click .o_activity_done': '_onMarkActivityDone',
},
+ specialData: '_fetchSpecialActivity',
// inherited
init: function () {
this._super.apply(this, arguments);
- this.dp = new concurrency.DropPrevious();
+ this.activities = this.record.specialData[this.name];
},
_render: function () {
- // note: the rendering of this widget is asynchronous as it needs to
- // fetch the details of the linked activities
- var self = this;
- var fetch_def = this.dp.add(this._readActivities());
- return fetch_def.then(function () {
- _.each(self.activities, function (activity) {
- if (activity.note) {
- activity.note = utils.parse_and_transform(activity.note, utils.add_link);
- }
- });
- var activities = setDelayLabel(self.activities);
- if (activities.length) {
- var nbActivities = _.countBy(activities, 'state');
- self.$el.html(QWeb.render('mail.activity_items', {
- activities: activities,
- nbPlannedActivities: nbActivities.planned,
- nbTodayActivities: nbActivities.today,
- nbOverdueActivities: nbActivities.overdue,
- date_format: time.getLangDateFormat(),
- datetime_format: time.getLangDatetimeFormat(),
- }));
- } else {
- self.$el.empty();
+ _.each(this.activities, function (activity) {
+ if (activity.note) {
+ activity.note = utils.parse_and_transform(activity.note, utils.add_link);
}
});
+ var activities = setDelayLabel(this.activities);
+ if (activities.length) {
+ var nbActivities = _.countBy(activities, 'state');
+ this.$el.html(QWeb.render('mail.activity_items', {
+ activities: activities,
+ nbPlannedActivities: nbActivities.planned,
+ nbTodayActivities: nbActivities.today,
+ nbOverdueActivities: nbActivities.overdue,
+ date_format: time.getLangDateFormat(),
+ datetime_format: time.getLangDatetimeFormat(),
+ }));
+ } else {
+ this.$el.empty();
+ }
},
_reset: function (record) {
this._super.apply(this, arguments);
+ this.activities = this.record.specialData[this.name];
// the mail widgets being persistent, one need to update the res_id on reset
this.res_id = record.res_id;
},
@@ -196,9 +216,6 @@ var Activity = AbstractActivityField.extend({
});
return this.do_action(action, {
on_close: function () {
- // remove the edited activity from the array of fetched activities to
- // force a reload of that activity
- self.activities = _.reject(self.activities, {id: activity_id});
self._reload({activity: true, thread: true});
},
});
@@ -312,10 +329,10 @@ var KanbanActivity = AbstractActivityField.extend({
_renderDropdown: function () {
var self = this;
this.$('.o_activity').html(QWeb.render("mail.KanbanActivityLoading"));
- return this._readActivities().then(function () {
+ return _readActivities(this, this.value.res_ids).then(function (activities) {
self.$('.o_activity').html(QWeb.render("mail.KanbanActivityDropdown", {
selection: self.selection,
- records: _.groupBy(setDelayLabel(self.activities), 'state'),
+ records: _.groupBy(setDelayLabel(activities), 'state'),
uid: self.getSession().uid,
}));
});
@@ -328,7 +345,9 @@ var KanbanActivity = AbstractActivityField.extend({
// handlers
_onButtonClicked: function (event) {
event.preventDefault();
- this._renderDropdown();
+ if (!this.$el.hasClass('open')) {
+ this._renderDropdown();
+ }
},
_onMarkActivityDone: function (event) {
event.stopPropagation();
diff --git a/addons/mail/static/src/js/composer.js b/addons/mail/static/src/js/composer.js
index 8de8a242e68..3a7b9c320e1 100644
--- a/addons/mail/static/src/js/composer.js
+++ b/addons/mail/static/src/js/composer.js
@@ -591,7 +591,7 @@ var BasicComposer = Widget.extend({
*/
_onKeydown: function (event) {
switch(event.which) {
- // UP, DOWN: prevent moving cursor if navigation in mention propositions
+ // UP, DOWN, TAB: prevent moving cursor if navigation in mention propositions
case $.ui.keyCode.UP:
case $.ui.keyCode.DOWN:
case $.ui.keyCode.TAB:
diff --git a/addons/mail/static/src/js/discuss_mobile.js b/addons/mail/static/src/js/discuss_mobile.js
index 52622b0ce32..b427ab4b98c 100644
--- a/addons/mail/static/src/js/discuss_mobile.js
+++ b/addons/mail/static/src/js/discuss_mobile.js
@@ -41,7 +41,7 @@ Discuss.include({
*/
on_attach_callback: function () {
if (this.channel && this._isInInboxTab()) {
- this.thread.scroll_to({offset: this.channels_scrolltop[this.channel.id]});
+ this.thread.scroll_to({offset: this.channelsScrolltop[this.channel.id]});
}
},
/**
@@ -49,7 +49,7 @@ Discuss.include({
*/
on_detach_callback: function () {
if (this._isInInboxTab()) {
- this.channels_scrolltop[this.channel.id] = this.thread.get_scrolltop();
+ this.channelsScrolltop[this.channel.id] = this.thread.get_scrolltop();
}
},
diff --git a/addons/mail/static/src/js/document_viewer.js b/addons/mail/static/src/js/document_viewer.js
index 2fa34b613ba..0aac950cf5b 100644
--- a/addons/mail/static/src/js/document_viewer.js
+++ b/addons/mail/static/src/js/document_viewer.js
@@ -17,6 +17,7 @@ var DocumentViewer = Widget.extend({
'click .o_viewer_video': '_onVideoClicked',
'click .move_next': '_onNext',
'click .move_previous': '_onPrevious',
+ 'click .o_rotate': '_onRotate',
'click .o_zoom_in': '_onZoomIn',
'click .o_zoom_out': '_onZoomOut',
'click .o_close_btn, .o_viewer_img_wrapper': '_onClose',
@@ -104,6 +105,30 @@ var DocumentViewer = Widget.extend({
this._reset();
},
/**
+ * Get CSS transform property based on scale and angle
+ *
+ * @private
+ * @param {float} scale
+ * @param {float} angle
+ */
+ _getTransform: function(scale, angle) {
+ return 'scale3d(' + scale + ', ' + scale + ', 1) rotate(' + angle + 'deg)'
+ },
+ /**
+ * Rotate image clockwise by provided angle
+ *
+ * @private
+ * @param {float} angle
+ */
+ _rotate: function (angle) {
+ this._reset();
+ var new_angle = (this.angle || 0) + angle;
+ this.$('.o_viewer_img').css('transform', this._getTransform(this.scale, new_angle));
+ this.$('.o_viewer_img').css('max-width', new_angle % 180 !== 0 ? $(document).height() : '100%');
+ this.$('.o_viewer_img').css('max-height', new_angle % 180 !== 0 ? $(document).width() : '100%');
+ this.angle = new_angle;
+ },
+ /**
* Zoom in/out image by provided scale
*
* @private
@@ -111,7 +136,7 @@ var DocumentViewer = Widget.extend({
*/
_zoom: function (scale) {
if (scale > 0.5) {
- this.$('.o_viewer_img').css('transform', 'scale3d(' + scale + ', ' + scale + ', 1)');
+ this.$('.o_viewer_img').css('transform', this._getTransform(scale, this.angle || 0));
this.scale = scale;
}
},
@@ -284,6 +309,14 @@ var DocumentViewer = Widget.extend({
* @private
* @param {MouseEvent} e
*/
+ _onRotate: function (e) {
+ e.preventDefault();
+ this._rotate(90);
+ },
+ /**
+ * @private
+ * @param {MouseEvent} e
+ */
_onZoomIn: function (e) {
e.preventDefault();
var scale = this.scale + ZOOM_STEP;
diff --git a/addons/mail/static/src/less/composer.less b/addons/mail/static/src/less/composer.less
index 9df25c895a3..e2fae8b106e 100644
--- a/addons/mail/static/src/less/composer.less
+++ b/addons/mail/static/src/less/composer.less
@@ -264,6 +264,7 @@
img {
width: @o-mail-attachment-image-size;
height: @o-mail-attachment-image-size;
+ image-orientation: from-image; // Only supported in Firefox
}
.o_image_overlay{
display: none;
@@ -365,6 +366,9 @@
.o-flex-display();
justify-content: center;
align-items: center;
+ img {
+ image-orientation: from-image; // Only supported in Firefox
+ }
img, video {
max-width: 100%;
max-height: 100%;
diff --git a/addons/mail/static/src/less/discuss.less b/addons/mail/static/src/less/discuss.less
index 46dc6d41bfd..4e0ae26dccc 100644
--- a/addons/mail/static/src/less/discuss.less
+++ b/addons/mail/static/src/less/discuss.less
@@ -321,15 +321,11 @@
.o_mail_chat_content {
.o-flex(1, 0, 0);
- margin-left: 0;
+ margin-left: 10px;
+ margin-right: 10px;
background-color: transparent;
- .o_mail_no_content {
- .o-position-absolute(@top: 30%);
- text-align: center;
- }
}
-
.o_mail_chat_tab_pane {
overflow-x: scroll;
.o_mail_add_channel {
diff --git a/addons/mail/static/src/less/thread.less b/addons/mail/static/src/less/thread.less
index 8d8bc761829..81dee297011 100644
--- a/addons/mail/static/src/less/thread.less
+++ b/addons/mail/static/src/less/thread.less
@@ -208,11 +208,14 @@
.o_thread_title {
margin-top: 20px;
margin-bottom: 20px;
- font-size: 20px;
+ font-weight: bold;
+ font-size: 125%;
}
.o_mail_no_content {
- .o-webclient-padding();
+ .o-position-absolute(30%, 0, 0, 0);
+ text-align: center;
+ font-size: 115%;
}
.o_thread_message .o_thread_message_core .o_mail_read_more {
diff --git a/addons/mail/static/src/xml/thread.xml b/addons/mail/static/src/xml/thread.xml
index 4a7ba086ebf..26c093c3e84 100644
--- a/addons/mail/static/src/xml/thread.xml
+++ b/addons/mail/static/src/xml/thread.xml
@@ -51,6 +51,11 @@
<div t-if="widget.activeAttachment.type == 'image'" class="o_viewer_toolbar">
<ul class="list-inline">
<li>
+ <a class="o_rotate" href="#">
+ <i class="fa fa-2x fa-repeat"></i>
+ </a>
+ </li>
+ <li>
<a class="o_zoom_out" href="#">
<i class="fa fa-2x fa-search-minus"></i>
</a>
@@ -105,18 +110,14 @@
<!-- When a channel is empty-->
<div t-name="mail.EmptyChannel" class="o_mail_no_content">
- <div class="o_view_nocontent">
- <div class="o_nocontent_help">
- <t t-if="options.channel_id==='channel_inbox'">
- <p>Congratulations, your inbox is empty</p>
- <p>New messages appear here.</p>
- </t>
- <t t-if="options.channel_id==='channel_starred'">
- <p>No starred message</p>
- <p>You can mark any message as 'starred', and it shows up in this channel.</p>
- </t>
- </div>
- </div>
+ <t t-if="options.channel_id==='channel_inbox'">
+ <div class="o_thread_title">Congratulations, your inbox is empty</div>
+ <div>New messages appear here.</div>
+ </t>
+ <t t-if="options.channel_id==='channel_starred'">
+ <div class="o_thread_title">No starred message</div>
+ <div>You can mark any message as 'starred', and it shows up in this channel.</div>
+ </t>
</div>
<t t-name="mail.ChatThread.Content">
@@ -266,7 +267,7 @@
</t>
<t t-name="mail.Attachment">
- <t t-set="type" t-value="attachment.mimetype.split('/').shift()"/>
+ <t t-set="type" t-value="attachment.mimetype and attachment.mimetype.split('/').shift()"/>
<div t-attf-class="o_attachment #{attachment.upload ? 'o_attachment_uploading' : ''}" t-att-title="attachment.name">
<t t-if="type == 'image'">
<div class="o_image_box">
diff --git a/addons/mail/static/tests/chatter_tests.js b/addons/mail/static/tests/chatter_tests.js
index 4a23f9b1d3b..a63e074ef4a 100644
--- a/addons/mail/static/tests/chatter_tests.js
+++ b/addons/mail/static/tests/chatter_tests.js
@@ -351,12 +351,12 @@ QUnit.test('kanban activity widget with an activity', function (assert) {
// click on the activity button to re-open dropdown
$record.find('.o_activity_btn').click();
- assert.strictEqual(rpcCount, 2, 'no RPC should be done as the activities are now in cache');
+ assert.strictEqual(rpcCount, 3, 'should have reloaded the activities');
// mark activity as done
$record.find('.o_mark_as_done').click();
$record = kanban.$('.o_kanban_record').first(); // the record widget has been reset
- assert.strictEqual(rpcCount, 4, 'should have done an RPC to mark activity as done, and a read');
+ assert.strictEqual(rpcCount, 5, 'should have done an RPC to mark activity as done, and a read');
assert.ok($record.find('.o_mail_activity .o_activity_color_default:not(.o_activity_color_today)').length,
"activity widget should have been updated correctly");
assert.strictEqual($record.find('.o_mail_activity.open').length, 1,
@@ -707,6 +707,128 @@ QUnit.test('chatter: Attachment viewer', function (assert) {
form.destroy();
});
+QUnit.test('form activity widget: read RPCs', function (assert) {
+ // Records of model 'mail.activity' may be updated in business flows (e.g.
+ // the date of a 'Meeting' activity is updated when the associated meeting
+ // is dragged&dropped in the Calendar view). Because of that, the activities
+ // must be reloaded when the form is reloaded (the widget can't keep an
+ // internal cache).
+ assert.expect(6);
+ this.data.partner.records[0].activity_ids = [1];
+ this.data.partner.records[0].activity_state = 'today';
+ this.data['mail.activity'].records = [{
+ id: 1,
+ display_name: "An activity",
+ date_deadline: moment().format("YYYY-MM-DD"), // now
+ state: "today",
+ user_id: 2,
+ activity_type_id: 2,
+ }];
+
+ var nbReads = 0;
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ services: this.services,
+ arch: '<form string="Partners">' +
+ '<div class="oe_chatter">' +
+ '<field name="activity_ids" widget="mail_activity"/>' +
+ '</div>' +
+ '</form>',
+ res_id: 2,
+ mockRPC: function (route, args) {
+ if (args.method === 'read' && args.model === 'mail.activity') {
+ nbReads++;
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ assert.strictEqual(nbReads, 1, "should have read the activities");
+ assert.strictEqual(form.$('.o_mail_activity .o_thread_message').length, 1,
+ "should display an activity");
+ assert.strictEqual(form.$('.o_mail_activity .o_thread_message .o_activity_date').text(),
+ 'Today', "the activity should be today");
+
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$buttons.find('.o_form_button_save').click();
+
+ assert.strictEqual(nbReads, 1, "should not have re-read the activities");
+
+ // simulate a date change, and a reload of the form view
+ var tomorrow = moment().add(1, 'day').format("YYYY-MM-DD");
+ this.data['mail.activity'].records[0].date_deadline = tomorrow;
+ form.reload();
+
+ assert.strictEqual(nbReads, 2, "should have re-read the activities");
+ assert.strictEqual(form.$('.o_mail_activity .o_thread_message .o_activity_date').text(),
+ 'Tomorrow', "the activity should be tomorrow");
+
+ form.destroy();
+});
+
+QUnit.test('form activity widget on a new record', function (assert) {
+ assert.expect(0);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ services: this.services,
+ arch: '<form string="Partners">' +
+ '<div class="oe_chatter">' +
+ '<field name="activity_ids" widget="mail_activity"/>' +
+ '</div>' +
+ '</form>',
+ mockRPC: function (route, args) {
+ if (args.method === 'read' && args.model === 'mail.activity') {
+ throw new Error("should not do a read on mail.activity");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ form.destroy();
+});
+
+QUnit.test('form activity widget with another x2many field in view', function (assert) {
+ assert.expect(1);
+
+ this.data.partner.fields.m2m = {string: "M2M", type: 'many2many', relation: 'partner'};
+
+ this.data.partner.records[0].m2m = [2];
+ this.data.partner.records[0].activity_ids = [1];
+ this.data.partner.records[0].activity_state = 'today';
+ this.data['mail.activity'].records = [{
+ id: 1,
+ display_name: "An activity",
+ date_deadline: moment().format("YYYY-MM-DD"), // now
+ state: "today",
+ user_id: 2,
+ activity_type_id: 2,
+ }];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ services: this.services,
+ arch: '<form string="Partners">' +
+ '<field name="m2m" widget="many2many_tags"/>' +
+ '<div class="oe_chatter">' +
+ '<field name="activity_ids" widget="mail_activity"/>' +
+ '</div>' +
+ '</form>',
+ res_id: 2,
+ });
+
+ assert.strictEqual(form.$('.o_mail_activity .o_thread_message').length, 1,
+ "should display an activity");
+
+ form.destroy();
+});
+
QUnit.test('form activity widget: schedule next activity', function (assert) {
assert.expect(5);
this.data.partner.records[0].activity_ids = [1];
@@ -746,7 +868,7 @@ QUnit.test('form activity widget: schedule next activity', function (assert) {
"the feedback should be sent correctly");
return $.when();
}
- if (args.method === 'read' && checkReadArgs) {
+ if (args.method === 'read' && args.model === 'partner' && checkReadArgs) {
assert.deepEqual(args.args[1], ['activity_ids', 'message_ids', 'display_name'],
"should only read the mail fields");
}
diff --git a/addons/mail/static/tests/discuss_mobile_tests.js b/addons/mail/static/tests/discuss_mobile_tests.js
index d17364b53ad..921436d4657 100644
--- a/addons/mail/static/tests/discuss_mobile_tests.js
+++ b/addons/mail/static/tests/discuss_mobile_tests.js
@@ -65,6 +65,29 @@ QUnit.test('mobile basic rendering', function (assert) {
});
});
+QUnit.test('on_{attach/detach}_callback', function (assert) {
+ assert.expect(2);
+ var done = assert.async();
+
+ createDiscuss({
+ id: 1,
+ context: {},
+ params: {},
+ data: this.data,
+ services: this.services,
+ }).then(function (discuss) {
+ try {
+ discuss.on_attach_callback();
+ assert.ok(true, 'should not crash on attach callback');
+ discuss.on_detach_callback();
+ assert.ok(true, 'should not crash on detach callback');
+ } finally {
+ discuss.destroy();
+ done();
+ }
+ });
+});
+
});
});
diff --git a/addons/mail/static/tests/discuss_tests.js b/addons/mail/static/tests/discuss_tests.js
index 4338db1cfaf..6990a04efba 100644
--- a/addons/mail/static/tests/discuss_tests.js
+++ b/addons/mail/static/tests/discuss_tests.js
@@ -44,10 +44,9 @@ QUnit.test('basic rendering', function (assert) {
assert.strictEqual($sidebar.length, 1,
"should have rendered a sidebar");
- var $nocontent = discuss.$('.o_mail_chat_content');
- assert.strictEqual($nocontent.length, 1,
+ assert.strictEqual(discuss.$('.o_mail_chat_content').length, 1,
"should have rendered the content");
- assert.strictEqual($nocontent.find('.o_mail_no_content').length, 1,
+ assert.strictEqual(discuss.$('.o_mail_no_content').length, 1,
"should display no content message");
var $inbox = $sidebar.find('.o_mail_chat_channel_item[data-channel-id=channel_inbox]');
diff --git a/addons/mail/static/tests/helpers/test_utils.js b/addons/mail/static/tests/helpers/test_utils.js
index 095da27be58..cd933bc2483 100644
--- a/addons/mail/static/tests/helpers/test_utils.js
+++ b/addons/mail/static/tests/helpers/test_utils.js
@@ -77,6 +77,15 @@ function createDiscuss(params) {
var selector = params.debug ? 'body' : '#qunit-fixture';
discuss.appendTo($(selector));
+ // override 'destroy' of discuss so that it calls 'destroy' on the parent
+ // instead, which is the parent of discuss and the mockServer.
+ discuss.destroy = function () {
+ // remove the override to properly destroy discuss and its children
+ // when it will be called the second time (by its parent)
+ delete discuss.destroy;
+ parent.destroy();
+ };
+
return discuss.call('chat_manager', 'isReady').then(function () {
return discuss;
});