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
This commit is contained in:
JC Brand 2019-01-25 11:53:07 +01:00
parent 3aaff4e973
commit 3c0e3d3fab
12 changed files with 336 additions and 353 deletions

96
dist/converse.js vendored
View File

@ -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) || '';
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).
_utils_form__WEBPACK_IMPORTED_MODULE_7__["default"].safeSave(this, {
'subject': {
'author': attrs.nick,
'text': attrs.subject || ''
}
});
return;
}
const subject_el = stanza.querySelector('subject');
const is_csn = _utils_form__WEBPACK_IMPORTED_MODULE_7__["default"].isOnlyChatStateNotification(attrs),
own_message = Strophe.getResourceFromJid(attrs.from) == this.get('nick');
if (subject_el) {
const subject = _.propertyOf(subject_el)('textContent') || '';
_utils_form__WEBPACK_IMPORTED_MODULE_7__["default"].safeSave(this, {
'subject': {
'author': sender,
'text': subject
}
});
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,

View File

@ -62,114 +62,111 @@
it("autocompletes when the user presses tab",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
async 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
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 ');
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(view.model.occupants.length).toBe(2);
// 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);
const textarea = view.el.querySelector('textarea.chat-textarea');
textarea.value = "hello som";
view.keyPressed(tab_event);
view.keyUp(tab_event);
expect(textarea.value).toBe('hello @z3r0 ');
// 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');
done();
}).catch(_.partial(console.error, _));
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();
}));
});
}));

View File

@ -44,7 +44,7 @@
type: 'chat',
id: (new Date()).getTime()
}).c('body').t('hello world').tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
expect(view.content.lastElementChild.textContent.trim().indexOf('hello world')).not.toBe(-1);
done();
@ -71,7 +71,7 @@
}).c('body').t(message).up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
const view = _converse.chatboxviews.get(sender_jid);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(1);
@ -322,7 +322,7 @@
done();
}));
it("can be closed by clicking a DOM element with class 'close-chatbox-button'",
it("can be closed by clicking a DOM element with class 'close-chatbox-button'",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
@ -533,7 +533,7 @@
it("does not open a new chatbox",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
async function (done, _converse) {
test_utils.createContacts(_converse, 'current');
test_utils.openControlBox();
@ -547,7 +547,7 @@
'type': 'chat',
'id': (new Date()).getTime()
}).c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
done();
}));
@ -660,21 +660,20 @@
// See XEP-0085 http://xmpp.org/extensions/xep-0085.html#definitions
spyOn(_converse, 'emit');
var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, sender_jid);
// <composing> state
var msg = $msg({
let msg = $msg({
from: sender_jid,
to: _converse.connection.jid,
type: 'chat',
id: (new Date()).getTime()
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
var view = _converse.chatboxviews.get(sender_jid);
expect(view).toBeDefined();
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
await test_utils.waitUntil(() => view.model.vcard.get('fullname') === mock.cur_names[1])
// Check that the notification appears inside the chatbox in the DOM
@ -689,8 +688,7 @@
type: 'chat',
id: (new Date()).getTime()
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
_converse.chatboxes.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
await _converse.chatboxes.onMessage(msg);
events = view.el.querySelectorAll('.chat-state-notification');
expect(events.length).toBe(1);
expect(events[0].textContent).toEqual(mock.cur_names[1] + ' is typing');
@ -725,7 +723,7 @@
'to': recipient_jid,
'type': 'chat'
}).c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => view.model.messages.length);
// Check that the chatbox and its view now exist
var chatbox = _converse.chatboxes.get(recipient_jid);
@ -818,7 +816,7 @@
type: 'chat',
id: (new Date()).getTime()
}).c('body').c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
const view = _converse.chatboxviews.get(sender_jid);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
@ -856,7 +854,7 @@
'to': recipient_jid,
'type': 'chat'
}).c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => view.model.messages.length);
// Check that the chatbox and its view now exist
var chatbox = _converse.chatboxes.get(recipient_jid);
@ -998,7 +996,7 @@
'type': 'chat'})
.c('composing', {'xmlns': Strophe.NS.CHATSTATES}).up()
.tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => view.model.messages.length);
expect(view.el.querySelectorAll('.chat-state-notification').length).toBe(1);
msg = $msg({
@ -1007,7 +1005,7 @@
type: 'chat',
id: (new Date()).getTime()
}).c('body').c('inactive', {'xmlns': Strophe.NS.CHATSTATES}).tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => (view.model.messages.length > 1));
expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
expect(view.el.querySelectorAll('.chat-state-notification').length).toBe(0);
@ -1034,7 +1032,7 @@
type: 'chat',
id: (new Date()).getTime()
}).c('body').c('gone', {'xmlns': Strophe.NS.CHATSTATES}).tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
const view = _converse.chatboxviews.get(sender_jid);
await test_utils.waitUntil(() => view.model.vcard.get('fullname') === mock.cur_names[1]);
@ -1121,7 +1119,7 @@
spyOn(_converse, 'incrementMsgCounter').and.callThrough();
spyOn(_converse, 'clearMsgCounter').and.callThrough();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
expect(_converse.incrementMsgCounter).toHaveBeenCalled();
expect(_converse.clearMsgCounter).not.toHaveBeenCalled();
@ -1149,7 +1147,7 @@
it("is not incremented when the message is received and the window is focused",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
async function (done, _converse) {
test_utils.createContacts(_converse, 'current');
test_utils.openControlBox();
@ -1157,8 +1155,8 @@
expect(_converse.msg_counter).toBe(0);
spyOn(_converse, 'incrementMsgCounter').and.callThrough();
_converse.saveWindowState(null, 'focus');
var message = 'This message will not increment the message counter';
var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
const message = 'This message will not increment the message counter';
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
msg = $msg({
from: sender_jid,
to: _converse.connection.jid,
@ -1166,7 +1164,7 @@
id: (new Date()).getTime()
}).c('body').t(message).up()
.c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
expect(_converse.incrementMsgCounter).not.toHaveBeenCalled();
expect(_converse.msg_counter).toBe(0);
done();
@ -1237,7 +1235,7 @@
const view = await test_utils.openChatBoxFor(_converse, sender_jid)
view.model.save('scrolled', true);
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => view.model.messages.length);
expect(view.model.get('num_unread')).toBe(1);
done();
@ -1256,7 +1254,7 @@
await test_utils.openChatBoxFor(_converse, sender_jid);
const chatbox = _converse.chatboxes.get(sender_jid);
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
expect(chatbox.get('num_unread')).toBe(0);
done();
}));
@ -1359,14 +1357,14 @@
const chatbox = _converse.chatboxes.get(sender_jid);
chatbox.save('scrolled', true);
msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => chatbox.messages.length);
const selector = 'a.open-chat:contains("' + chatbox.get('fullname') + '") .msgs-indicator';
indicator_el = sizzle(selector, _converse.rosterview.el).pop();
expect(indicator_el.textContent).toBe('1');
msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread too');
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => chatbox.messages.length > 1);
indicator_el = sizzle(selector, _converse.rosterview.el).pop();
expect(indicator_el.textContent).toBe('2');
@ -1390,14 +1388,14 @@
chatboxview.minimize();
msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => chatbox.messages.length);
const selector = 'a.open-chat:contains("' + chatbox.get('fullname') + '") .msgs-indicator';
indicator_el = sizzle(selector, _converse.rosterview.el).pop();
expect(indicator_el.textContent).toBe('1');
msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread too');
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => chatbox.messages.length > 1);
indicator_el = sizzle(selector, _converse.rosterview.el).pop();
expect(indicator_el.textContent).toBe('2');

View File

@ -310,7 +310,7 @@
'type': 'chat'})
.c('body').t("message")
.tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => _converse.api.chats.get().length);
const view = _converse.chatboxviews.get(sender_jid);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
@ -325,7 +325,7 @@
'type': 'chat'})
.c('body').t("Older message")
.tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
msg = $msg({'id': 'aeb215', 'to': _converse.bare_jid})
@ -338,7 +338,7 @@
'type': 'chat'})
.c('body').t("Inbetween message").up()
.tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
msg = $msg({'id': 'aeb216', 'to': _converse.bare_jid})
@ -351,7 +351,7 @@
'type': 'chat'})
.c('body').t("another inbetween message")
.tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
msg = $msg({'id': 'aeb217', 'to': _converse.bare_jid})
@ -364,7 +364,7 @@
'type': 'chat'})
.c('body').t("An earlier message on the next day")
.tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
msg = $msg({'id': 'aeb218', 'to': _converse.bare_jid})
@ -377,7 +377,7 @@
'type': 'chat'})
.c('body').t("newer message from the next day")
.tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
// Insert <composing> message, to also check that
@ -391,7 +391,7 @@
'type': 'chat'})
.c('composing', {'xmlns': Strophe.NS.CHATSTATES}).up()
.tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
msg = $msg({
@ -403,7 +403,7 @@
.c('composing', {'xmlns': Strophe.NS.CHATSTATES}).up()
.c('body').t("latest message")
.tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
const chat_content = view.el.querySelector('.chat-content');
@ -464,7 +464,7 @@
it("is ignored if it's a malformed headline message",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
async function (done, _converse) {
test_utils.createContacts(_converse, 'current');
test_utils.openControlBox();
@ -481,7 +481,7 @@
type: 'chat',
id: (new Date()).getTime()
}).c('body').t("This headline message will not be shown").tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
expect(_converse.log.calledWith(
"onMessage: Ignoring incoming headline message from JID: localhost",
Strophe.LogLevel.INFO
@ -523,7 +523,7 @@
'to': _converse.bare_jid+'/another-resource',
'type': 'chat'
}).c('body').t(msgtext).tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => _converse.api.chats.get().length)
const chatbox = _converse.chatboxes.get(sender_jid);
const view = _converse.chatboxviews.get(sender_jid);
@ -574,7 +574,7 @@
'to': recipient_jid,
'type': 'chat'
}).c('body').t(msgtext).tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => _converse.api.chats.get().length);
// Check that the chatbox and its view now exist
const chatbox = _converse.chatboxes.get(recipient_jid);
@ -598,25 +598,25 @@
it("will be discarded if it's a malicious message meant to look like a carbon copy",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
async function (done, _converse) {
test_utils.createContacts(_converse, 'current');
test_utils.openControlBox();
/* <message from="mallory@evil.example" to="b@xmpp.example">
* <received xmlns='urn:xmpp:carbons:2'>
* <forwarded xmlns='urn:xmpp:forward:0'>
* <message from="alice@xmpp.example" to="bob@xmpp.example/client1">
* <body>Please come to Creepy Valley tonight, alone!</body>
* </message>
* </forwarded>
* </received>
* </message>
*/
* <received xmlns='urn:xmpp:carbons:2'>
* <forwarded xmlns='urn:xmpp:forward:0'>
* <message from="alice@xmpp.example" to="bob@xmpp.example/client1">
* <body>Please come to Creepy Valley tonight, alone!</body>
* </message>
* </forwarded>
* </received>
* </message>
*/
spyOn(_converse, 'log');
var msgtext = 'Please come to Creepy Valley tonight, alone!';
var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
var impersonated_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
var msg = $msg({
const msgtext = 'Please come to Creepy Valley tonight, alone!';
const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
const impersonated_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
const msg = $msg({
'from': sender_jid,
'id': (new Date()).getTime(),
'to': _converse.connection.jid,
@ -630,10 +630,10 @@
'to': _converse.connection.jid,
'type': 'chat'
}).c('body').t(msgtext).tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
// Check that chatbox for impersonated user is not created.
var chatbox = _converse.chatboxes.get(impersonated_jid);
let chatbox = _converse.chatboxes.get(impersonated_jid);
expect(chatbox).not.toBeDefined();
// Check that the chatbox for the malicous user is not created
@ -673,7 +673,7 @@
id: (new Date()).getTime()
}).c('body').t(message).up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => chatview.model.messages.length);
expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
@ -734,7 +734,7 @@
}).c('body').t(message).up()
.c('delay', { xmlns:'urn:xmpp:delay', from: 'localhost', stamp: one_day_ago.format() })
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
@ -766,7 +766,7 @@
id: new Date().getTime()
}).c('body').t(message).up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
@ -1196,7 +1196,7 @@
it("received may emit a message delivery receipt",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) {
async function (done, _converse) {
test_utils.createContacts(_converse, 'current', 1);
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
const msg_id = u.getUniqueId();
@ -1211,7 +1211,7 @@
'id': msg_id,
}).c('body').t('Message!').up()
.c('request', {'xmlns': Strophe.NS.RECEIPTS}).tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
const receipt = sizzle(`received[xmlns="${Strophe.NS.RECEIPTS}"]`, sent_stanzas[0].tree()).pop();
expect(Strophe.serialize(receipt)).toBe(`<received id="${msg_id}" xmlns="${Strophe.NS.RECEIPTS}"/>`);
done();
@ -1241,7 +1241,7 @@
'id': msg_id
}).c('body').t('Message!').up()
.c('request', {'xmlns': Strophe.NS.RECEIPTS}).tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => _converse.api.chats.get().length);
expect(_converse.chatboxes.sendReceiptStanza).not.toHaveBeenCalled();
done();
@ -1270,7 +1270,7 @@
'id': msg_id
}).c('body').t('Message!').up()
.c('request', {'xmlns': Strophe.NS.RECEIPTS}).tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => _converse.api.chats.get().length);
expect(_converse.chatboxes.sendReceiptStanza).not.toHaveBeenCalled();
done();
@ -1475,7 +1475,7 @@
// We don't already have an open chatbox for this user
expect(_converse.chatboxes.get(sender_jid)).not.toBeDefined();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => _converse.api.chats.get().length);
expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
@ -1527,7 +1527,7 @@
let chatbox = _converse.chatboxes.get(sender_jid);
expect(chatbox).not.toBeDefined();
// onMessage is a handler for received XMPP messages
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => _converse.api.chats.get().length)
const view = _converse.chatboxviews.get(sender_jid);
@ -1536,7 +1536,7 @@
expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
// onMessage is a handler for received XMPP messages
_converse.allow_non_roster_messaging =true;
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => view.model.messages.length);
expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
// Check that the chatbox and its view now exist
@ -1824,7 +1824,7 @@
id: (new Date()).getTime()
}).c('body').t("This message will not be shown").up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => _converse.api.chats.get().length);
expect(_converse.log).toHaveBeenCalledWith(
"onMessage: Ignoring incoming message intended for a different resource: dummy@localhost/some-other-resource",
@ -1840,7 +1840,7 @@
id: '134234623462346'
}).c('body').t(message).up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.chatboxes.onMessage(msg);
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => _converse.chatboxviews.keys().length > 1, 1000);
const view = _converse.chatboxviews.get(sender_jid);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
@ -2043,9 +2043,9 @@
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($(view.el).find('.chat-msg').hasClass('mentioned')).toBeTruthy();
expect(u.hasClass('mentioned', view.el.querySelector('.chat-msg'))).toBeTruthy();
done();
}));
@ -2064,8 +2064,7 @@
to: 'dummy@localhost',
type: 'groupchat'
}).c('body').t('I wrote this message!').tree();
view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
await view.model.onMessage(msg);
expect(view.model.messages.last().get('sender')).toBe('me');
done();
}));
@ -2091,7 +2090,7 @@
}).tree();
_converse.connection._dataRecv(test_utils.createRequest(stanza));
const msg_id = u.getUniqueId();
view.model.onMessage($msg({
await view.model.onMessage($msg({
'from': 'lounge@localhost/newguy',
'to': _converse.connection.jid,
'type': 'groupchat',
@ -2102,10 +2101,10 @@
expect(view.el.querySelector('.chat-msg__text').textContent)
.toBe('But soft, what light through yonder airlock breaks?');
view.model.onMessage($msg({
await view.model.onMessage($msg({
'from': 'lounge@localhost/newguy',
'to': _converse.connection.jid,
'type': 'chat',
'type': 'groupchat',
'id': u.getUniqueId(),
}).c('body').t('But soft, what light through yonder chimney breaks?').up()
.c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
@ -2114,10 +2113,10 @@
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
view.model.onMessage($msg({
await view.model.onMessage($msg({
'from': 'lounge@localhost/newguy',
'to': _converse.connection.jid,
'type': 'chat',
'type': 'groupchat',
'id': u.getUniqueId(),
}).c('body').t('But soft, what light through yonder window breaks?').up()
.c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
@ -2207,7 +2206,7 @@
expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(false);
// Check that messages from other users are skipped
view.model.onMessage($msg({
await view.model.onMessage($msg({
'from': room_jid+'/someone-else',
'id': (new Date()).getTime(),
'to': 'dummy@localhost',
@ -2264,8 +2263,7 @@
'to': 'dummy@localhost',
'type': 'groupchat',
}).c('body').t(body).up().tree();
view.model.onMessage(msg);
await new Promise((resolve, reject) => view.model.messages.once('rendered', resolve));
await view.model.onMessage(msg);
expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(1);
done();
}));
@ -2302,7 +2300,7 @@
.c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'6', 'end':'10', 'type':'mention', 'uri':'xmpp:z3r0@localhost'}).up()
.c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'11', 'end':'14', 'type':'mention', 'uri':'xmpp:dummy@localhost'}).up()
.c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'15', 'end':'23', 'type':'mention', 'uri':'xmpp:mr.robot@localhost'}).nodeTree;
view.model.onMessage(msg);
await view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
expect(view.el.querySelectorAll('.chat-msg__text').length).toBe(1);
expect(view.el.querySelector('.chat-msg__text').outerHTML).toBe(

View File

@ -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');

View File

@ -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) {
async 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 () {}
};
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();

View File

@ -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(),

View File

@ -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);

View File

@ -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) {

View File

@ -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

View File

@ -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});
}

View File

@ -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",