Commit e181aaf9 authored by JC Brand's avatar JC Brand

Make the message view's `render` method async

So that we first render dynamic content (e.g. images) before inserting
it into the chat.

Also, add the `show_images_inline` setting (which is the cause of this
whole change).

Updated tests to handle this new change and start using async/await
instead of promise callbacks.
parent 2426f9b7
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -2252,9 +2252,7 @@ ...@@ -2252,9 +2252,7 @@
} }
}, },
"backbone.browserStorage": { "backbone.browserStorage": {
"version": "0.0.3", "version": "github:jcbrand/Backbone.browserStorage#7079bf7bf9a43474da1d48e31e3cda6c4a716382",
"resolved": "https://registry.npmjs.org/backbone.browserStorage/-/backbone.browserStorage-0.0.3.tgz",
"integrity": "sha1-ikIi8I2bHQslLR14/1CUuNCKc2s=",
"dev": true, "dev": true,
"requires": { "requires": {
"backbone": "1.3.3", "backbone": "1.3.3",
......
...@@ -58,18 +58,16 @@ ...@@ -58,18 +58,16 @@
it("supports the /me command", it("supports the /me command",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { async function (done, _converse) {
var view;
test_utils.createContacts(_converse, 'current'); test_utils.createContacts(_converse, 'current');
test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp']) await test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp']);
.then(() => test_utils.waitUntil(() => _converse.xmppstatus.vcard.get('fullname')), 300) await test_utils.waitUntil(() => _converse.xmppstatus.vcard.get('fullname'));
.then(function () { await test_utils.openControlBox();
test_utils.openControlBox();
expect(_converse.chatboxes.length).toEqual(1); expect(_converse.chatboxes.length).toEqual(1);
var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
var message = '/me is tired'; let message = '/me is tired';
var msg = $msg({ const msg = $msg({
from: sender_jid, from: sender_jid,
to: _converse.connection.jid, to: _converse.connection.jid,
type: 'chat', type: 'chat',
...@@ -78,41 +76,31 @@ ...@@ -78,41 +76,31 @@
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree(); .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.chatboxes.onMessage(msg); _converse.chatboxes.onMessage(msg);
view = _converse.chatboxviews.get(sender_jid); const view = _converse.chatboxviews.get(sender_jid);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
test_utils.waitUntil(function () {
return u.isVisible(view.el);
}).then(function () {
expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(1); expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(1);
expect(_.includes(view.el.querySelector('.chat-msg__author').textContent, '**Max Frankfurter')).toBeTruthy(); expect(_.includes(view.el.querySelector('.chat-msg__author').textContent, '**Max Frankfurter')).toBeTruthy();
expect($(view.el).find('.chat-msg__text').text()).toBe(' is tired'); expect(view.el.querySelector('.chat-msg__text').textContent).toBe(' is tired');
message = '/me is as well'; message = '/me is as well';
test_utils.sendMessage(view, message); await test_utils.sendMessage(view, message);
expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(2); expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(2);
await test_utils.waitUntil(() => $(view.el).find('.chat-msg__author:last').text().trim() === '**Max Mustermann');
return test_utils.waitUntil(() => $(view.el).find('.chat-msg__author:last').text().trim() === '**Max Mustermann'); expect(sizzle('.chat-msg__text:last', view.el).pop().textContent).toBe(' is as well');
}).then(function () {
expect($(view.el).find('.chat-msg__text:last').text()).toBe(' is as well');
expect($(view.el).find('.chat-msg:last').hasClass('chat-msg--followup')).toBe(false); expect($(view.el).find('.chat-msg:last').hasClass('chat-msg--followup')).toBe(false);
// Check that /me messages after a normal message don't // Check that /me messages after a normal message don't
// get the 'chat-msg--followup' class. // get the 'chat-msg--followup' class.
message = 'This a normal message'; message = 'This a normal message';
test_utils.sendMessage(view, message); await test_utils.sendMessage(view, message);
let message_el = view.el.querySelector('.message:last-child'); let message_el = view.el.querySelector('.message:last-child');
expect(u.hasClass('chat-msg--followup', message_el)).toBeFalsy(); expect(u.hasClass('chat-msg--followup', message_el)).toBeFalsy();
message = '/me wrote a 3rd person message'; message = '/me wrote a 3rd person message';
test_utils.sendMessage(view, message); await test_utils.sendMessage(view, message);
message_el = view.el.querySelector('.message:last-child'); message_el = view.el.querySelector('.message:last-child');
expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(3); expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(3);
expect($(view.el).find('.chat-msg__text:last').text()).toBe(' wrote a 3rd person message'); expect(sizzle('.chat-msg__text:last', view.el).pop().textContent).toBe(' wrote a 3rd person message');
expect($(view.el).find('.chat-msg__author:last').is(':visible')).toBeTruthy(); expect(u.isVisible(sizzle('.chat-msg__author:last', view.el).pop())).toBeTruthy();
expect(u.hasClass('chat-msg--followup', message_el)).toBeFalsy(); expect(u.hasClass('chat-msg--followup', message_el)).toBeFalsy();
done(); done();
});
});
})); }));
it("is created when you click on a roster item", mock.initConverseWithPromises( it("is created when you click on a roster item", mock.initConverseWithPromises(
...@@ -684,7 +672,7 @@ ...@@ -684,7 +672,7 @@
it("will be shown if received", it("will be shown if received",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'current'); test_utils.createContacts(_converse, 'current');
test_utils.openControlBox(); test_utils.openControlBox();
...@@ -705,10 +693,9 @@ ...@@ -705,10 +693,9 @@
expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object)); expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
var view = _converse.chatboxviews.get(sender_jid); var view = _converse.chatboxviews.get(sender_jid);
expect(view).toBeDefined(); expect(view).toBeDefined();
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
test_utils.waitUntil(() => view.model.vcard.get('fullname') === mock.cur_names[1]) await test_utils.waitUntil(() => view.model.vcard.get('fullname') === mock.cur_names[1])
.then(function () {
var view = _converse.chatboxviews.get(sender_jid);
// Check that the notification appears inside the chatbox in the DOM // Check that the notification appears inside the chatbox in the DOM
let events = view.el.querySelectorAll('.chat-state-notification'); let events = view.el.querySelectorAll('.chat-state-notification');
expect(events.length).toBe(1); expect(events.length).toBe(1);
...@@ -722,11 +709,11 @@ ...@@ -722,11 +709,11 @@
id: (new Date()).getTime() id: (new Date()).getTime()
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
_converse.chatboxes.onMessage(msg); _converse.chatboxes.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
events = view.el.querySelectorAll('.chat-state-notification'); events = view.el.querySelectorAll('.chat-state-notification');
expect(events.length).toBe(1); expect(events.length).toBe(1);
expect(events[0].textContent).toEqual(mock.cur_names[1] + ' is typing'); expect(events[0].textContent).toEqual(mock.cur_names[1] + ' is typing');
done(); done();
})
})); }));
it("can be a composing carbon message that this user sent from a different client", it("can be a composing carbon message that this user sent from a different client",
...@@ -843,18 +830,15 @@ ...@@ -843,18 +830,15 @@
it("will be shown if received", it("will be shown if received",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'current'); test_utils.createContacts(_converse, 'current');
test_utils.openControlBox(); test_utils.openControlBox();
test_utils.waitUntil(function () { await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
return $(_converse.rosterview.el).find('.roster-group').length;
}, 300)
.then(function () {
// TODO: only show paused state if the previous state was composing // TODO: only show paused state if the previous state was composing
// See XEP-0085 http://xmpp.org/extensions/xep-0085.html#definitions // See XEP-0085 http://xmpp.org/extensions/xep-0085.html#definitions
spyOn(_converse, 'emit'); 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';
// <paused> state // <paused> state
var msg = $msg({ var msg = $msg({
from: sender_jid, from: sender_jid,
...@@ -864,15 +848,12 @@ ...@@ -864,15 +848,12 @@
}).c('body').c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree(); }).c('body').c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree();
_converse.chatboxes.onMessage(msg); _converse.chatboxes.onMessage(msg);
expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object)); expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
var view = _converse.chatboxviews.get(sender_jid); const view = _converse.chatboxviews.get(sender_jid);
test_utils.waitUntil(() => view.model.vcard.get('fullname') === mock.cur_names[1]) await new Promise((resolve, reject) => view.once('messageInserted', resolve));
.then(function () { await test_utils.waitUntil(() => view.model.vcard.get('fullname') === mock.cur_names[1])
var view = _converse.chatboxviews.get(sender_jid);
var event = view.el.querySelector('.chat-info.chat-state-notification'); var event = view.el.querySelector('.chat-info.chat-state-notification');
expect(event.textContent).toEqual(mock.cur_names[1] + ' has stopped typing'); expect(event.textContent).toEqual(mock.cur_names[1] + ' has stopped typing');
done(); done();
});
});
})); }));
it("can be a paused carbon message that this user sent from a different client", it("can be a paused carbon message that this user sent from a different client",
...@@ -1242,14 +1223,11 @@ ...@@ -1242,14 +1223,11 @@
it("is incremented from zero when chatbox was closed after viewing previously received messages and the window is not focused now", it("is incremented from zero when chatbox was closed after viewing previously received messages and the window is not focused now",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'current'); test_utils.createContacts(_converse, 'current');
// initial state // initial state
expect(_converse.msg_counter).toBe(0); expect(_converse.msg_counter).toBe(0);
let view;
const message = 'This message will always increment the message counter from zero', const message = 'This message will always increment the message counter from zero',
sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost', sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
msgFactory = function () { msgFactory = function () {
...@@ -1267,13 +1245,13 @@ ...@@ -1267,13 +1245,13 @@
// leave converse-chat page // leave converse-chat page
_converse.windowState = 'hidden'; _converse.windowState = 'hidden';
_converse.chatboxes.onMessage(msgFactory()); _converse.chatboxes.onMessage(msgFactory());
return test_utils.waitUntil(() => _converse.api.chats.get().length) await test_utils.waitUntil(() => _converse.api.chats.get().length)
.then(() => { let view = _converse.chatboxviews.get(sender_jid);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
expect(_converse.msg_counter).toBe(1); expect(_converse.msg_counter).toBe(1);
// come back to converse-chat page // come back to converse-chat page
_converse.saveWindowState(null, 'focus'); _converse.saveWindowState(null, 'focus');
view = _converse.chatboxviews.get(sender_jid);
expect(u.isVisible(view.el)).toBeTruthy(); expect(u.isVisible(view.el)).toBeTruthy();
expect(_converse.msg_counter).toBe(0); expect(_converse.msg_counter).toBe(0);
...@@ -1283,13 +1261,12 @@ ...@@ -1283,13 +1261,12 @@
// check that msg_counter is incremented from zero again // check that msg_counter is incremented from zero again
_converse.chatboxes.onMessage(msgFactory()); _converse.chatboxes.onMessage(msgFactory());
return test_utils.waitUntil(() => _converse.api.chats.get().length) await test_utils.waitUntil(() => _converse.api.chats.get().length)
}).then(() => {
view = _converse.chatboxviews.get(sender_jid); view = _converse.chatboxviews.get(sender_jid);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
expect(u.isVisible(view.el)).toBeTruthy(); expect(u.isVisible(view.el)).toBeTruthy();
expect(_converse.msg_counter).toBe(1); expect(_converse.msg_counter).toBe(1);
done(); done();
});
})); }));
}); });
......
...@@ -414,13 +414,11 @@ ...@@ -414,13 +414,11 @@
it("is opened when an xmpp: URI is clicked inside another groupchat", it("is opened when an xmpp: URI is clicked inside another groupchat",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { async function (done, _converse) {
let view;
test_utils.createContacts(_converse, 'current'); test_utils.createContacts(_converse, 'current');
test_utils.waitUntil(() => test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy')) await test_utils.waitUntil(() => test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy'));
.then(() => { const view = _converse.chatboxviews.get('lounge@localhost');
view = _converse.chatboxviews.get('lounge@localhost');
if (!view.el.querySelectorAll('.chat-area').length) { if (!view.el.querySelectorAll('.chat-area').length) {
view.renderChatArea(); view.renderChatArea();
} }
...@@ -435,12 +433,11 @@ ...@@ -435,12 +433,11 @@
}).c('body').t(message).tree(); }).c('body').t(message).tree();
view.model.onMessage(msg); view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
view.el.querySelector('.chat-msg__text a').click(); view.el.querySelector('.chat-msg__text a').click();
return test_utils.waitUntil(() => _converse.chatboxes.length === 3) await test_utils.waitUntil(() => _converse.chatboxes.length === 3)
}).then(() => {
expect(_.includes(_converse.chatboxes.pluck('id'), 'coven@chat.shakespeare.lit')).toBe(true); expect(_.includes(_converse.chatboxes.pluck('id'), 'coven@chat.shakespeare.lit')).toBe(true);
done() done()
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
})); }));
it("shows a notification if its not anonymous", it("shows a notification if its not anonymous",
...@@ -507,10 +504,9 @@ ...@@ -507,10 +504,9 @@
it("shows join/leave messages when users enter or exit a groupchat", it("shows join/leave messages when users enter or exit a groupchat",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) { async function (done, _converse) {
test_utils.openChatRoom(_converse, "coven", 'chat.shakespeare.lit', 'some1') await test_utils.openChatRoom(_converse, "coven", 'chat.shakespeare.lit', 'some1');
.then(() => {
const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
const chat_content = view.el.querySelector('.chat-content'); const chat_content = view.el.querySelector('.chat-content');
/* We don't show join/leave messages for existing occupants. We /* We don't show join/leave messages for existing occupants. We
...@@ -573,6 +569,7 @@ ...@@ -573,6 +569,7 @@
'type': 'groupchat' 'type': 'groupchat'
}).c('body').t('hello world').tree(); }).c('body').t('hello world').tree();
_converse.connection._dataRecv(test_utils.createRequest(msg)); _converse.connection._dataRecv(test_utils.createRequest(msg));
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
// Add another entrant, otherwise the above message will be // Add another entrant, otherwise the above message will be
// collapsed if "newguy" leaves immediately again // collapsed if "newguy" leaves immediately again
...@@ -777,7 +774,6 @@ ...@@ -777,7 +774,6 @@
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("newgirl has entered and left the groupchat"); expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("newgirl has entered and left the groupchat");
expect(view.model.occupants.length).toBe(4); expect(view.model.occupants.length).toBe(4);
done(); done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
})); }));
it("combines subsequent join/leave messages when users enter or exit a groupchat", it("combines subsequent join/leave messages when users enter or exit a groupchat",
...@@ -937,22 +933,20 @@ ...@@ -937,22 +933,20 @@
it("shows a new day indicator if a join/leave message is received on a new day", it("shows a new day indicator if a join/leave message is received on a new day",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { async function (done, _converse) {
test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'dummy').then(function () { await test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'dummy');
const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
const chat_content = view.el.querySelector('.chat-content'); const chat_content = view.el.querySelector('.chat-content');
var indicator = chat_content.querySelector('.date-separator'); let indicator = chat_content.querySelector('.date-separator');
expect(indicator).not.toBe(null); expect(indicator).not.toBe(null);
expect(indicator.getAttribute('class')).toEqual('message date-separator'); expect(indicator.getAttribute('class')).toEqual('message date-separator');
expect(indicator.getAttribute('data-isodate')).toEqual(moment().startOf('day').format()); expect(indicator.getAttribute('data-isodate')).toEqual(moment().startOf('day').format());
expect(indicator.querySelector('time').textContent).toEqual(moment().startOf('day').format("dddd MMM Do YYYY")); expect(indicator.querySelector('time').textContent).toEqual(moment().startOf('day').format("dddd MMM Do YYYY"));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(1); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(1);
expect(chat_content.querySelector('div.chat-info').textContent).toBe( expect(chat_content.querySelector('div.chat-info').textContent).toBe("dummy has entered the groupchat");
"dummy has entered the groupchat"
);
var baseTime = new Date(); const baseTime = new Date();
jasmine.clock().install(); jasmine.clock().install();
jasmine.clock().mockDate(baseTime); jasmine.clock().mockDate(baseTime);
var ONE_DAY_LATER = 86400000; var ONE_DAY_LATER = 86400000;
...@@ -1020,7 +1014,7 @@ ...@@ -1020,7 +1014,7 @@
jasmine.clock().tick(ONE_DAY_LATER); jasmine.clock().tick(ONE_DAY_LATER);
var stanza = Strophe.xmlHtmlNode( let stanza = Strophe.xmlHtmlNode(
'<message xmlns="jabber:client"' + '<message xmlns="jabber:client"' +
' to="dummy@localhost/_converse.js-290929789"' + ' to="dummy@localhost/_converse.js-290929789"' +
' type="groupchat"' + ' type="groupchat"' +
...@@ -1029,6 +1023,7 @@ ...@@ -1029,6 +1023,7 @@
' <delay xmlns="urn:xmpp:delay" stamp="'+moment().format()+'" from="some1@localhost"/>'+ ' <delay xmlns="urn:xmpp:delay" stamp="'+moment().format()+'" from="some1@localhost"/>'+
'</message>').firstChild; '</message>').firstChild;
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
presence = $pres({ presence = $pres({
to: 'dummy@localhost/_converse.js-29092160', to: 'dummy@localhost/_converse.js-29092160',
...@@ -1063,6 +1058,7 @@ ...@@ -1063,6 +1058,7 @@
' <delay xmlns="urn:xmpp:delay" stamp="'+moment().format()+'" from="some1@localhost"/>'+ ' <delay xmlns="urn:xmpp:delay" stamp="'+moment().format()+'" from="some1@localhost"/>'+
'</message>').firstChild; '</message>').firstChild;
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
jasmine.clock().tick(ONE_DAY_LATER); jasmine.clock().tick(ONE_DAY_LATER);
// Test a user leaving a groupchat // Test a user leaving a groupchat
...@@ -1093,35 +1089,32 @@ ...@@ -1093,35 +1089,32 @@
'"Disconnected: Replaced by new connection"'); '"Disconnected: Replaced by new connection"');
jasmine.clock().uninstall(); jasmine.clock().uninstall();
done(); done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
})); }));
it("supports the /me command", it("supports the /me command",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { async function (done, _converse) {
let view; await test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp']);
test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp']) await test_utils.waitUntil(() => _converse.xmppstatus.vcard.get('fullname'));
.then(() => test_utils.waitUntil(() => _converse.xmppstatus.vcard.get('fullname')))
.then(() => {
test_utils.createContacts(_converse, 'current'); test_utils.createContacts(_converse, 'current');
return test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy'); await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
}).then(() => { const view = _converse.chatboxviews.get('lounge@localhost');
view = _converse.chatboxviews.get('lounge@localhost');
if (!view.el.querySelectorAll('.chat-area').length) { if (!view.el.querySelectorAll('.chat-area').length) {
view.renderChatArea(); view.renderChatArea();
} }
var message = '/me is tired'; let message = '/me is tired';
var nick = mock.chatroom_names[0], const nick = mock.chatroom_names[0];
msg = $msg({ let msg = $msg({
'from': 'lounge@localhost/'+nick, 'from': 'lounge@localhost/'+nick,
'id': (new Date()).getTime(), 'id': (new Date()).getTime(),
'to': 'dummy@localhost', 'to': 'dummy@localhost',
'type': 'groupchat' 'type': 'groupchat'
}).c('body').t(message).tree(); }).c('body').t(message).tree();
view.model.onMessage(msg); 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(_.includes(view.el.querySelector('.chat-msg__author').textContent, '**Dyon van de Wege')).toBeTruthy();
expect(view.el.querySelector('.chat-msg__text').textContent).toBe(' is tired'); expect(view.el.querySelector('.chat-msg__text').textContent).toBe(' is tired');
...@@ -1133,32 +1126,29 @@ ...@@ -1133,32 +1126,29 @@
type: 'groupchat' type: 'groupchat'
}).c('body').t(message).tree(); }).c('body').t(message).tree();
view.model.onMessage(msg); 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(_.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'); expect(sizzle('.chat-msg__text:last', view.el).pop().textContent).toBe(' is as well');
done(); done();
});
})); }));
it("can be configured if you're its owner", it("can be configured if you're its owner",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) { async function (done, _converse) {
let view, sent_IQ, IQ_id; let sent_IQ, IQ_id;
const sendIQ = _converse.connection.sendIQ; const sendIQ = _converse.connection.sendIQ;
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
sent_IQ = iq; sent_IQ = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback); IQ_id = sendIQ.bind(this)(iq, callback, errback);
}); });
_converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'}) await _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'});
.then(() => { const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
spyOn(view.model, 'saveAffiliationAndRole').and.callThrough(); spyOn(view.model, 'saveAffiliationAndRole').and.callThrough();
// We pretend this is a new room, so no disco info is returned. // We pretend this is a new room, so no disco info is returned.
var features_stanza = $iq({ const features_stanza = $iq({
from: 'coven@chat.shakespeare.lit', from: 'coven@chat.shakespeare.lit',
'id': IQ_id, 'id': IQ_id,
'to': 'dummy@localhost/desktop', 'to': 'dummy@localhost/desktop',
...@@ -1188,8 +1178,7 @@ ...@@ -1188,8 +1178,7 @@
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(view.model.saveAffiliationAndRole).toHaveBeenCalled(); expect(view.model.saveAffiliationAndRole).toHaveBeenCalled();
expect(u.isVisible(view.el.querySelector('.toggle-chatbox-button'))).toBeTruthy(); expect(u.isVisible(view.el.querySelector('.toggle-chatbox-button'))).toBeTruthy();
return test_utils.waitUntil(() => !_.isNull(view.el.querySelector('.configure-chatroom-button'))) await test_utils.waitUntil(() => !_.isNull(view.el.querySelector('.configure-chatroom-button')))
}).then(() => {
expect(u.isVisible(view.el.querySelector('.configure-chatroom-button'))).toBeTruthy(); expect(u.isVisible(view.el.querySelector('.configure-chatroom-button'))).toBeTruthy();
view.el.querySelector('.configure-chatroom-button').click(); view.el.querySelector('.configure-chatroom-button').click();
...@@ -1326,8 +1315,7 @@ ...@@ -1326,8 +1315,7 @@
.c('value').t('cauldronburn'); .c('value').t('cauldronburn');
_converse.connection._dataRecv(test_utils.createRequest(config_stanza)); _converse.connection._dataRecv(test_utils.createRequest(config_stanza));
return test_utils.waitUntil(() => view.el.querySelectorAll('form.chatroom-form').length) await test_utils.waitUntil(() => view.el.querySelectorAll('form.chatroom-form').length)
}).then(() => {
expect(view.el.querySelectorAll('form.chatroom-form').length).toBe(1); expect(view.el.querySelectorAll('form.chatroom-form').length).toBe(1);
expect(view.el.querySelectorAll('form.chatroom-form fieldset').length).toBe(2); expect(view.el.querySelectorAll('form.chatroom-form fieldset').length).toBe(2);
var membersonly = view.el.querySelectorAll('input[name="muc#roomconfig_membersonly"]'); var membersonly = view.el.querySelectorAll('input[name="muc#roomconfig_membersonly"]');
...@@ -1360,15 +1348,14 @@ ...@@ -1360,15 +1348,14 @@
expect(sent_stanza.querySelector('field[var="muc#roomconfig_allowpm"] value').textContent).toBe('moderators'); expect(sent_stanza.querySelector('field[var="muc#roomconfig_allowpm"] value').textContent).toBe('moderators');
expect(sent_stanza.querySelector('field[var="muc#roomconfig_presencebroadcast"] value').textContent).toBe('moderator'); expect(sent_stanza.querySelector('field[var="muc#roomconfig_presencebroadcast"] value').textContent).toBe('moderator');
done(); done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
})); }));
it("shows all members even if they're not currently present in the groupchat", it("shows all members even if they're not currently present in the groupchat",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { async function (done, _converse) {
test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function() { await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
var name; var name;
var view = _converse.chatboxviews.get('lounge@localhost'), var view = _converse.chatboxviews.get('lounge@localhost'),
occupants = view.el.querySelector('.occupant-list'); occupants = view.el.querySelector('.occupant-list');
...@@ -1415,15 +1402,14 @@ ...@@ -1415,15 +1402,14 @@
expect(occupants.querySelectorAll('li').length).toBe(7); expect(occupants.querySelectorAll('li').length).toBe(7);
} }
done(); done();
}).catch(_.partial(console.error, _));
})); }));
it("shows users currently present in the groupchat", it("shows users currently present in the groupchat",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { async function (done, _converse) {
test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function() { await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
var name; var name;
var view = _converse.chatboxviews.get('lounge@localhost'), var view = _converse.chatboxviews.get('lounge@localhost'),
occupants = view.el.querySelector('.occupant-list'); occupants = view.el.querySelector('.occupant-list');
...@@ -1470,13 +1456,13 @@ ...@@ -1470,13 +1456,13 @@
expect(occupants.querySelectorAll('li').length).toBe(i+1); expect(occupants.querySelectorAll('li').length).toBe(i+1);
} }
done(); done();
}).catch(_.partial(console.error, _));
})); }));
it("escapes occupant nicknames when rendering them, to avoid JS-injection attacks", it("escapes occupant nicknames when rendering them, to avoid JS-injection attacks",
mock.initConverseWithPromises(null, ['rosterGroupsFetched'], {}, function (done, _converse) { mock.initConverseWithPromises(null, ['rosterGroupsFetched'], {},
async function (done, _converse) {
test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () { await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
/* <presence xmlns="jabber:client" to="jc@chat.example.org/converse.js-17184538" /* <presence xmlns="jabber:client" to="jc@chat.example.org/converse.js-17184538"
* from="oo@conference.chat.example.org/&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;"> * from="oo@conference.chat.example.org/&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;">
* <x xmlns="http://jabber.org/protocol/muc#user"> * <x xmlns="http://jabber.org/protocol/muc#user">
...@@ -1485,7 +1471,7 @@ ...@@ -1485,7 +1471,7 @@
* </x> * </x>
* </presence>" * </presence>"
*/ */
var presence = $pres({ const presence = $pres({
to:'dummy@localhost/pda', to:'dummy@localhost/pda',
from:"lounge@localhost/&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;" from:"lounge@localhost/&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;"
}).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
...@@ -1496,20 +1482,19 @@ ...@@ -1496,20 +1482,19 @@
.c('status').attrs({code:'110'}).nodeTree; .c('status').attrs({code:'110'}).nodeTree;
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
var view = _converse.chatboxviews.get('lounge@localhost'); const view = _converse.chatboxviews.get('lounge@localhost');
var occupants = view.el.querySelector('.occupant-list').querySelectorAll('li .occupant-nick'); const occupants = view.el.querySelector('.occupant-list').querySelectorAll('li .occupant-nick');
expect(occupants.length).toBe(2); expect(occupants.length).toBe(2);
expect(occupants[0].textContent.trim()).toBe("&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;"); expect(occupants[0].textContent.trim()).toBe("&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;");
done(); done();
});
})); }));
it("indicates moderators and visitors by means of a special css class and tooltip", it("indicates moderators and visitors by means of a special css class and tooltip",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { async function (done, _converse) {
test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () { await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
var view = _converse.chatboxviews.get('lounge@localhost'); var view = _converse.chatboxviews.get('lounge@localhost');
var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost'; var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
...@@ -1565,7 +1550,6 @@ ...@@ -1565,7 +1550,6 @@
contact_jid + ' This user can NOT send messages in this groupchat. Click to mention visitorwoman in your message.' contact_jid + ' This user can NOT send messages in this groupchat. Click to mention visitorwoman in your message.'
); );
done(); done();
}).catch(_.partial(console.error, _));
})); }));
it("properly handles notification that a room has been destroyed", it("properly handles notification that a room has been destroyed",
...@@ -1810,13 +1794,12 @@ ...@@ -1810,13 +1794,12 @@
it("shows received groupchat messages", it("shows received groupchat messages",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { async function (done, _converse) {
let view;
const text = 'This is a received message'; const text = 'This is a received message';
test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () { await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
spyOn(_converse, 'emit'); spyOn(_converse, 'emit');
view = _converse.chatboxviews.get('lounge@localhost'); const view = _converse.chatboxviews.get('lounge@localhost');
if (!view.el.querySelectorAll('.chat-area').length) { if (!view.el.querySelectorAll('.chat-area').length) {
view.renderChatArea(); view.renderChatArea();
} }
...@@ -1826,40 +1809,41 @@ ...@@ -1826,40 +1809,41 @@
'muc_jid': `${view.model.get('jid')}/${nick}` 'muc_jid': `${view.model.get('jid')}/${nick}`
}); });
var message = $msg({ const message = $msg({
from: 'lounge@localhost/'+nick, from: 'lounge@localhost/'+nick,
id: '1', id: '1',
to: 'dummy@localhost', to: 'dummy@localhost',
type: 'groupchat' type: 'groupchat'
}).c('body').t(text); }).c('body').t(text);
view.model.onMessage(message.nodeTree); view.model.onMessage(message.nodeTree);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
const chat_content = view.el.querySelector('.chat-content'); const chat_content = view.el.querySelector('.chat-content');
expect(chat_content.querySelectorAll('.chat-msg').length).toBe(1); expect(chat_content.querySelectorAll('.chat-msg').length).toBe(1);
expect(chat_content.querySelector('.chat-msg__text').textContent).toBe(text); expect(chat_content.querySelector('.chat-msg__text').textContent).toBe(text);
expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object)); expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
done(); done();
});
})); }));
it("shows sent groupchat messages", it("shows sent groupchat messages",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { async function (done, _converse) {
test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () { await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
spyOn(_converse, 'emit'); spyOn(_converse, 'emit');
var view = _converse.chatboxviews.get('lounge@localhost'); const view = _converse.chatboxviews.get('lounge@localhost');
if (!view.el.querySelectorAll('.chat-area').length) { if (!view.el.querySelectorAll('.chat-area').length) {
view.renderChatArea(); view.renderChatArea();
} }
var text = 'This is a sent message'; const text = 'This is a sent message';
var textarea = view.el.querySelector('.chat-textarea'); const textarea = view.el.querySelector('.chat-textarea');
textarea.value = text; textarea.value = text;
view.keyPressed({ view.keyPressed({
target: textarea, target: textarea,
preventDefault: _.noop, preventDefault: _.noop,
keyCode: 13 keyCode: 13
}); });
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
expect(_converse.emit).toHaveBeenCalledWith('messageSend', text); expect(_converse.emit).toHaveBeenCalledWith('messageSend', text);
const chat_content = view.el.querySelector('.chat-content'); const chat_content = view.el.querySelector('.chat-content');
...@@ -1867,7 +1851,7 @@ ...@@ -1867,7 +1851,7 @@
// Let's check that if we receive the same message again, it's // Let's check that if we receive the same message again, it's
// not shown. // not shown.
var message = $msg({ const message = $msg({
from: 'lounge@localhost/dummy', from: 'lounge@localhost/dummy',
to: 'dummy@localhost.com', to: 'dummy@localhost.com',
type: 'groupchat', type: 'groupchat',
...@@ -1876,24 +1860,23 @@ ...@@ -1876,24 +1860,23 @@
view.model.onMessage(message.nodeTree); view.model.onMessage(message.nodeTree);
expect(chat_content.querySelectorAll('.chat-msg').length).toBe(1); expect(chat_content.querySelectorAll('.chat-msg').length).toBe(1);
expect(sizzle('.chat-msg__text:last').pop().textContent).toBe(text); expect(sizzle('.chat-msg__text:last').pop().textContent).toBe(text);
expect(view.model.messages.length).toBe(1);
// We don't emit an event if it's our own message // We don't emit an event if it's our own message
expect(_converse.emit.calls.count(), 1); expect(_converse.emit.calls.count(), 1);
done(); done();
});
})); }));
it("will cause the chat area to be scrolled down only if it was at the bottom already", it("will cause the chat area to be scrolled down only if it was at the bottom already",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { async function (done, _converse) {
var message = 'This message is received while the chat area is scrolled up'; var message = 'This message is received while the chat area is scrolled up';
test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () { await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
var view = _converse.chatboxviews.get('lounge@localhost'); var view = _converse.chatboxviews.get('lounge@localhost');
spyOn(view, 'scrollDown').and.callThrough(); spyOn(view, 'scrollDown').and.callThrough();
/* Create enough messages so that there's a // Create enough messages so that there's a scrollbar.
* scrollbar. const promises = [];
*/
for (var i=0; i<20; i++) { for (var i=0; i<20; i++) {
view.model.onMessage( view.model.onMessage(
$msg({ $msg({
...@@ -1902,9 +1885,11 @@ ...@@ -1902,9 +1885,11 @@
type: 'groupchat', type: 'groupchat',
id: (new Date()).getTime(), id: (new Date()).getTime(),
}).c('body').t('Message: '+i).tree()); }).c('body').t('Message: '+i).tree());
promises.push(new Promise((resolve, reject) => view.once('messageInserted', resolve)))
} }
await Promise.all(promises);
// Give enough time for `markScrolled` to have been called // Give enough time for `markScrolled` to have been called
setTimeout(() => { setTimeout(async () => {
view.content.scrollTop = 0; view.content.scrollTop = 0;
view.model.onMessage( view.model.onMessage(
$msg({ $msg({
...@@ -1913,6 +1898,7 @@ ...@@ -1913,6 +1898,7 @@
type: 'groupchat', type: 'groupchat',
id: (new Date()).getTime(), id: (new Date()).getTime(),
}).c('body').t(message).tree()); }).c('body').t(message).tree());
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
// Now check that the message appears inside the chatbox in the DOM // Now check that the message appears inside the chatbox in the DOM
const chat_content = view.el.querySelector('.chat-content'); const chat_content = view.el.querySelector('.chat-content');
...@@ -1921,7 +1907,6 @@ ...@@ -1921,7 +1907,6 @@
expect(view.content.scrollTop).toBe(0); expect(view.content.scrollTop).toBe(0);
done(); done();
}, 500); }, 500);
});
})); }));
it("shows the room topic in the header", it("shows the room topic in the header",
...@@ -3887,14 +3872,11 @@ ...@@ -3887,14 +3872,11 @@
it("will be shown if received", it("will be shown if received",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { async function (done, _converse) {
let view;
const room_jid = 'coven@chat.shakespeare.lit'; const room_jid = 'coven@chat.shakespeare.lit';
test_utils.openAndEnterChatRoom( await test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'some1');
_converse, 'coven', 'chat.shakespeare.lit', 'some1').then(function () { const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
const chat_content = view.el.querySelector('.chat-content'); const chat_content = view.el.querySelector('.chat-content');
expect(sizzle('div.chat-info:first', chat_content).pop().textContent) expect(sizzle('div.chat-info:first', chat_content).pop().textContent)
...@@ -3941,16 +3923,16 @@ ...@@ -3941,16 +3923,16 @@
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg); view.model.onMessage(msg);
return test_utils.waitUntil(() => view.el.querySelectorAll('.chat-state-notification').length); await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-state-notification').length);
}).then(() => {
// Check that the notification appears inside the chatbox in the DOM // Check that the notification appears inside the chatbox in the DOM
var events = view.el.querySelectorAll('.chat-event'); let events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3); expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat'); expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
var notifications = view.el.querySelectorAll('.chat-state-notification'); let notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(1); expect(notifications.length).toBe(1);
expect(notifications[0].textContent).toEqual('newguy is typing'); expect(notifications[0].textContent).toEqual('newguy is typing');
...@@ -3960,13 +3942,14 @@ ...@@ -3960,13 +3942,14 @@
}); });
// Check that it doesn't appear twice // Check that it doesn't appear twice
let msg = $msg({ msg = $msg({
from: room_jid+'/newguy', from: room_jid+'/newguy',
id: (new Date()).getTime(), id: (new Date()).getTime(),
to: 'dummy@localhost', to: 'dummy@localhost',
type: 'groupchat' type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg); view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
events = view.el.querySelectorAll('.chat-event'); events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3); expect(events.length).toBe(3);
...@@ -3988,6 +3971,7 @@ ...@@ -3988,6 +3971,7 @@
type: 'groupchat' type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg); view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
events = view.el.querySelectorAll('.chat-event'); events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3); expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat'); expect(events[0].textContent).toEqual('some1 has entered the groupchat');
...@@ -4009,8 +3993,9 @@ ...@@ -4009,8 +3993,9 @@
type: 'groupchat' type: 'groupchat'
}).c('body').t('hello world').tree(); }).c('body').t('hello world').tree();
view.model.onMessage(msg); view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
var messages = view.el.querySelectorAll('.message'); const messages = view.el.querySelectorAll('.message');
expect(messages.length).toBe(7); expect(messages.length).toBe(7);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.el.querySelector('.chat-msg .chat-msg__text').textContent).toBe('hello world'); expect(view.el.querySelector('.chat-msg .chat-msg__text').textContent).toBe('hello world');
...@@ -4038,7 +4023,6 @@ ...@@ -4038,7 +4023,6 @@
notifications = view.el.querySelectorAll('.chat-state-notification'); notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(0); expect(notifications.length).toBe(0);
done(); done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
})); }));
}); });
...@@ -4046,10 +4030,9 @@ ...@@ -4046,10 +4030,9 @@
it("will be shown if received", it("will be shown if received",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) { async function (done, _converse) {
test_utils.openChatRoom(_converse, "coven", 'chat.shakespeare.lit', 'some1') await test_utils.openChatRoom(_converse, "coven", 'chat.shakespeare.lit', 'some1');
.then(() => {
const room_jid = 'coven@chat.shakespeare.lit'; const room_jid = 'coven@chat.shakespeare.lit';
const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
const chat_content = view.el.querySelector('.chat-content'); const chat_content = view.el.querySelector('.chat-content');
...@@ -4062,7 +4045,7 @@ ...@@ -4062,7 +4045,7 @@
* </x> * </x>
* </presence></body> * </presence></body>
*/ */
var presence = $pres({ let presence = $pres({
to: 'dummy@localhost/_converse.js-29092160', to: 'dummy@localhost/_converse.js-29092160',
from: 'coven@chat.shakespeare.lit/some1' from: 'coven@chat.shakespeare.lit/some1'
}).c('x', {xmlns: Strophe.NS.MUC_USER}) }).c('x', {xmlns: Strophe.NS.MUC_USER})
...@@ -4116,6 +4099,7 @@ ...@@ -4116,6 +4099,7 @@
type: 'groupchat' type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg); view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
// Check that the notification appears inside the chatbox in the DOM // Check that the notification appears inside the chatbox in the DOM
var events = view.el.querySelectorAll('.chat-event'); var events = view.el.querySelectorAll('.chat-event');
...@@ -4136,6 +4120,7 @@ ...@@ -4136,6 +4120,7 @@
type: 'groupchat' type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg); view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
events = view.el.querySelectorAll('.chat-event'); events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3); expect(events.length).toBe(3);
...@@ -4155,6 +4140,7 @@ ...@@ -4155,6 +4140,7 @@
type: 'groupchat' type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg); view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
events = view.el.querySelectorAll('.chat-event'); events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3); expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat'); expect(events[0].textContent).toEqual('some1 has entered the groupchat');
...@@ -4174,6 +4160,7 @@ ...@@ -4174,6 +4160,7 @@
type: 'groupchat' type: 'groupchat'
}).c('body').c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree(); }).c('body').c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg); view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
events = view.el.querySelectorAll('.chat-event'); events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3); expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat'); expect(events[0].textContent).toEqual('some1 has entered the groupchat');
...@@ -4185,7 +4172,6 @@ ...@@ -4185,7 +4172,6 @@
expect(notifications[0].textContent).toEqual('nomorenicks is typing'); expect(notifications[0].textContent).toEqual('nomorenicks is typing');
expect(notifications[1].textContent).toEqual('newguy has stopped typing'); expect(notifications[1].textContent).toEqual('newguy has stopped typing');
done(); done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
})); }));
}); });
}); });
......
...@@ -268,37 +268,34 @@ ...@@ -268,37 +268,34 @@
describe("when clicked and a file chosen", function () { describe("when clicked and a file chosen", function () {
it("is uploaded and sent out", mock.initConverseWithAsync(function (done, _converse) { it("is uploaded and sent out", mock.initConverseWithAsync(
test_utils.waitUntilDiscoConfirmed( async function (done, _converse) {
await test_utils.waitUntilDiscoConfirmed(
_converse, _converse.domain, _converse, _converse.domain,
[{'category': 'server', 'type':'IM'}], [{'category': 'server', 'type':'IM'}],
['http://jabber.org/protocol/disco#items'], [], 'info').then(function () { ['http://jabber.org/protocol/disco#items'], [], 'info');
var send_backup = XMLHttpRequest.prototype.send; const send_backup = XMLHttpRequest.prototype.send;
var IQ_stanzas = _converse.connection.IQ_stanzas; const IQ_stanzas = _converse.connection.IQ_stanzas;
let contact_jid;
test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items') await test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items');
.then(() => test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], [])) await test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], []);
.then(() => {
test_utils.createContacts(_converse, 'current'); test_utils.createContacts(_converse, 'current');
_converse.emit('rosterContactsFetched'); _converse.emit('rosterContactsFetched');
contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost'; const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
return test_utils.openChatBoxFor(_converse, contact_jid); await test_utils.openChatBoxFor(_converse, contact_jid);
}).then(() => { const view = _converse.chatboxviews.get(contact_jid);
var view = _converse.chatboxviews.get(contact_jid); const file = {
var file = {
'type': 'image/jpeg', 'type': 'image/jpeg',
'size': '23456' , 'size': '23456' ,
'lastModifiedDate': "", 'lastModifiedDate': "",
'name': "my-juliet.jpg" 'name': "my-juliet.jpg"
}; };
view.model.sendFiles([file]); view.model.sendFiles([file]);
return test_utils.waitUntil(function () { await new Promise((resolve, reject) => view.once('messageInserted', resolve));
return _.filter(IQ_stanzas, function (iq) {
return iq.nodeTree.querySelector('iq[to="upload.montague.tld"] request'); await test_utils.waitUntil(() => _.filter(IQ_stanzas, iq => iq.nodeTree.querySelector('iq[to="upload.montague.tld"] request')).length);
}).length > 0;
}).then(function () {
var iq = IQ_stanzas.pop(); var iq = IQ_stanzas.pop();
expect(iq.toLocaleString()).toBe( expect(iq.toLocaleString()).toBe(
`<iq from="dummy@localhost/resource" `+ `<iq from="dummy@localhost/resource" `+
...@@ -334,24 +331,24 @@ ...@@ -334,24 +331,24 @@
const message = view.model.messages.at(0); const message = view.model.messages.at(0);
expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0'); expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
message.set('progress', 0.5); message.set('progress', 0.5);
expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0.5'); test_utils.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '0.5')
.then(() => {
message.set('progress', 1); message.set('progress', 1);
expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('1'); test_utils.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '1')
}).then(() => {
message.save({ message.save({
'upload': _converse.SUCCESS, 'upload': _converse.SUCCESS,
'oob_url': message.get('get'), 'oob_url': message.get('get'),
'message': message.get('get') 'message': message.get('get')
}); });
return new Promise((resolve, reject) => view.model.messages.once('rendered', resolve));
}); });
var sent_stanza;
spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
sent_stanza = stanza;
}); });
let sent_stanza;
spyOn(_converse.connection, 'send').and.callFake(stanza => (sent_stanza = stanza));
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(function () { await test_utils.waitUntil(() => sent_stanza, 1000);
return sent_stanza;
}, 1000).then(function () {
expect(sent_stanza.toLocaleString()).toBe( expect(sent_stanza.toLocaleString()).toBe(
`<message from="dummy@localhost/resource" `+ `<message from="dummy@localhost/resource" `+
`id="${sent_stanza.nodeTree.getAttribute("id")}" `+ `id="${sent_stanza.nodeTree.getAttribute("id")}" `+
...@@ -364,8 +361,7 @@ ...@@ -364,8 +361,7 @@
`<url>${message}</url>`+ `<url>${message}</url>`+
`</x>`+ `</x>`+
`</message>`); `</message>`);
return test_utils.waitUntil(() => view.el.querySelector('.chat-image'), 1000); await test_utils.waitUntil(() => view.el.querySelector('.chat-image'), 1000);
}).then(function () {
// Check that the image renders // Check that the image renders
expect(view.el.querySelector('.chat-msg .chat-msg__media').innerHTML.trim()).toEqual( expect(view.el.querySelector('.chat-msg .chat-msg__media').innerHTML.trim()).toEqual(
`<!-- src/templates/image.html -->\n`+ `<!-- src/templates/image.html -->\n`+
...@@ -374,38 +370,33 @@ ...@@ -374,38 +370,33 @@
`</a>`); `</a>`);
XMLHttpRequest.prototype.send = send_backup; XMLHttpRequest.prototype.send = send_backup;
done(); done();
});
});
});
});
})); }));
it("is uploaded and sent out from a groupchat", mock.initConverseWithAsync(function (done, _converse) { it("is uploaded and sent out from a groupchat", mock.initConverseWithAsync(
test_utils.waitUntilDiscoConfirmed( async function (done, _converse) {
await test_utils.waitUntilDiscoConfirmed(
_converse, _converse.domain, _converse, _converse.domain,
[{'category': 'server', 'type':'IM'}], [{'category': 'server', 'type':'IM'}],
['http://jabber.org/protocol/disco#items'], [], 'info').then(function () { ['http://jabber.org/protocol/disco#items'], [], 'info');
var send_backup = XMLHttpRequest.prototype.send; const send_backup = XMLHttpRequest.prototype.send;
var IQ_stanzas = _converse.connection.IQ_stanzas; const IQ_stanzas = _converse.connection.IQ_stanzas;
test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items').then(function () { await test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items');
test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], []).then(function () { await test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], []);
test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () { await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
var view = _converse.chatboxviews.get('lounge@localhost'); const view = _converse.chatboxviews.get('lounge@localhost');
var file = { const file = {
'type': 'image/jpeg', 'type': 'image/jpeg',
'size': '23456' , 'size': '23456' ,
'lastModifiedDate': "", 'lastModifiedDate': "",
'name': "my-juliet.jpg" 'name': "my-juliet.jpg"
}; };
view.model.sendFiles([file]); view.model.sendFiles([file]);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
return test_utils.waitUntil(function () { await test_utils.waitUntil(() => _.filter(IQ_stanzas, iq => iq.nodeTree.querySelector('iq[to="upload.montague.tld"] request')).length);
return _.filter(IQ_stanzas, function (iq) {
return iq.nodeTree.querySelector('iq[to="upload.montague.tld"] request');
}).length > 0;
}).then(function () {
var iq = IQ_stanzas.pop(); var iq = IQ_stanzas.pop();
expect(iq.toLocaleString()).toBe( expect(iq.toLocaleString()).toBe(
`<iq from="dummy@localhost/resource" `+ `<iq from="dummy@localhost/resource" `+
...@@ -441,22 +432,24 @@ ...@@ -441,22 +432,24 @@
const message = view.model.messages.at(0); const message = view.model.messages.at(0);
expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0'); expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
message.set('progress', 0.5); message.set('progress', 0.5);
expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0.5'); test_utils.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '0.5')
.then(() => {
message.set('progress', 1); message.set('progress', 1);
expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('1'); test_utils.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '1')
}).then(() => {
message.save({ message.save({
'upload': _converse.SUCCESS, 'upload': _converse.SUCCESS,
'oob_url': message.get('get'), 'oob_url': message.get('get'),
'message': message.get('get') 'message': message.get('get')
}); });
return new Promise((resolve, reject) => view.model.messages.once('rendered', resolve));
}); });
var sent_stanza;
spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
sent_stanza = stanza;
}); });
let sent_stanza;
spyOn(_converse.connection, 'send').and.callFake(stanza => (sent_stanza = stanza));
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => sent_stanza, 1000).then(function () { await test_utils.waitUntil(() => sent_stanza, 1000);
expect(sent_stanza.toLocaleString()).toBe( expect(sent_stanza.toLocaleString()).toBe(
`<message `+ `<message `+
`from="dummy@localhost/resource" `+ `from="dummy@localhost/resource" `+
...@@ -470,8 +463,7 @@ ...@@ -470,8 +463,7 @@
`<url>${message}</url>`+ `<url>${message}</url>`+
`</x>`+ `</x>`+
`</message>`); `</message>`);
return test_utils.waitUntil(() => view.el.querySelector('.chat-image'), 1000); await test_utils.waitUntil(() => view.el.querySelector('.chat-image'), 1000);
}).then(function () {
// Check that the image renders // Check that the image renders
expect(view.el.querySelector('.chat-msg .chat-msg__media').innerHTML.trim()).toEqual( expect(view.el.querySelector('.chat-msg .chat-msg__media').innerHTML.trim()).toEqual(
`<!-- src/templates/image.html -->\n`+ `<!-- src/templates/image.html -->\n`+
...@@ -480,12 +472,6 @@ ...@@ -480,12 +472,6 @@
`</a>`); `</a>`);
XMLHttpRequest.prototype.send = send_backup; XMLHttpRequest.prototype.send = send_backup;
done(); done();
});
});
});
});
});
});
})); }));
it("shows an error message if the file is too large", mock.initConverseWithAsync(function (done, _converse) { it("shows an error message if the file is too large", mock.initConverseWithAsync(function (done, _converse) {
...@@ -617,27 +603,24 @@ ...@@ -617,27 +603,24 @@
describe("While a file is being uploaded", function () { describe("While a file is being uploaded", function () {
it("shows a progress bar", mock.initConverseWithPromises( it("shows a progress bar", mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
test_utils.waitUntilDiscoConfirmed( await test_utils.waitUntilDiscoConfirmed(
_converse, _converse.domain, _converse, _converse.domain,
[{'category': 'server', 'type':'IM'}], [{'category': 'server', 'type':'IM'}],
['http://jabber.org/protocol/disco#items'], [], 'info').then(function () { ['http://jabber.org/protocol/disco#items'], [], 'info');
var send_backup = XMLHttpRequest.prototype.send; const send_backup = XMLHttpRequest.prototype.send;
var IQ_stanzas = _converse.connection.IQ_stanzas; const IQ_stanzas = _converse.connection.IQ_stanzas;
let view, contact_jid;
test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items') await test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items');
.then(() => test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], [])) await test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], []);
.then(() => {
test_utils.createContacts(_converse, 'current'); test_utils.createContacts(_converse, 'current');
_converse.emit('rosterContactsFetched'); _converse.emit('rosterContactsFetched');
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost'; await test_utils.openChatBoxFor(_converse, contact_jid);
return test_utils.openChatBoxFor(_converse, contact_jid); const view = _converse.chatboxviews.get(contact_jid);
}).then(() => {
view = _converse.chatboxviews.get(contact_jid);
const file = { const file = {
'type': 'image/jpeg', 'type': 'image/jpeg',
'size': '23456' , 'size': '23456' ,
...@@ -645,8 +628,8 @@ ...@@ -645,8 +628,8 @@
'name': "my-juliet.jpg" 'name': "my-juliet.jpg"
}; };
view.model.sendFiles([file]); view.model.sendFiles([file]);
return test_utils.waitUntil(() => _.filter(IQ_stanzas, (iq) => iq.nodeTree.querySelector('iq[to="upload.montague.tld"] request')).length) await new Promise((resolve, reject) => view.once('messageInserted', resolve));
}).then(function () { await test_utils.waitUntil(() => _.filter(IQ_stanzas, (iq) => iq.nodeTree.querySelector('iq[to="upload.montague.tld"] request')).length)
const iq = IQ_stanzas.pop(); const iq = IQ_stanzas.pop();
expect(iq.toLocaleString()).toBe( expect(iq.toLocaleString()).toBe(
`<iq from="dummy@localhost/resource" `+ `<iq from="dummy@localhost/resource" `+
...@@ -680,19 +663,18 @@ ...@@ -680,19 +663,18 @@
const message = view.model.messages.at(0); const message = view.model.messages.at(0);
expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0'); expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
message.set('progress', 0.5); message.set('progress', 0.5);
expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0.5'); test_utils.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '0.5')
.then(() => {
message.set('progress', 1); message.set('progress', 1);
expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('1'); test_utils.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '1')
}).then(() => {
expect(view.el.querySelector('.chat-content .chat-msg__text').textContent).toBe('Uploading file: my-juliet.jpg, 22.91 KB'); expect(view.el.querySelector('.chat-content .chat-msg__text').textContent).toBe('Uploading file: my-juliet.jpg, 22.91 KB');
done(); done();
}); });
var sent_stanza;
spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
sent_stanza = stanza;
}); });
let sent_stanza;
spyOn(_converse.connection, 'send').and.callFake(stanza => (sent_stanza = stanza));
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
});
});
})); }));
}); });
}); });
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -95,21 +95,18 @@ ...@@ -95,21 +95,18 @@
{ whitelisted_plugins: ['converse-roomslist'], { whitelisted_plugins: ['converse-roomslist'],
allow_bookmarks: false // Makes testing easier, otherwise we allow_bookmarks: false // Makes testing easier, otherwise we
// have to mock stanza traffic. // have to mock stanza traffic.
}, function (done, _converse) { }, async function (done, _converse) {
let view;
const IQ_stanzas = _converse.connection.IQ_stanzas; const IQ_stanzas = _converse.connection.IQ_stanzas;
const room_jid = 'coven@chat.shakespeare.lit'; const room_jid = 'coven@chat.shakespeare.lit';
test_utils.openControlBox(); test_utils.openControlBox();
_converse.api.rooms.open(room_jid, {'nick': 'some1'}) await _converse.api.rooms.open(room_jid, {'nick': 'some1'});
.then(() => { const last_stanza = await test_utils.waitUntil(() => _.get(_.filter(
return test_utils.waitUntil(() => _.get(_.filter(
IQ_stanzas, IQ_stanzas,
iq => iq.nodeTree.querySelector( iq => iq.nodeTree.querySelector(
`iq[to="${room_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]` `iq[to="${room_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
)).pop(), 'nodeTree')); )).pop(), 'nodeTree'));
}).then(last_stanza => { const view = _converse.chatboxviews.get(room_jid);
view = _converse.chatboxviews.get(room_jid);
const IQ_id = last_stanza.getAttribute('id'); const IQ_id = last_stanza.getAttribute('id');
const features_stanza = $iq({ const features_stanza = $iq({
'from': 'coven@chat.shakespeare.lit', 'from': 'coven@chat.shakespeare.lit',
...@@ -139,9 +136,8 @@ ...@@ -139,9 +136,8 @@
.c('field', {'type':'text-single', 'var':'muc#roominfo_occupants', 'label':'Number of occupants'}) .c('field', {'type':'text-single', 'var':'muc#roominfo_occupants', 'label':'Number of occupants'})
.c('value').t(0); .c('value').t(0);
_converse.connection._dataRecv(test_utils.createRequest(features_stanza)); _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
return test_utils.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING) await test_utils.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING)
}).then(function () { let presence = $pres({
var presence = $pres({
to: _converse.connection.jid, to: _converse.connection.jid,
from: 'coven@chat.shakespeare.lit/some1', from: 'coven@chat.shakespeare.lit/some1',
id: 'DC352437-C019-40EC-B590-AF29E879AF97' id: 'DC352437-C019-40EC-B590-AF29E879AF97'
...@@ -160,9 +156,7 @@ ...@@ -160,9 +156,7 @@
info_el.click(); info_el.click();
const modal = view.model.room_details_modal; const modal = view.model.room_details_modal;
return test_utils.waitUntil(() => u.isVisible(modal.el), 2000); await test_utils.waitUntil(() => u.isVisible(modal.el), 2000);
}).then(() => {
const modal = view.model.room_details_modal;
let els = modal.el.querySelectorAll('p.room-info'); let els = modal.el.querySelectorAll('p.room-info');
expect(els[0].textContent).toBe("Name: A Dark Cave") expect(els[0].textContent).toBe("Name: A Dark Cave")
expect(els[1].textContent).toBe("Groupchat address (JID): coven@chat.shakespeare.lit") expect(els[1].textContent).toBe("Groupchat address (JID): coven@chat.shakespeare.lit")
...@@ -177,7 +171,7 @@ ...@@ -177,7 +171,7 @@
'Not anonymous - All other groupchat participants can see your XMPP username'+ 'Not anonymous - All other groupchat participants can see your XMPP username'+
'Not moderated - Participants entering this groupchat can write right away' 'Not moderated - Participants entering this groupchat can write right away'
); );
const presence = $pres({ presence = $pres({
to: 'dummy@localhost/_converse.js-29092160', to: 'dummy@localhost/_converse.js-29092160',
from: 'coven@chat.shakespeare.lit/newguy' from: 'coven@chat.shakespeare.lit/newguy'
}) })
...@@ -201,7 +195,6 @@ ...@@ -201,7 +195,6 @@
expect(els[4].textContent).toBe("Topic author: someone") expect(els[4].textContent).toBe("Topic author: someone")
expect(els[5].textContent).toBe("Online users: 2") expect(els[5].textContent).toBe("Online users: 2")
done(); done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
})); }));
it("can be closed", mock.initConverseWithPromises( it("can be closed", mock.initConverseWithPromises(
......
(function (root, factory) { (function (root, factory) {
define(["jasmine", "mock", "test-utils"], factory); define(["jasmine", "mock", "test-utils"], factory);
} (this, function (jasmine, mock, test_utils) { } (this, function (jasmine, mock, test_utils) {
var _ = converse.env._; const _ = converse.env._;
var Strophe = converse.env.Strophe; const Strophe = converse.env.Strophe;
var $msg = converse.env.$msg; const $msg = converse.env.$msg;
var $pres = converse.env.$pres; const $pres = converse.env.$pres;
var u = converse.env.utils; const u = converse.env.utils;
return describe("A spoiler message", function () { describe("A spoiler message", function () {
it("can be received with a hint", it("can be received with a hint",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'current'); test_utils.createContacts(_converse, 'current');
var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
/* <message to='romeo@montague.net/orchard' from='juliet@capulet.net/balcony' id='spoiler2'> /* <message to='romeo@montague.net/orchard' from='juliet@capulet.net/balcony' id='spoiler2'>
* <body>And at the end of the story, both of them die! It is so tragic!</body> * <body>And at the end of the story, both of them die! It is so tragic!</body>
* <spoiler xmlns='urn:xmpp:spoiler:0'>Love story end</spoiler> * <spoiler xmlns='urn:xmpp:spoiler:0'>Love story end</spoiler>
* </message> * </message>
*/ */
var spoiler_hint = "Love story end" const spoiler_hint = "Love story end"
var spoiler = "And at the end of the story, both of them die! It is so tragic!"; const spoiler = "And at the end of the story, both of them die! It is so tragic!";
var msg = $msg({ const msg = $msg({
'xmlns': 'jabber:client', 'xmlns': 'jabber:client',
'to': _converse.bare_jid, 'to': _converse.bare_jid,
'from': sender_jid, 'from': sender_jid,
...@@ -36,36 +36,30 @@ ...@@ -36,36 +36,30 @@
.tree(); .tree();
_converse.chatboxes.onMessage(msg); _converse.chatboxes.onMessage(msg);
var view = _converse.chatboxviews.get(sender_jid); const view = _converse.chatboxviews.get(sender_jid);
await test_utils.waitUntil(() => view.model.vcard.get('fullname') === 'Max Frankfurter')
return test_utils.waitUntil(() => view.model.vcard.get('fullname') === 'Max Frankfurter')
.then(function () {
expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Max Frankfurter'); expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Max Frankfurter');
const message_content = view.el.querySelector('.chat-msg__text');
var message_content = view.el.querySelector('.chat-msg__text');
expect(message_content.textContent).toBe(spoiler); expect(message_content.textContent).toBe(spoiler);
const spoiler_hint_el = view.el.querySelector('.spoiler-hint');
var spoiler_hint_el = view.el.querySelector('.spoiler-hint');
expect(spoiler_hint_el.textContent).toBe(spoiler_hint); expect(spoiler_hint_el.textContent).toBe(spoiler_hint);
done(); done();
});
})); }));
it("can be received without a hint", it("can be received without a hint",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'current'); test_utils.createContacts(_converse, 'current');
var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
/* <message to='romeo@montague.net/orchard' from='juliet@capulet.net/balcony' id='spoiler2'> /* <message to='romeo@montague.net/orchard' from='juliet@capulet.net/balcony' id='spoiler2'>
* <body>And at the end of the story, both of them die! It is so tragic!</body> * <body>And at the end of the story, both of them die! It is so tragic!</body>
* <spoiler xmlns='urn:xmpp:spoiler:0'>Love story end</spoiler> * <spoiler xmlns='urn:xmpp:spoiler:0'>Love story end</spoiler>
* </message> * </message>
*/ */
var spoiler = "And at the end of the story, both of them die! It is so tragic!"; const spoiler = "And at the end of the story, both of them die! It is so tragic!";
var msg = $msg({ const msg = $msg({
'xmlns': 'jabber:client', 'xmlns': 'jabber:client',
'to': _converse.bare_jid, 'to': _converse.bare_jid,
'from': sender_jid, 'from': sender_jid,
...@@ -75,25 +69,20 @@ ...@@ -75,25 +69,20 @@
'xmlns': 'urn:xmpp:spoiler:0', 'xmlns': 'urn:xmpp:spoiler:0',
}).tree(); }).tree();
_converse.chatboxes.onMessage(msg); _converse.chatboxes.onMessage(msg);
const view = _converse.chatboxviews.get(sender_jid);
var view = _converse.chatboxviews.get(sender_jid); await test_utils.waitUntil(() => view.model.vcard.get('fullname') === 'Max Frankfurter')
return test_utils.waitUntil(() => view.model.vcard.get('fullname') === 'Max Frankfurter')
.then(function () {
expect(_.includes(view.el.querySelector('.chat-msg__author').textContent, 'Max Frankfurter')).toBeTruthy(); expect(_.includes(view.el.querySelector('.chat-msg__author').textContent, 'Max Frankfurter')).toBeTruthy();
const message_content = view.el.querySelector('.chat-msg__text');
var message_content = view.el.querySelector('.chat-msg__text');
expect(message_content.textContent).toBe(spoiler); expect(message_content.textContent).toBe(spoiler);
const spoiler_hint_el = view.el.querySelector('.spoiler-hint');
var spoiler_hint_el = view.el.querySelector('.spoiler-hint');
expect(spoiler_hint_el.textContent).toBe(''); expect(spoiler_hint_el.textContent).toBe('');
done(); done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
})); }));
it("can be sent without a hint", it("can be sent without a hint",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'current', 1); test_utils.createContacts(_converse, 'current', 1);
_converse.emit('rosterContactsFetched'); _converse.emit('rosterContactsFetched');
...@@ -105,22 +94,21 @@ ...@@ -105,22 +94,21 @@
// have a resource, that resource is then queried to see // have a resource, that resource is then queried to see
// whether Strophe.NS.SPOILER is supported, in which case // whether Strophe.NS.SPOILER is supported, in which case
// the spoiler button will appear. // the spoiler button will appear.
var presence = $pres({ const presence = $pres({
'from': contact_jid+'/phone', 'from': contact_jid+'/phone',
'to': 'dummy@localhost' 'to': 'dummy@localhost'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
test_utils.openChatBoxFor(_converse, contact_jid) await test_utils.openChatBoxFor(_converse, contact_jid);
.then(() => test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER])) await test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]);
.then(() => { const view = _converse.chatboxviews.get(contact_jid);
var view = _converse.chatboxviews.get(contact_jid);
spyOn(view, 'onMessageSubmitted').and.callThrough(); spyOn(view, 'onMessageSubmitted').and.callThrough();
spyOn(_converse.connection, 'send'); spyOn(_converse.connection, 'send');
var spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler'); let spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
spoiler_toggle.click(); spoiler_toggle.click();
var textarea = view.el.querySelector('.chat-textarea'); const textarea = view.el.querySelector('.chat-textarea');
textarea.value = 'This is the spoiler'; textarea.value = 'This is the spoiler';
view.keyPressed({ view.keyPressed({
target: textarea, target: textarea,
...@@ -128,6 +116,7 @@ ...@@ -128,6 +116,7 @@
keyCode: 13 keyCode: 13
}); });
expect(view.onMessageSubmitted).toHaveBeenCalled(); expect(view.onMessageSubmitted).toHaveBeenCalled();
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
/* Test the XML stanza /* Test the XML stanza
* *
...@@ -141,18 +130,18 @@ ...@@ -141,18 +130,18 @@
* <spoiler xmlns="urn:xmpp:spoiler:0"/> * <spoiler xmlns="urn:xmpp:spoiler:0"/>
* </message>" * </message>"
*/ */
var stanza = _converse.connection.send.calls.argsFor(0)[0].tree(); const stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
var spoiler_el = stanza.querySelector('spoiler[xmlns="urn:xmpp:spoiler:0"]'); const spoiler_el = stanza.querySelector('spoiler[xmlns="urn:xmpp:spoiler:0"]');
expect(_.isNull(spoiler_el)).toBeFalsy(); expect(_.isNull(spoiler_el)).toBeFalsy();
expect(spoiler_el.textContent).toBe(''); expect(spoiler_el.textContent).toBe('');
var body_el = stanza.querySelector('body'); const body_el = stanza.querySelector('body');
expect(body_el.textContent).toBe('This is the spoiler'); expect(body_el.textContent).toBe('This is the spoiler');
/* Test the HTML spoiler message */ /* Test the HTML spoiler message */
expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Max Mustermann'); expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Max Mustermann');
var spoiler_msg_el = view.el.querySelector('.chat-msg__text.spoiler'); const spoiler_msg_el = view.el.querySelector('.chat-msg__text.spoiler');
expect(spoiler_msg_el.textContent).toBe('This is the spoiler'); expect(spoiler_msg_el.textContent).toBe('This is the spoiler');
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy(); expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
...@@ -164,42 +153,40 @@ ...@@ -164,42 +153,40 @@
spoiler_toggle.click(); spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy(); expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
done(); done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
})); }));
it("can be sent with a hint", it("can be sent with a hint",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'current', 1); test_utils.createContacts(_converse, 'current', 1);
_converse.emit('rosterContactsFetched'); _converse.emit('rosterContactsFetched');
test_utils.openControlBox(); test_utils.openControlBox();
var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
// XXX: We need to send a presence from the contact, so that we // XXX: We need to send a presence from the contact, so that we
// have a resource, that resource is then queried to see // have a resource, that resource is then queried to see
// whether Strophe.NS.SPOILER is supported, in which case // whether Strophe.NS.SPOILER is supported, in which case
// the spoiler button will appear. // the spoiler button will appear.
var presence = $pres({ const presence = $pres({
'from': contact_jid+'/phone', 'from': contact_jid+'/phone',
'to': 'dummy@localhost' 'to': 'dummy@localhost'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
test_utils.openChatBoxFor(_converse, contact_jid) await test_utils.openChatBoxFor(_converse, contact_jid);
.then(() => test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER])) await test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]);
.then(() => { const view = _converse.chatboxviews.get(contact_jid);
var view = _converse.chatboxviews.get(contact_jid); let spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
var spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
spoiler_toggle.click(); spoiler_toggle.click();
spyOn(view, 'onMessageSubmitted').and.callThrough(); spyOn(view, 'onMessageSubmitted').and.callThrough();
spyOn(_converse.connection, 'send'); spyOn(_converse.connection, 'send');
var textarea = view.el.querySelector('.chat-textarea'); const textarea = view.el.querySelector('.chat-textarea');
textarea.value = 'This is the spoiler'; textarea.value = 'This is the spoiler';
var hint_input = view.el.querySelector('.spoiler-hint'); const hint_input = view.el.querySelector('.spoiler-hint');
hint_input.value = 'This is the hint'; hint_input.value = 'This is the hint';
view.keyPressed({ view.keyPressed({
...@@ -208,6 +195,7 @@ ...@@ -208,6 +195,7 @@
keyCode: 13 keyCode: 13
}); });
expect(view.onMessageSubmitted).toHaveBeenCalled(); expect(view.onMessageSubmitted).toHaveBeenCalled();
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
/* Test the XML stanza /* Test the XML stanza
* *
...@@ -221,19 +209,19 @@ ...@@ -221,19 +209,19 @@
* <spoiler xmlns="urn:xmpp:spoiler:0">This is the hint</spoiler> * <spoiler xmlns="urn:xmpp:spoiler:0">This is the hint</spoiler>
* </message>" * </message>"
*/ */
var stanza = _converse.connection.send.calls.argsFor(0)[0].tree(); const stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
var spoiler_el = stanza.querySelector('spoiler[xmlns="urn:xmpp:spoiler:0"]'); const spoiler_el = stanza.querySelector('spoiler[xmlns="urn:xmpp:spoiler:0"]');
expect(_.isNull(spoiler_el)).toBeFalsy(); expect(_.isNull(spoiler_el)).toBeFalsy();
expect(spoiler_el.textContent).toBe('This is the hint'); expect(spoiler_el.textContent).toBe('This is the hint');
var body_el = stanza.querySelector('body'); const body_el = stanza.querySelector('body');
expect(body_el.textContent).toBe('This is the spoiler'); expect(body_el.textContent).toBe('This is the spoiler');
/* Test the HTML spoiler message */ /* Test the HTML spoiler message */
expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Max Mustermann'); expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Max Mustermann');
var spoiler_msg_el = view.el.querySelector('.chat-msg__text.spoiler'); const spoiler_msg_el = view.el.querySelector('.chat-msg__text.spoiler');
expect(spoiler_msg_el.textContent).toBe('This is the spoiler'); expect(spoiler_msg_el.textContent).toBe('This is the spoiler');
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy(); expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
...@@ -245,7 +233,6 @@ ...@@ -245,7 +233,6 @@
spoiler_toggle.click(); spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy(); expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
done(); done();
});
})); }));
}); });
})); }));
...@@ -297,7 +297,7 @@ ...@@ -297,7 +297,7 @@
'message': _converse.chatboxes.getMessageBody(stanza), 'message': _converse.chatboxes.getMessageBody(stanza),
'references': this.getReferencesFromStanza(stanza), 'references': this.getReferencesFromStanza(stanza),
'older_versions': older_versions, 'older_versions': older_versions,
'edited': true 'edited': moment().format()
}); });
return true; return true;
} }
...@@ -395,7 +395,7 @@ ...@@ -395,7 +395,7 @@
older_versions.push(message.get('message')); older_versions.push(message.get('message'));
message.save({ message.save({
'correcting': false, 'correcting': false,
'edited': true, 'edited': moment().format(),
'message': attrs.message, 'message': attrs.message,
'older_versions': older_versions, 'older_versions': older_versions,
'references': attrs.references 'references': attrs.references
......
...@@ -17,8 +17,10 @@ ...@@ -17,8 +17,10 @@
const { Backbone, _ } = converse.env; const { Backbone, _ } = converse.env;
const AvatarMixin = { const AvatarMixin = {
renderAvatar () {
const canvas_el = this.el.querySelector('canvas'); renderAvatar (el) {
el = el || this.el;
const canvas_el = el.querySelector('canvas');
if (_.isNull(canvas_el)) { if (_.isNull(canvas_el)) {
return; return;
} }
...@@ -27,6 +29,7 @@ ...@@ -27,6 +29,7 @@
img_src = "data:" + image_type + ";base64," + image, img_src = "data:" + image_type + ";base64," + image,
img = new Image(); img = new Image();
return new Promise((resolve, reject) => {
img.onload = () => { img.onload = () => {
const ctx = canvas_el.getContext('2d'), const ctx = canvas_el.getContext('2d'),
ratio = img.width / img.height; ratio = img.width / img.height;
...@@ -38,8 +41,10 @@ ...@@ -38,8 +41,10 @@
} else { } else {
ctx.drawImage(img, 0, 0, canvas_el.width, canvas_el.height*ratio); ctx.drawImage(img, 0, 0, canvas_el.width, canvas_el.height*ratio);
} }
resolve();
}; };
img.src = img_src; img.src = img_src;
});
}, },
}; };
......
...@@ -312,6 +312,7 @@ ...@@ -312,6 +312,7 @@
this.model.messages.on('add', this.onMessageAdded, this); this.model.messages.on('add', this.onMessageAdded, this);
this.model.messages.on('rendered', this.scrollDown, this); this.model.messages.on('rendered', this.scrollDown, this);
this.model.messages.on('edited', (view) => this.markFollowups(view.el));
this.model.on('show', this.show, this); this.model.on('show', this.show, this);
this.model.on('destroy', this.remove, this); this.model.on('destroy', this.remove, this);
...@@ -673,7 +674,8 @@ ...@@ -673,7 +674,8 @@
if (view.model.get('type') === 'error') { if (view.model.get('type') === 'error') {
const previous_msg_el = this.content.querySelector(`[data-msgid="${view.model.get('msgid')}"]`); const previous_msg_el = this.content.querySelector(`[data-msgid="${view.model.get('msgid')}"]`);
if (previous_msg_el) { if (previous_msg_el) {
return previous_msg_el.insertAdjacentElement('afterend', view.el); previous_msg_el.insertAdjacentElement('afterend', view.el);
return this.trigger('messageInserted', view.el);
} }
} }
const current_msg_date = moment(view.model.get('time')) || moment, const current_msg_date = moment(view.model.get('time')) || moment,
...@@ -692,6 +694,7 @@ ...@@ -692,6 +694,7 @@
previous_msg_el.insertAdjacentElement('afterend', view.el); previous_msg_el.insertAdjacentElement('afterend', view.el);
this.markFollowups(view.el); this.markFollowups(view.el);
} }
return this.trigger('messageInserted', view.el);
}, },
markFollowups (el) { markFollowups (el) {
...@@ -731,7 +734,7 @@ ...@@ -731,7 +734,7 @@
} }
}, },
showMessage (message) { async showMessage (message) {
/* Inserts a chat message into the content area of the chat box. /* Inserts a chat message into the content area of the chat box.
* *
* Will also insert a new day indicator if the message is on a * Will also insert a new day indicator if the message is on a
...@@ -741,6 +744,8 @@ ...@@ -741,6 +744,8 @@
* (Backbone.Model) message: The message object * (Backbone.Model) message: The message object
*/ */
const view = new _converse.MessageView({'model': message}); const view = new _converse.MessageView({'model': message});
await view.render();
this.clearChatStateNotification(message); this.clearChatStateNotification(message);
this.insertMessage(view); this.insertMessage(view);
this.insertDayIndicator(view.el); this.insertDayIndicator(view.el);
......
...@@ -41,8 +41,11 @@ ...@@ -41,8 +41,11 @@
{ __ } = _converse; { __ } = _converse;
_converse.MessageVersionsModal = _converse.BootstrapModal.extend({ _converse.api.settings.update({
'show_images_inline': true
});
_converse.MessageVersionsModal = _converse.BootstrapModal.extend({
toHTML () { toHTML () {
return tpl_message_versions_modal(_.extend( return tpl_message_versions_modal(_.extend(
this.model.toJSON(), { this.model.toJSON(), {
...@@ -61,17 +64,11 @@ ...@@ -61,17 +64,11 @@
if (this.model.vcard) { if (this.model.vcard) {
this.model.vcard.on('change', this.render, this); this.model.vcard.on('change', this.render, this);
} }
this.model.on('change:correcting', this.onMessageCorrection, this); this.model.on('change', this.onChanged, this);
this.model.on('change:message', this.render, this);
this.model.on('change:progress', this.renderFileUploadProgresBar, this);
this.model.on('change:type', this.render, this);
this.model.on('change:upload', this.render, this);
this.model.on('destroy', this.remove, this); this.model.on('destroy', this.remove, this);
this.render();
}, },
render () { async render () {
const is_followup = u.hasClass('chat-msg--followup', this.el);
let msg; let msg;
if (this.model.isOnlyChatStateNotification()) { if (this.model.isOnlyChatStateNotification()) {
this.renderChatStateNotification() this.renderChatStateNotification()
...@@ -80,20 +77,32 @@ ...@@ -80,20 +77,32 @@
} else if (this.model.get('type') === 'error') { } else if (this.model.get('type') === 'error') {
this.renderErrorMessage(); this.renderErrorMessage();
} else { } else {
this.renderChatMessage(); await this.renderChatMessage();
}
if (is_followup) {
u.addClass('chat-msg--followup', this.el);
} }
return this.el; return this.el;
}, },
onMessageCorrection () { async onChanged (item) {
this.render(); // Jot down whether it was edited because the `changed`
if (!this.model.get('correcting') && this.model.changed.message) { // attr gets removed when this.render() gets called further
// down.
const edited = item.changed.edited;
if (this.model.changed.progress) {
return this.renderFileUploadProgresBar();
}
if (_.filter(['correcting', 'message', 'type', 'upload'],
prop => Object.prototype.hasOwnProperty.call(this.model.changed, prop)).length) {
await this.render();
}
if (edited) {
this.onMessageEdited();
}
},
onMessageEdited () {
this.el.addEventListener('animationend', () => u.removeClass('onload', this.el)); this.el.addEventListener('animationend', () => u.removeClass('onload', this.el));
this.model.collection.trigger('edited', this);
u.addClass('onload', this.el); u.addClass('onload', this.el);
}
}, },
replaceElement (msg) { replaceElement (msg) {
...@@ -104,7 +113,7 @@ ...@@ -104,7 +113,7 @@
return this.el; return this.el;
}, },
renderChatMessage () { async renderChatMessage () {
const is_me_message = this.isMeCommand(), const is_me_message = this.isMeCommand(),
moment_time = moment(this.model.get('time')), moment_time = moment(this.model.get('time')),
role = this.model.vcard ? this.model.vcard.get('role') : null, role = this.model.vcard ? this.model.vcard.get('role') : null,
...@@ -148,14 +157,14 @@ ...@@ -148,14 +157,14 @@
_.partial(u.addEmoji, _converse, _) _.partial(u.addEmoji, _converse, _)
)(text); )(text);
} }
u.renderImageURLs(_converse, msg_content).then(() => { if (_converse.show_images_inline) {
this.model.collection.trigger('rendered'); await u.renderImageURLs(_converse, msg_content);
}); }
this.replaceElement(msg);
if (this.model.get('type') !== 'headline') { if (this.model.get('type') !== 'headline') {
this.renderAvatar(); await this.renderAvatar(msg);
} }
this.replaceElement(msg);
this.model.collection.trigger('rendered', this);
}, },
renderErrorMessage () { renderErrorMessage () {
......
...@@ -478,6 +478,7 @@ ...@@ -478,6 +478,7 @@
initialize () { initialize () {
_converse.BootstrapModal.prototype.initialize.apply(this, arguments); _converse.BootstrapModal.prototype.initialize.apply(this, arguments);
this.model.on('change', this.render, this); this.model.on('change', this.render, this);
this.model.occupants.on('add', this.render, this);
this.model.occupants.on('change', this.render, this); this.model.occupants.on('change', this.render, this);
}, },
......
...@@ -71,7 +71,7 @@ ...@@ -71,7 +71,7 @@
'warn': _.get(console, 'log') ? console.log.bind(console) : _.noop 'warn': _.get(console, 'log') ? console.log.bind(console) : _.noop
}, console); }, console);
var isImage = function (url) { const isImage = function (url) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
var img = new Image(); var img = new Image();
var timer = window.setTimeout(function () { var timer = window.setTimeout(function () {
......
...@@ -297,13 +297,15 @@ ...@@ -297,13 +297,15 @@
.c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree(); .c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree();
} }
utils.sendMessage = function (chatboxview, message) { utils.sendMessage = function (view, message) {
chatboxview.el.querySelector('.chat-textarea').value = message; const promise = new Promise((resolve, reject) => view.on('messageInserted', resolve));
chatboxview.keyPressed({ view.el.querySelector('.chat-textarea').value = message;
target: chatboxview.el.querySelector('textarea.chat-textarea'), view.keyPressed({
target: view.el.querySelector('textarea.chat-textarea'),
preventDefault: _.noop, preventDefault: _.noop,
keyCode: 13 keyCode: 13
}); });
return promise;
}; };
return utils; return utils;
})); }));
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