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