Commit 3c0e3d3f authored by JC Brand's avatar JC Brand

Refactor out `createMessage`.

Changes:

* Avoids leaky abstraction of MUC code into converse-chatboxes
* Avoid creating unnecessary message objects (e.g. without <body)
* Add fix for #1369.
* Rename spec/chatroom.js to spec/muc.js
parent 3aaff4e9
......@@ -51920,11 +51920,11 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__["default"].plugins
'afterShown': _.noop
});
function onHeadlineMessage(message) {
async function onHeadlineMessage(message) {
/* Handler method for all incoming messages of type "headline". */
const from_jid = message.getAttribute('from');
if (utils.isHeadlineMessage(_converse, message)) {
const from_jid = message.getAttribute('from');
if (_.includes(from_jid, '@') && !_converse.api.contacts.get(from_jid) && !_converse.allow_non_roster_messaging) {
return;
}
......@@ -51942,19 +51942,21 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__["default"].plugins
'from': from_jid
});
chatbox.createMessage(message, message);
const attrs = await chatbox.getMessageAttributesFromStanza(message, message);
await chatbox.messages.create(attrs);
_converse.emit('message', {
'chatbox': chatbox,
'stanza': message
});
}
return true;
}
function registerHeadlineHandler() {
_converse.connection.addHandler(onHeadlineMessage, null, 'message');
_converse.connection.addHandler(message => {
onHeadlineMessage(message);
return true;
}, null, 'message');
}
_converse.on('connected', registerHeadlineHandler);
......@@ -61803,8 +61805,10 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
'is_delayed': !_.isNil(delay),
'is_spoiler': !_.isNil(spoiler),
'message': _converse.chatboxes.getMessageBody(stanza) || undefined,
'references': this.getReferencesFromStanza(stanza),
'msgid': stanza.getAttribute('id'),
'references': this.getReferencesFromStanza(stanza),
'subject': _.propertyOf(stanza.querySelector('subject'))('textContent'),
'thread': _.propertyOf(stanza.querySelector('thread'))('textContent'),
'time': delay ? delay.getAttribute('stamp') : moment().format(),
'type': stanza.getAttribute('type')
};
......@@ -61837,25 +61841,6 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
return attrs;
},
async createMessage(message, original_stanza) {
/* Create a Backbone.Message object inside this chat box
* based on the identified message stanza.
*/
const attrs = await this.getMessageAttributesFromStanza(message, original_stanza),
is_csn = u.isOnlyChatStateNotification(attrs);
if (is_csn && (attrs.is_delayed || attrs.type === 'groupchat' && Strophe.getResourceFromJid(attrs.from) == this.get('nick'))) {
// XXX: MUC leakage
// No need showing delayed or our own CSN messages
return;
} else if (!is_csn && !attrs.file && !attrs.plaintext && !attrs.message && !attrs.oob_url && attrs.type !== 'error') {
// TODO: handle <subject> messages (currently being done by ChatRoom)
return;
} else {
return this.messages.create(attrs);
}
},
isHidden() {
/* Returns a boolean to indicate whether a newly received
* message will be visible to the user or not.
......@@ -61952,7 +61937,7 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
});
},
onErrorMessage(message) {
async onErrorMessage(message) {
/* Handler method for all incoming error message stanzas
*/
const from_jid = Strophe.getBareJidFromJid(message.getAttribute('from'));
......@@ -61990,8 +61975,8 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
_converse.log(message, Strophe.LogLevel.ERROR);
}
chatbox.createMessage(message, message);
return true;
const attrs = await chatbox.getMessageAttributesFromStanza(message, message);
chatbox.messages.create(attrs);
},
getMessageBody(stanza) {
......@@ -62023,7 +62008,7 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
_converse.api.send(receipt_stanza);
},
onMessage(stanza) {
async onMessage(stanza) {
/* Handler method for all incoming single-user chat "message"
* stanzas.
*
......@@ -62104,7 +62089,9 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
if (!message) {
// Only create the message when we're sure it's not a duplicate
chatbox.createMessage(stanza, original_stanza).then(msg => chatbox.incrementUnreadMsgCounter(msg)).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
const attrs = await chatbox.getMessageAttributesFromStanza(stanza, original_stanza);
const msg = chatbox.messages.create(attrs);
chatbox.incrementUnreadMsgCounter(msg);
}
}
......@@ -62112,8 +62099,6 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
'stanza': original_stanza,
'chatbox': chatbox
});
return true;
},
getChatBox(jid, attrs = {}, create) {
......@@ -65298,7 +65283,9 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam
this.addSpinner();
_converse.api.archive.query(_.extend({
_converse.api.archive.query( // TODO: only query from the last message we have
// in our history
_.extend({
'groupchat': is_groupchat,
'before': '',
// Page backwards from the most recent message
......@@ -66839,28 +66826,37 @@ _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].plugins.add('converse-muc
return;
}
const jid = stanza.getAttribute('from'),
resource = Strophe.getResourceFromJid(jid),
sender = resource && Strophe.unescapeNode(resource) || '';
if (!this.handleMessageCorrection(stanza)) {
if (sender === '') {
return;
}
const attrs = await this.getMessageAttributesFromStanza(stanza, original_stanza);
const subject_el = stanza.querySelector('subject');
if (!attrs.nick) {
return;
}
if (subject_el) {
const subject = _.propertyOf(subject_el)('textContent') || '';
if (!this.handleMessageCorrection(stanza)) {
if (attrs.subject && !attrs.thread && !attrs.message) {
// https://xmpp.org/extensions/xep-0045.html#subject-mod
// -----------------------------------------------------
// The subject is changed by sending a message of type "groupchat" to the <room@service>,
// where the <message/> MUST contain a <subject/> element that specifies the new subject but
// MUST NOT contain a <body/> element (or a <thread/> element).
_utils_form__WEBPACK_IMPORTED_MODULE_7__["default"].safeSave(this, {
'subject': {
'author': sender,
'text': subject
'author': attrs.nick,
'text': attrs.subject || ''
}
});
return;
}
const is_csn = _utils_form__WEBPACK_IMPORTED_MODULE_7__["default"].isOnlyChatStateNotification(attrs),
own_message = Strophe.getResourceFromJid(attrs.from) == this.get('nick');
if (is_csn && (attrs.is_delayed || own_message)) {
// No need showing delayed or our own CSN messages
return;
}
const msg = await this.createMessage(stanza, original_stanza);
const msg = await this.messages.create(attrs);
if (forwarded && msg && msg.get('sender') === 'me') {
msg.save({
......@@ -66871,7 +66867,7 @@ _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].plugins.add('converse-muc
this.incrementUnreadMsgCounter(msg);
}
if (sender !== this.get('nick')) {
if (attrs.nick !== this.get('nick')) {
// We only emit an event if it's not our own message
_converse.emit('message', {
'stanza': original_stanza,
......@@ -62,114 +62,111 @@
it("autocompletes when the user presses tab",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy')
.then(() => {
const view = _converse.chatboxviews.get('lounge@localhost');
expect(view.model.occupants.length).toBe(1);
let presence = $pres({
'to': 'dummy@localhost/resource',
'from': 'lounge@localhost/some1'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'some1@localhost/resource',
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(view.model.occupants.length).toBe(2);
const textarea = view.el.querySelector('textarea.chat-textarea');
textarea.value = "hello som";
// Press tab
const tab_event = {
'target': textarea,
'preventDefault': _.noop,
'stopPropagation': _.noop,
'keyCode': 9
}
view.keyPressed(tab_event);
view.keyUp(tab_event);
expect(view.el.querySelector('.suggestion-box__results').hidden).toBeFalsy();
expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(1);
expect(view.el.querySelector('.suggestion-box__results li').textContent).toBe('some1');
const backspace_event = {
'target': textarea,
'preventDefault': _.noop,
'keyCode': 8
}
for (var i=0; i<3; i++) {
// Press backspace 3 times to remove "som"
view.keyPressed(backspace_event);
textarea.value = textarea.value.slice(0, textarea.value.length-1)
view.keyUp(backspace_event);
}
expect(view.el.querySelector('.suggestion-box__results').hidden).toBeTruthy();
presence = $pres({
'to': 'dummy@localhost/resource',
'from': 'lounge@localhost/some2'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'some2@localhost/resource',
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
textarea.value = "hello s s";
view.keyPressed(tab_event);
view.keyUp(tab_event);
expect(view.el.querySelector('.suggestion-box__results').hidden).toBeFalsy();
expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(2);
const up_arrow_event = {
'target': textarea,
'preventDefault': () => (up_arrow_event.defaultPrevented = true),
'stopPropagation': _.noop,
'keyCode': 38
}
view.keyPressed(up_arrow_event);
view.keyUp(up_arrow_event);
expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(2);
expect(view.el.querySelector('.suggestion-box__results li[aria-selected="false"]').textContent).toBe('some1');
expect(view.el.querySelector('.suggestion-box__results li[aria-selected="true"]').textContent).toBe('some2');
view.keyPressed({
'target': textarea,
'preventDefault': _.noop,
'stopPropagation': _.noop,
'keyCode': 13 // Enter
async function (done, _converse) {
await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
const view = _converse.chatboxviews.get('lounge@localhost');
expect(view.model.occupants.length).toBe(1);
let presence = $pres({
'to': 'dummy@localhost/resource',
'from': 'lounge@localhost/some1'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'some1@localhost/resource',
'role': 'participant'
});
expect(textarea.value).toBe('hello s @some2 ');
// Test that pressing tab twice selects
presence = $pres({
'to': 'dummy@localhost/resource',
'from': 'lounge@localhost/z3r0'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'z3r0@localhost/resource',
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
textarea.value = "hello z";
view.keyPressed(tab_event);
view.keyUp(tab_event);
view.keyPressed(tab_event);
view.keyUp(tab_event);
expect(textarea.value).toBe('hello @z3r0 ');
done();
}).catch(_.partial(console.error, _));
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(view.model.occupants.length).toBe(2);
const textarea = view.el.querySelector('textarea.chat-textarea');
textarea.value = "hello som";
// Press tab
const tab_event = {
'target': textarea,
'preventDefault': _.noop,
'stopPropagation': _.noop,
'keyCode': 9
}
view.keyPressed(tab_event);
view.keyUp(tab_event);
expect(view.el.querySelector('.suggestion-box__results').hidden).toBeFalsy();
expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(1);
expect(view.el.querySelector('.suggestion-box__results li').textContent).toBe('some1');
const backspace_event = {
'target': textarea,
'preventDefault': _.noop,
'keyCode': 8
}
for (var i=0; i<3; i++) {
// Press backspace 3 times to remove "som"
view.keyPressed(backspace_event);
textarea.value = textarea.value.slice(0, textarea.value.length-1)
view.keyUp(backspace_event);
}
expect(view.el.querySelector('.suggestion-box__results').hidden).toBeTruthy();
presence = $pres({
'to': 'dummy@localhost/resource',
'from': 'lounge@localhost/some2'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'some2@localhost/resource',
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
textarea.value = "hello s s";
view.keyPressed(tab_event);
view.keyUp(tab_event);
expect(view.el.querySelector('.suggestion-box__results').hidden).toBeFalsy();
expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(2);
const up_arrow_event = {
'target': textarea,
'preventDefault': () => (up_arrow_event.defaultPrevented = true),
'stopPropagation': _.noop,
'keyCode': 38
}
view.keyPressed(up_arrow_event);
view.keyUp(up_arrow_event);
expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(2);
expect(view.el.querySelector('.suggestion-box__results li[aria-selected="false"]').textContent).toBe('some1');
expect(view.el.querySelector('.suggestion-box__results li[aria-selected="true"]').textContent).toBe('some2');
view.keyPressed({
'target': textarea,
'preventDefault': _.noop,
'stopPropagation': _.noop,
'keyCode': 13 // Enter
});
expect(textarea.value).toBe('hello s @some2 ');
// Test that pressing tab twice selects
presence = $pres({
'to': 'dummy@localhost/resource',
'from': 'lounge@localhost/z3r0'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'z3r0@localhost/resource',
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
textarea.value = "hello z";
view.keyPressed(tab_event);
view.keyUp(tab_event);
view.keyPressed(tab_event);
view.keyUp(tab_event);
expect(textarea.value).toBe('hello @z3r0 ');
done();
}));
});
}));
This diff is collapsed.
This diff is collapsed.
......@@ -411,7 +411,7 @@
'type': 'groupchat'
}).c('body').t(message).tree();
view.model.onMessage(msg);
await view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
view.el.querySelector('.chat-msg__text a').click();
await test_utils.waitUntil(() => _converse.chatboxes.length === 3)
......@@ -933,7 +933,7 @@
'type': 'groupchat'
}).c('body').t('Some message').tree();
view.model.onMessage(msg);
await view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
let stanza = Strophe.xmlHtmlNode(
......@@ -1151,7 +1151,7 @@
'to': 'dummy@localhost',
'type': 'groupchat'
}).c('body').t(message).tree();
view.model.onMessage(msg);
await view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
expect(_.includes(view.el.querySelector('.chat-msg__author').textContent, '**Dyon van de Wege')).toBeTruthy();
expect(view.el.querySelector('.chat-msg__text').textContent).toBe('is tired');
......@@ -1163,7 +1163,7 @@
to: 'dummy@localhost',
type: 'groupchat'
}).c('body').t(message).tree();
view.model.onMessage(msg);
await view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
expect(_.includes(sizzle('.chat-msg__author:last', view.el).pop().textContent, '**Max Mustermann')).toBeTruthy();
expect(sizzle('.chat-msg__text:last', view.el).pop().textContent).toBe('is as well');
......@@ -1836,7 +1836,7 @@
to: 'dummy@localhost',
type: 'groupchat'
}).c('body').t(text);
view.model.onMessage(message.nodeTree);
await view.model.onMessage(message.nodeTree);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
const chat_content = view.el.querySelector('.chat-content');
expect(chat_content.querySelectorAll('.chat-msg').length).toBe(1);
......@@ -1878,7 +1878,7 @@
type: 'groupchat',
id: view.model.messages.at(0).get('msgid')
}).c('body').t(text);
view.model.onMessage(message.nodeTree);
await view.model.onMessage(message.nodeTree);
expect(chat_content.querySelectorAll('.chat-msg').length).toBe(1);
expect(sizzle('.chat-msg__text:last').pop().textContent).toBe(text);
expect(view.model.messages.length).toBe(1);
......@@ -1912,7 +1912,7 @@
// Give enough time for `markScrolled` to have been called
setTimeout(async () => {
view.content.scrollTop = 0;
view.model.onMessage(
await view.model.onMessage(
$msg({
from: 'lounge@localhost/someone',
to: 'dummy@localhost.com',
......@@ -1945,6 +1945,7 @@
'</message>').firstChild;
_converse.connection._dataRecv(test_utils.createRequest(stanza));
const view = _converse.chatboxviews.get('jdev@conference.jabber.org');
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
const chat_content = view.el.querySelector('.chat-content');
expect(sizzle('.chat-event:last').pop().textContent).toBe('Topic set by ralphm');
expect(sizzle('.chat-topic:last').pop().textContent).toBe(text);
......@@ -3999,7 +4000,7 @@
var contact_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
const nick = mock.chatroom_names[0];
view.model.onMessage($msg({
await view.model.onMessage($msg({
from: room_jid+'/'+nick,
id: (new Date()).getTime(),
to: 'dummy@localhost',
......@@ -4010,7 +4011,7 @@
expect(roomspanel.el.querySelectorAll('.msgs-indicator').length).toBe(1);
expect(roomspanel.el.querySelector('.msgs-indicator').textContent).toBe('1');
view.model.onMessage($msg({
await view.model.onMessage($msg({
'from': room_jid+'/'+nick,
'id': (new Date()).getTime(),
'to': 'dummy@localhost',
......@@ -4076,14 +4077,14 @@
// See XEP-0085 http://xmpp.org/extensions/xep-0085.html#definitions
// <composing> state
var msg = $msg({
let msg = $msg({
from: room_jid+'/newguy',
id: (new Date()).getTime(),
to: 'dummy@localhost',
type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg);
await view.model.onMessage(msg);
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-state-notification').length);
// Check that the notification appears inside the chatbox in the DOM
......@@ -4109,8 +4110,7 @@
to: 'dummy@localhost',
type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
await view.model.onMessage(msg);
events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3);
......@@ -4131,8 +4131,7 @@
to: 'dummy@localhost',
type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
await view.model.onMessage(msg);
events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat');
......@@ -4153,7 +4152,7 @@
to: 'dummy@localhost',
type: 'groupchat'
}).c('body').t('hello world').tree();
view.model.onMessage(msg);
await view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
const messages = view.el.querySelectorAll('.message');
......@@ -4259,8 +4258,7 @@
to: 'dummy@localhost',
type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
await view.model.onMessage(msg);
// Check that the notification appears inside the chatbox in the DOM
var events = view.el.querySelectorAll('.chat-event');
......@@ -4280,8 +4278,7 @@
to: 'dummy@localhost',
type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
await view.model.onMessage(msg);
events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3);
......@@ -4300,8 +4297,7 @@
to: 'dummy@localhost',
type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
await view.model.onMessage(msg);
events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat');
......@@ -4320,8 +4316,7 @@
to: 'dummy@localhost',
type: 'groupchat'
}).c('body').c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
await view.model.onMessage(msg);
events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat');
......
......@@ -16,16 +16,17 @@
it("is shown when a new private message is received",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
async function (done, _converse) {
// TODO: not yet testing show_desktop_notifications setting
test_utils.createContacts(_converse, 'current');
await test_utils.createContacts(_converse, 'current');
spyOn(_converse, 'showMessageNotification').and.callThrough();
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
spyOn(_converse, 'isMessageToHiddenChat').and.returnValue(true);
var message = 'This message will show a desktop notification';
var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
const message = 'This message will show a desktop notification';
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
msg = $msg({
from: sender_jid,
to: _converse.connection.jid,
......@@ -33,60 +34,63 @@
id: (new Date()).getTime()
}).c('body').t(message).up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.chatboxes.onMessage(msg); // This will emit 'message'
await _converse.chatboxes.onMessage(msg); // This will emit 'message'
await test_utils.waitUntil(() => _converse.api.chatviews.get(sender_jid));
expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
expect(_converse.showMessageNotification).toHaveBeenCalled();
done();
}));
it("is shown when you are mentioned in a chat room",
it("is shown when you are mentioned in a groupchat",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
test_utils.createContacts(_converse, 'current');
test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
var view = _converse.chatboxviews.get('lounge@localhost');
if (!$(view.el).find('.chat-area').length) { view.renderChatArea(); }
var no_notification = false;
if (typeof window.Notification === 'undefined') {
no_notification = true;
window.Notification = function () {
return {
'close': function () {}
};
async function (done, _converse) {
await test_utils.createContacts(_converse, 'current');
await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
const view = _converse.api.chatviews.get('lounge@localhost');
if (!view.el.querySelectorAll('.chat-area').length) {
view.renderChatArea();
}
let no_notification = false;
if (typeof window.Notification === 'undefined') {
no_notification = true;
window.Notification = function () {
return {
'close': function () {}
};
}
spyOn(_converse, 'showMessageNotification').and.callThrough();
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
var message = 'dummy: This message will show a desktop notification';
var nick = mock.chatroom_names[0],
msg = $msg({
from: 'lounge@localhost/'+nick,
id: (new Date()).getTime(),
to: 'dummy@localhost',
type: 'groupchat'
}).c('body').t(message).tree();
_converse.chatboxes.onMessage(msg); // This will emit 'message'
expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
expect(_converse.showMessageNotification).toHaveBeenCalled();
if (no_notification) {
delete window.Notification;
}
done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
};
}
spyOn(_converse, 'showMessageNotification').and.callThrough();
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
const message = 'dummy: This message will show a desktop notification';
const nick = mock.chatroom_names[0],
msg = $msg({
from: 'lounge@localhost/'+nick,
id: (new Date()).getTime(),
to: 'dummy@localhost',
type: 'groupchat'
}).c('body').t(message).tree();
await _converse.chatboxes.onMessage(msg); // This will emit 'message'
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
expect(_converse.showMessageNotification).toHaveBeenCalled();
if (no_notification) {
delete window.Notification;
}
done();
}));
it("is shown for headline messages",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
async function (done, _converse) {
spyOn(_converse, 'showMessageNotification').and.callThrough();
spyOn(_converse, 'isMessageToHiddenChat').and.returnValue(true);
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
var stanza = $msg({
const stanza = $msg({
'type': 'headline',
'from': 'notify.example.com',
'to': 'dummy@localhost',
......@@ -97,6 +101,9 @@
.c('x', {'xmlns': 'jabber:x:oob'})
.c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
await test_utils.waitUntil(() => _converse.chatboxviews.keys().length);
const view = _converse.chatboxviews.get('notify.example.com');
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
expect(
_.includes(_converse.chatboxviews.keys(),
'notify.example.com')
......@@ -156,7 +163,7 @@
describe("When play_sounds is set to true", function () {
describe("A notification sound", function () {
it("is played when the current user is mentioned in a chat room",
it("is played when the current user is mentioned in a groupchat",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
async function (done, _converse) {
......@@ -176,8 +183,7 @@
to: 'dummy@localhost',
type: 'groupchat'
}).c('body').t(text);
view.model.onMessage(message.nodeTree);
await view.model.onMessage(message.nodeTree);
await test_utils.waitUntil(() => _converse.playSoundNotification.calls.count());
expect(_converse.playSoundNotification).toHaveBeenCalled();
......@@ -188,7 +194,7 @@
to: 'dummy@localhost',
type: 'groupchat'
}).c('body').t(text);
view.model.onMessage(message.nodeTree);
await view.model.onMessage(message.nodeTree);
expect(_converse.playSoundNotification, 1);
_converse.play_sounds = false;
......@@ -199,7 +205,7 @@
to: 'dummy@localhost',
type: 'groupchat'
}).c('body').t(text);
view.model.onMessage(message.nodeTree);
await view.model.onMessage(message.nodeTree);
expect(_converse.playSoundNotification, 1);
_converse.play_sounds = false;
done();
......
......@@ -285,7 +285,7 @@
view.model.set({'minimized': true});
const contact_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
const nick = mock.chatroom_names[0];
view.model.onMessage(
await view.model.onMessage(
$msg({
from: room_jid+'/'+nick,
id: (new Date()).getTime(),
......@@ -293,13 +293,12 @@
type: 'groupchat'
}).c('body').t('foo').tree());
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
// If the user isn't mentioned, the counter doesn't get incremented, but the text of the groupchat is bold
let room_el = _converse.rooms_list_view.el.querySelector(".available-chatroom");
expect(_.includes(room_el.classList, 'unread-msgs')).toBeTruthy();
// If the user is mentioned, the counter also gets updated
view.model.onMessage(
await view.model.onMessage(
$msg({
from: room_jid+'/'+nick,
id: (new Date()).getTime(),
......@@ -311,7 +310,7 @@
spyOn(view.model, 'incrementUnreadMsgCounter').and.callThrough();
let indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
expect(indicator_el.textContent).toBe('1');
view.model.onMessage(
await view.model.onMessage(
$msg({
from: room_jid+'/'+nick,
id: (new Date()).getTime(),
......
......@@ -105,10 +105,10 @@ converse.plugins.add('converse-headline', {
'afterShown': _.noop
});
function onHeadlineMessage (message) {
async function onHeadlineMessage (message) {
/* Handler method for all incoming messages of type "headline". */
const from_jid = message.getAttribute('from');
if (utils.isHeadlineMessage(_converse, message)) {
const from_jid = message.getAttribute('from');
if (_.includes(from_jid, '@') &&
!_converse.api.contacts.get(from_jid) &&
!_converse.allow_non_roster_messaging) {
......@@ -125,14 +125,17 @@ converse.plugins.add('converse-headline', {
'type': _converse.HEADLINES_TYPE,
'from': from_jid
});
chatbox.createMessage(message, message);
const attrs = await chatbox.getMessageAttributesFromStanza(message, message);
await chatbox.messages.create(attrs);
_converse.emit('message', {'chatbox': chatbox, 'stanza': message});
}
return true;
}
function registerHeadlineHandler () {
_converse.connection.addHandler(onHeadlineMessage, null, 'message');
_converse.connection.addHandler(message => {
onHeadlineMessage(message);
return true
}, null, 'message');
}
_converse.on('connected', registerHeadlineHandler);
_converse.on('reconnected', registerHeadlineHandler);
......
......@@ -544,8 +544,10 @@ converse.plugins.add('converse-chatboxes', {
'is_delayed': !_.isNil(delay),
'is_spoiler': !_.isNil(spoiler),
'message': _converse.chatboxes.getMessageBody(stanza) || undefined,
'references': this.getReferencesFromStanza(stanza),
'msgid': stanza.getAttribute('id'),
'references': this.getReferencesFromStanza(stanza),
'subject': _.propertyOf(stanza.querySelector('subject'))('textContent'),
'thread': _.propertyOf(stanza.querySelector('thread'))('textContent'),
'time': delay ? delay.getAttribute('stamp') : moment().format(),
'type': stanza.getAttribute('type')
};
......@@ -573,25 +575,6 @@ converse.plugins.add('converse-chatboxes', {
return attrs;
},
async createMessage (message, original_stanza) {
/* Create a Backbone.Message object inside this chat box
* based on the identified message stanza.
*/
const attrs = await this.getMessageAttributesFromStanza(message, original_stanza),
is_csn = u.isOnlyChatStateNotification(attrs);
if (is_csn && (attrs.is_delayed || (attrs.type === 'groupchat' && Strophe.getResourceFromJid(attrs.from) == this.get('nick')))) {
// XXX: MUC leakage
// No need showing delayed or our own CSN messages
return;
} else if (!is_csn && !attrs.file && !attrs.plaintext && !attrs.message && !attrs.oob_url && attrs.type !== 'error') {
// TODO: handle <subject> messages (currently being done by ChatRoom)
return;
} else {
return this.messages.create(attrs);
}
},
isHidden () {
/* Returns a boolean to indicate whether a newly received
* message will be visible to the user or not.
......@@ -680,7 +663,7 @@ converse.plugins.add('converse-chatboxes', {
});
},
onErrorMessage (message) {
async onErrorMessage (message) {
/* Handler method for all incoming error message stanzas
*/
const from_jid = Strophe.getBareJidFromJid(message.getAttribute('from'));
......@@ -708,8 +691,8 @@ converse.plugins.add('converse-chatboxes', {
_converse.log('Received an error message without id attribute!', Strophe.LogLevel.ERROR);
_converse.log(message, Strophe.LogLevel.ERROR);
}
chatbox.createMessage(message, message);
return true;
const attrs = await chatbox.getMessageAttributesFromStanza(message, message);
chatbox.messages.create(attrs);
},
getMessageBody (stanza) {
......@@ -736,7 +719,7 @@ converse.plugins.add('converse-chatboxes', {
_converse.api.send(receipt_stanza);
},
onMessage (stanza) {
async onMessage (stanza) {
/* Handler method for all incoming single-user chat "message"
* stanzas.
*
......@@ -816,13 +799,12 @@ converse.plugins.add('converse-chatboxes', {
message = msgid && chatbox.messages.findWhere({msgid});
if (!message) {
// Only create the message when we're sure it's not a duplicate
chatbox.createMessage(stanza, original_stanza)
.then(msg => chatbox.incrementUnreadMsgCounter(msg))
.catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
const attrs = await chatbox.getMessageAttributesFromStanza(stanza, original_stanza);
const msg = chatbox.messages.create(attrs);
chatbox.incrementUnreadMsgCounter(msg);
}
}
_converse.emit('message', {'stanza': original_stanza, 'chatbox': chatbox});
return true;
},
getChatBox (jid, attrs={}, create) {
......
......@@ -218,6 +218,8 @@ converse.plugins.add('converse-mam', {
if (!results.length) { return; }
this.addSpinner();
_converse.api.archive.query(
// TODO: only query from the last message we have
// in our history
_.extend({
'groupchat': is_groupchat,
'before': '', // Page backwards from the most recent message
......
......@@ -987,33 +987,40 @@ converse.plugins.add('converse-muc', {
const original_stanza = stanza,
forwarded = sizzle(`forwarded[xmlns="${Strophe.NS.FORWARD}"]`, stanza).pop();
if (forwarded) {
stanza = forwarded.querySelector('message');
}
if (this.isDuplicate(stanza, original_stanza)) {
return;
}
const jid = stanza.getAttribute('from'),
resource = Strophe.getResourceFromJid(jid),
sender = resource && Strophe.unescapeNode(resource) || '';
const attrs = await this.getMessageAttributesFromStanza(stanza, original_stanza);
if (!attrs.nick) {
return;
}
if (!this.handleMessageCorrection(stanza)) {
if (sender === '') {
if (attrs.subject && !attrs.thread && !attrs.message) {
// https://xmpp.org/extensions/xep-0045.html#subject-mod
// -----------------------------------------------------
// The subject is changed by sending a message of type "groupchat" to the <room@service>,
// where the <message/> MUST contain a <subject/> element that specifies the new subject but
// MUST NOT contain a <body/> element (or a <thread/> element).
u.safeSave(this, {'subject': {'author': attrs.nick, 'text': attrs.subject || ''}});
return;
}
const subject_el = stanza.querySelector('subject');
if (subject_el) {
const subject = _.propertyOf(subject_el)('textContent') || '';
u.safeSave(this, {'subject': {'author': sender, 'text': subject}});
const is_csn = u.isOnlyChatStateNotification(attrs),
own_message = Strophe.getResourceFromJid(attrs.from) == this.get('nick');
if (is_csn && (attrs.is_delayed || own_message)) {
// No need showing delayed or our own CSN messages
return;
}
const msg = await this.createMessage(stanza, original_stanza);
const msg = await this.messages.create(attrs);
if (forwarded && msg && msg.get('sender') === 'me') {
msg.save({'received': moment().format()});
}
this.incrementUnreadMsgCounter(msg);
}
if (sender !== this.get('nick')) {
if (attrs.nick !== this.get('nick')) {
// We only emit an event if it's not our own message
_converse.emit('message', {'stanza': original_stanza, 'chatbox': this});
}
......
......@@ -112,7 +112,7 @@ var specs = [
"spec/chatbox",
"spec/user-details-modal",
"spec/messages",
"spec/chatroom",
"spec/muc",
"spec/room_registration",
"spec/autocomplete",
"spec/minchats",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment