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,61 +58,49 @@ ...@@ -58,61 +58,49 @@
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); const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; let message = '/me is tired';
var message = '/me is tired'; const msg = $msg({
var msg = $msg({ from: sender_jid,
from: sender_jid, to: _converse.connection.jid,
to: _converse.connection.jid, type: 'chat',
type: 'chat', id: (new Date()).getTime()
id: (new Date()).getTime() }).c('body').t(message).up()
}).c('body').t(message).up() .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.chatboxes.onMessage(msg);
view = _converse.chatboxviews.get(sender_jid);
test_utils.waitUntil(function () { _converse.chatboxes.onMessage(msg);
return u.isVisible(view.el); const view = _converse.chatboxviews.get(sender_jid);
}).then(function () { await new Promise((resolve, reject) => view.once('messageInserted', resolve));
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'; await test_utils.sendMessage(view, message);
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');
expect(sizzle('.chat-msg__text:last', view.el).pop().textContent).toBe(' is as well');
return test_utils.waitUntil(() => $(view.el).find('.chat-msg__author:last').text().trim() === '**Max Mustermann'); expect($(view.el).find('.chat-msg:last').hasClass('chat-msg--followup')).toBe(false);
}).then(function () { // Check that /me messages after a normal message don't
expect($(view.el).find('.chat-msg__text:last').text()).toBe(' is as well'); // get the 'chat-msg--followup' class.
expect($(view.el).find('.chat-msg:last').hasClass('chat-msg--followup')).toBe(false); message = 'This a normal message';
await test_utils.sendMessage(view, message);
// Check that /me messages after a normal message don't let message_el = view.el.querySelector('.message:last-child');
// get the 'chat-msg--followup' class. expect(u.hasClass('chat-msg--followup', message_el)).toBeFalsy();
message = 'This a normal message'; message = '/me wrote a 3rd person message';
test_utils.sendMessage(view, message); await test_utils.sendMessage(view, message);
let message_el = view.el.querySelector('.message:last-child'); message_el = view.el.querySelector('.message:last-child');
expect(u.hasClass('chat-msg--followup', message_el)).toBeFalsy(); expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(3);
expect(sizzle('.chat-msg__text:last', view.el).pop().textContent).toBe(' wrote a 3rd person message');
message = '/me wrote a 3rd person message'; expect(u.isVisible(sizzle('.chat-msg__author:last', view.el).pop())).toBeTruthy();
test_utils.sendMessage(view, message); expect(u.hasClass('chat-msg--followup', message_el)).toBeFalsy();
message_el = view.el.querySelector('.message:last-child'); done();
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(u.hasClass('chat-msg--followup', message_el)).toBeFalsy();
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,28 +693,27 @@ ...@@ -705,28 +693,27 @@
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 () { // Check that the notification appears inside the chatbox in the DOM
var view = _converse.chatboxviews.get(sender_jid); let events = view.el.querySelectorAll('.chat-state-notification');
// Check that the notification appears inside the chatbox in the DOM expect(events.length).toBe(1);
let events = view.el.querySelectorAll('.chat-state-notification'); expect(events[0].textContent).toEqual(mock.cur_names[1] + ' is typing');
expect(events.length).toBe(1);
expect(events[0].textContent).toEqual(mock.cur_names[1] + ' is typing');
// Check that it doesn't appear twice // Check that it doesn't appear twice
msg = $msg({ msg = $msg({
from: sender_jid, from: sender_jid,
to: _converse.connection.jid, to: _converse.connection.jid,
type: 'chat', type: 'chat',
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);
events = view.el.querySelectorAll('.chat-state-notification'); await new Promise((resolve, reject) => view.once('messageInserted', resolve));
expect(events.length).toBe(1); events = view.el.querySelectorAll('.chat-state-notification');
expect(events[0].textContent).toEqual(mock.cur_names[1] + ' is typing'); expect(events.length).toBe(1);
done(); 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", it("can be a composing carbon message that this user sent from a different client",
...@@ -841,38 +828,32 @@ ...@@ -841,38 +828,32 @@
})); }));
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; // TODO: only show paused state if the previous state was composing
}, 300) // See XEP-0085 http://xmpp.org/extensions/xep-0085.html#definitions
.then(function () { spyOn(_converse, 'emit');
// TODO: only show paused state if the previous state was composing const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
// See XEP-0085 http://xmpp.org/extensions/xep-0085.html#definitions // <paused> state
spyOn(_converse, 'emit'); var msg = $msg({
var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost'; from: sender_jid,
// <paused> state to: _converse.connection.jid,
var msg = $msg({ type: 'chat',
from: sender_jid, id: (new Date()).getTime()
to: _converse.connection.jid, }).c('body').c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree();
type: 'chat', _converse.chatboxes.onMessage(msg);
id: (new Date()).getTime() expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
}).c('body').c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree(); const view = _converse.chatboxviews.get(sender_jid);
_converse.chatboxes.onMessage(msg); await new Promise((resolve, reject) => view.once('messageInserted', resolve));
expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object)); 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');
test_utils.waitUntil(() => view.model.vcard.get('fullname') === mock.cur_names[1]) expect(event.textContent).toEqual(mock.cur_names[1] + ' has stopped typing');
.then(function () { done();
var view = _converse.chatboxviews.get(sender_jid);
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", 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,29 +1245,28 @@ ...@@ -1267,29 +1245,28 @@
// 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);
expect(_converse.msg_counter).toBe(1); await new Promise((resolve, reject) => view.once('messageInserted', resolve));
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);
// close chatbox and leave converse-chat page again // close chatbox and leave converse-chat page again
view.close(); view.close();
_converse.windowState = 'hidden'; _converse.windowState = 'hidden';
// 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();
});
})); }));
}); });
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -268,224 +268,210 @@ ...@@ -268,224 +268,210 @@
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;
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');
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]);
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" `+
`id="${iq.nodeTree.getAttribute("id")}" `+
`to="upload.montague.tld" `+
`type="get" `+
`xmlns="jabber:client">`+
`<request `+
`content-type="image/jpeg" `+
`filename="my-juliet.jpg" `+
`size="23456" `+
`xmlns="urn:xmpp:http:upload:0"/>`+
`</iq>`);
test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items') var base_url = document.URL.split(window.location.pathname)[0];
.then(() => test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], [])) var message = base_url+"/logo/conversejs-filled.svg";
var stanza = Strophe.xmlHtmlNode(
"<iq from='upload.montague.tld'"+
" id='"+iq.nodeTree.getAttribute('id')+"'"+
" to='dummy@localhost/resource'"+
" type='result'>"+
"<slot xmlns='urn:xmpp:http:upload:0'>"+
" <put url='https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my-juliet.jpg'>"+
" <header name='Authorization'>Basic Base64String==</header>"+
" <header name='Cookie'>foo=bar; user=romeo</header>"+
" </put>"+
" <get url='"+message+"' />"+
"</slot>"+
"</iq>").firstElementChild;
spyOn(XMLHttpRequest.prototype, 'send').and.callFake(function () {
const message = view.model.messages.at(0);
expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
message.set('progress', 0.5);
test_utils.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '0.5')
.then(() => { .then(() => {
test_utils.createContacts(_converse, 'current'); message.set('progress', 1);
_converse.emit('rosterContactsFetched'); test_utils.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '1')
contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
return test_utils.openChatBoxFor(_converse, contact_jid);
}).then(() => { }).then(() => {
var view = _converse.chatboxviews.get(contact_jid); message.save({
var file = { 'upload': _converse.SUCCESS,
'type': 'image/jpeg', 'oob_url': message.get('get'),
'size': '23456' , 'message': message.get('get')
'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 () {
var iq = IQ_stanzas.pop();
expect(iq.toLocaleString()).toBe(
`<iq from="dummy@localhost/resource" `+
`id="${iq.nodeTree.getAttribute("id")}" `+
`to="upload.montague.tld" `+
`type="get" `+
`xmlns="jabber:client">`+
`<request `+
`content-type="image/jpeg" `+
`filename="my-juliet.jpg" `+
`size="23456" `+
`xmlns="urn:xmpp:http:upload:0"/>`+
`</iq>`);
var base_url = document.URL.split(window.location.pathname)[0];
var message = base_url+"/logo/conversejs-filled.svg";
var stanza = Strophe.xmlHtmlNode(
"<iq from='upload.montague.tld'"+
" id='"+iq.nodeTree.getAttribute('id')+"'"+
" to='dummy@localhost/resource'"+
" type='result'>"+
"<slot xmlns='urn:xmpp:http:upload:0'>"+
" <put url='https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my-juliet.jpg'>"+
" <header name='Authorization'>Basic Base64String==</header>"+
" <header name='Cookie'>foo=bar; user=romeo</header>"+
" </put>"+
" <get url='"+message+"' />"+
"</slot>"+
"</iq>").firstElementChild;
spyOn(XMLHttpRequest.prototype, 'send').and.callFake(function () {
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');
message.set('progress', 1);
expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('1');
message.save({
'upload': _converse.SUCCESS,
'oob_url': message.get('get'),
'message': message.get('get')
});
});
var sent_stanza;
spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
sent_stanza = stanza;
});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(function () {
return sent_stanza;
}, 1000).then(function () {
expect(sent_stanza.toLocaleString()).toBe(
`<message from="dummy@localhost/resource" `+
`id="${sent_stanza.nodeTree.getAttribute("id")}" `+
`to="irini.vlastuin@localhost" `+
`type="chat" `+
`xmlns="jabber:client">`+
`<body>${message}</body>`+
`<active xmlns="http://jabber.org/protocol/chatstates"/>`+
`<x xmlns="jabber:x:oob">`+
`<url>${message}</url>`+
`</x>`+
`</message>`);
return test_utils.waitUntil(() => view.el.querySelector('.chat-image'), 1000);
}).then(function () {
// Check that the image renders
expect(view.el.querySelector('.chat-msg .chat-msg__media').innerHTML.trim()).toEqual(
`<!-- src/templates/image.html -->\n`+
`<a href="${window.location.origin}/logo/conversejs-filled.svg" target="_blank" rel="noopener">`+
`<img class="chat-image img-thumbnail" src="${window.location.origin}/logo/conversejs-filled.svg">`+
`</a>`);
XMLHttpRequest.prototype.send = send_backup;
done();
});
}); });
return new Promise((resolve, reject) => view.model.messages.once('rendered', resolve));
}); });
}); });
let sent_stanza;
spyOn(_converse.connection, 'send').and.callFake(stanza => (sent_stanza = stanza));
_converse.connection._dataRecv(test_utils.createRequest(stanza));
await test_utils.waitUntil(() => sent_stanza, 1000);
expect(sent_stanza.toLocaleString()).toBe(
`<message from="dummy@localhost/resource" `+
`id="${sent_stanza.nodeTree.getAttribute("id")}" `+
`to="irini.vlastuin@localhost" `+
`type="chat" `+
`xmlns="jabber:client">`+
`<body>${message}</body>`+
`<active xmlns="http://jabber.org/protocol/chatstates"/>`+
`<x xmlns="jabber:x:oob">`+
`<url>${message}</url>`+
`</x>`+
`</message>`);
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`+
`<a href="${window.location.origin}/logo/conversejs-filled.svg" target="_blank" rel="noopener">`+
`<img class="chat-image img-thumbnail" src="${window.location.origin}/logo/conversejs-filled.svg">`+
`</a>`);
XMLHttpRequest.prototype.send = send_backup;
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');
const send_backup = XMLHttpRequest.prototype.send;
const IQ_stanzas = _converse.connection.IQ_stanzas;
var send_backup = XMLHttpRequest.prototype.send; await test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items');
var IQ_stanzas = _converse.connection.IQ_stanzas; await test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], []);
await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items').then(function () { const view = _converse.chatboxviews.get('lounge@localhost');
test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], []).then(function () { const file = {
test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () { 'type': 'image/jpeg',
var view = _converse.chatboxviews.get('lounge@localhost'); 'size': '23456' ,
var file = { 'lastModifiedDate': "",
'type': 'image/jpeg', 'name': "my-juliet.jpg"
'size': '23456' , };
'lastModifiedDate': "", view.model.sendFiles([file]);
'name': "my-juliet.jpg" await new Promise((resolve, reject) => view.once('messageInserted', resolve));
};
view.model.sendFiles([file]); await test_utils.waitUntil(() => _.filter(IQ_stanzas, iq => iq.nodeTree.querySelector('iq[to="upload.montague.tld"] request')).length);
var iq = IQ_stanzas.pop();
return test_utils.waitUntil(function () { expect(iq.toLocaleString()).toBe(
return _.filter(IQ_stanzas, function (iq) { `<iq from="dummy@localhost/resource" `+
return iq.nodeTree.querySelector('iq[to="upload.montague.tld"] request'); `id="${iq.nodeTree.getAttribute("id")}" `+
}).length > 0; `to="upload.montague.tld" `+
}).then(function () { `type="get" `+
var iq = IQ_stanzas.pop(); `xmlns="jabber:client">`+
expect(iq.toLocaleString()).toBe( `<request `+
`<iq from="dummy@localhost/resource" `+ `content-type="image/jpeg" `+
`id="${iq.nodeTree.getAttribute("id")}" `+ `filename="my-juliet.jpg" `+
`to="upload.montague.tld" `+ `size="23456" `+
`type="get" `+ `xmlns="urn:xmpp:http:upload:0"/>`+
`xmlns="jabber:client">`+ `</iq>`);
`<request `+
`content-type="image/jpeg" `+ var base_url = document.URL.split(window.location.pathname)[0];
`filename="my-juliet.jpg" `+ var message = base_url+"/logo/conversejs-filled.svg";
`size="23456" `+
`xmlns="urn:xmpp:http:upload:0"/>`+ var stanza = Strophe.xmlHtmlNode(
`</iq>`); "<iq from='upload.montague.tld'"+
" id='"+iq.nodeTree.getAttribute('id')+"'"+
var base_url = document.URL.split(window.location.pathname)[0]; " to='dummy@localhost/resource'"+
var message = base_url+"/logo/conversejs-filled.svg"; " type='result'>"+
"<slot xmlns='urn:xmpp:http:upload:0'>"+
var stanza = Strophe.xmlHtmlNode( " <put url='https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my-juliet.jpg'>"+
"<iq from='upload.montague.tld'"+ " <header name='Authorization'>Basic Base64String==</header>"+
" id='"+iq.nodeTree.getAttribute('id')+"'"+ " <header name='Cookie'>foo=bar; user=romeo</header>"+
" to='dummy@localhost/resource'"+ " </put>"+
" type='result'>"+ " <get url='"+message+"' />"+
"<slot xmlns='urn:xmpp:http:upload:0'>"+ "</slot>"+
" <put url='https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my-juliet.jpg'>"+ "</iq>").firstElementChild;
" <header name='Authorization'>Basic Base64String==</header>"+
" <header name='Cookie'>foo=bar; user=romeo</header>"+ spyOn(XMLHttpRequest.prototype, 'send').and.callFake(function () {
" </put>"+ const message = view.model.messages.at(0);
" <get url='"+message+"' />"+ expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
"</slot>"+ message.set('progress', 0.5);
"</iq>").firstElementChild; test_utils.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '0.5')
.then(() => {
spyOn(XMLHttpRequest.prototype, 'send').and.callFake(function () { message.set('progress', 1);
const message = view.model.messages.at(0); test_utils.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '1')
expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0'); }).then(() => {
message.set('progress', 0.5); message.save({
expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0.5'); 'upload': _converse.SUCCESS,
message.set('progress', 1); 'oob_url': message.get('get'),
expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('1'); 'message': message.get('get')
message.save({
'upload': _converse.SUCCESS,
'oob_url': message.get('get'),
'message': message.get('get')
});
});
var sent_stanza;
spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
sent_stanza = stanza;
});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => sent_stanza, 1000).then(function () {
expect(sent_stanza.toLocaleString()).toBe(
`<message `+
`from="dummy@localhost/resource" `+
`id="${sent_stanza.nodeTree.getAttribute("id")}" `+
`to="lounge@localhost" `+
`type="groupchat" `+
`xmlns="jabber:client">`+
`<body>${message}</body>`+
`<active xmlns="http://jabber.org/protocol/chatstates"/>`+
`<x xmlns="jabber:x:oob">`+
`<url>${message}</url>`+
`</x>`+
`</message>`);
return test_utils.waitUntil(() => view.el.querySelector('.chat-image'), 1000);
}).then(function () {
// Check that the image renders
expect(view.el.querySelector('.chat-msg .chat-msg__media').innerHTML.trim()).toEqual(
`<!-- src/templates/image.html -->\n`+
`<a href="${window.location.origin}/logo/conversejs-filled.svg" target="_blank" rel="noopener">`+
`<img class="chat-image img-thumbnail" src="${window.location.origin}/logo/conversejs-filled.svg">`+
`</a>`);
XMLHttpRequest.prototype.send = send_backup;
done();
});
});
});
}); });
return new Promise((resolve, reject) => view.model.messages.once('rendered', resolve));
}); });
}); });
let sent_stanza;
spyOn(_converse.connection, 'send').and.callFake(stanza => (sent_stanza = stanza));
_converse.connection._dataRecv(test_utils.createRequest(stanza));
await test_utils.waitUntil(() => sent_stanza, 1000);
expect(sent_stanza.toLocaleString()).toBe(
`<message `+
`from="dummy@localhost/resource" `+
`id="${sent_stanza.nodeTree.getAttribute("id")}" `+
`to="lounge@localhost" `+
`type="groupchat" `+
`xmlns="jabber:client">`+
`<body>${message}</body>`+
`<active xmlns="http://jabber.org/protocol/chatstates"/>`+
`<x xmlns="jabber:x:oob">`+
`<url>${message}</url>`+
`</x>`+
`</message>`);
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`+
`<a href="${window.location.origin}/logo/conversejs-filled.svg" target="_blank" rel="noopener">`+
`<img class="chat-image img-thumbnail" src="${window.location.origin}/logo/conversejs-filled.svg">`+
`</a>`);
XMLHttpRequest.prototype.send = send_backup;
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,82 +603,78 @@ ...@@ -617,82 +603,78 @@
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], []);
test_utils.createContacts(_converse, 'current');
_converse.emit('rosterContactsFetched');
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]);
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" `+
`id="${iq.nodeTree.getAttribute("id")}" `+
`to="upload.montague.tld" `+
`type="get" `+
`xmlns="jabber:client">`+
`<request `+
`content-type="image/jpeg" `+
`filename="my-juliet.jpg" `+
`size="23456" `+
`xmlns="urn:xmpp:http:upload:0"/>`+
`</iq>`);
const base_url = document.URL.split(window.location.pathname)[0];
const message = base_url+"/logo/conversejs-filled.svg";
const stanza = Strophe.xmlHtmlNode(
"<iq from='upload.montague.tld'"+
" id='"+iq.nodeTree.getAttribute('id')+"'"+
" to='dummy@localhost/resource'"+
" type='result'>"+
"<slot xmlns='urn:xmpp:http:upload:0'>"+
" <put url='https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my-juliet.jpg'>"+
" <header name='Authorization'>Basic Base64String==</header>"+
" <header name='Cookie'>foo=bar; user=romeo</header>"+
" </put>"+
" <get url='"+message+"' />"+
"</slot>"+
"</iq>").firstElementChild;
spyOn(XMLHttpRequest.prototype, 'send').and.callFake(function () {
const message = view.model.messages.at(0);
expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0');
message.set('progress', 0.5);
test_utils.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '0.5')
.then(() => { .then(() => {
test_utils.createContacts(_converse, 'current'); message.set('progress', 1);
_converse.emit('rosterContactsFetched'); test_utils.waitUntil(() => view.el.querySelector('.chat-content progress').getAttribute('value') === '1')
contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
return test_utils.openChatBoxFor(_converse, contact_jid);
}).then(() => { }).then(() => {
view = _converse.chatboxviews.get(contact_jid); expect(view.el.querySelector('.chat-content .chat-msg__text').textContent).toBe('Uploading file: my-juliet.jpg, 22.91 KB');
const file = { done();
'type': 'image/jpeg',
'size': '23456' ,
'lastModifiedDate': "",
'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 () {
const iq = IQ_stanzas.pop();
expect(iq.toLocaleString()).toBe(
`<iq from="dummy@localhost/resource" `+
`id="${iq.nodeTree.getAttribute("id")}" `+
`to="upload.montague.tld" `+
`type="get" `+
`xmlns="jabber:client">`+
`<request `+
`content-type="image/jpeg" `+
`filename="my-juliet.jpg" `+
`size="23456" `+
`xmlns="urn:xmpp:http:upload:0"/>`+
`</iq>`);
const base_url = document.URL.split(window.location.pathname)[0];
const message = base_url+"/logo/conversejs-filled.svg";
const stanza = Strophe.xmlHtmlNode(
"<iq from='upload.montague.tld'"+
" id='"+iq.nodeTree.getAttribute('id')+"'"+
" to='dummy@localhost/resource'"+
" type='result'>"+
"<slot xmlns='urn:xmpp:http:upload:0'>"+
" <put url='https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my-juliet.jpg'>"+
" <header name='Authorization'>Basic Base64String==</header>"+
" <header name='Cookie'>foo=bar; user=romeo</header>"+
" </put>"+
" <get url='"+message+"' />"+
"</slot>"+
"</iq>").firstElementChild;
spyOn(XMLHttpRequest.prototype, 'send').and.callFake(function () {
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');
message.set('progress', 1);
expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('1');
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;
});
_converse.connection._dataRecv(test_utils.createRequest(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,113 +95,106 @@ ...@@ -95,113 +95,106 @@
{ 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')); const view = _converse.chatboxviews.get(room_jid);
}).then(last_stanza => { const IQ_id = last_stanza.getAttribute('id');
view = _converse.chatboxviews.get(room_jid); const features_stanza = $iq({
const IQ_id = last_stanza.getAttribute('id'); 'from': 'coven@chat.shakespeare.lit',
const features_stanza = $iq({ 'id': IQ_id,
'from': 'coven@chat.shakespeare.lit', 'to': 'dummy@localhost/desktop',
'id': IQ_id, 'type': 'result'
'to': 'dummy@localhost/desktop', })
'type': 'result' .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'})
}) .c('identity', {
.c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'}) 'category': 'conference',
.c('identity', { 'name': 'A Dark Cave',
'category': 'conference', 'type': 'text'
'name': 'A Dark Cave',
'type': 'text'
}).up()
.c('feature', {'var': 'http://jabber.org/protocol/muc'}).up()
.c('feature', {'var': 'muc_passwordprotected'}).up()
.c('feature', {'var': 'muc_hidden'}).up()
.c('feature', {'var': 'muc_temporary'}).up()
.c('feature', {'var': 'muc_open'}).up()
.c('feature', {'var': 'muc_unmoderated'}).up()
.c('feature', {'var': 'muc_nonanonymous'}).up()
.c('feature', {'var': 'urn:xmpp:mam:0'}).up()
.c('x', { 'xmlns':'jabber:x:data', 'type':'result'})
.c('field', {'var':'FORM_TYPE', 'type':'hidden'})
.c('value').t('http://jabber.org/protocol/muc#roominfo').up().up()
.c('field', {'type':'text-single', 'var':'muc#roominfo_description', 'label':'Description'})
.c('value').t('This is the description').up().up()
.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({
to: _converse.connection.jid,
from: 'coven@chat.shakespeare.lit/some1',
id: 'DC352437-C019-40EC-B590-AF29E879AF97'
}).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
.c('item').attrs({
affiliation: 'member',
jid: _converse.bare_jid,
role: 'participant'
}).up() }).up()
.c('status').attrs({code:'110'}); .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up()
_converse.connection._dataRecv(test_utils.createRequest(presence)); .c('feature', {'var': 'muc_passwordprotected'}).up()
.c('feature', {'var': 'muc_hidden'}).up()
.c('feature', {'var': 'muc_temporary'}).up()
.c('feature', {'var': 'muc_open'}).up()
.c('feature', {'var': 'muc_unmoderated'}).up()
.c('feature', {'var': 'muc_nonanonymous'}).up()
.c('feature', {'var': 'urn:xmpp:mam:0'}).up()
.c('x', { 'xmlns':'jabber:x:data', 'type':'result'})
.c('field', {'var':'FORM_TYPE', 'type':'hidden'})
.c('value').t('http://jabber.org/protocol/muc#roominfo').up().up()
.c('field', {'type':'text-single', 'var':'muc#roominfo_description', 'label':'Description'})
.c('value').t('This is the description').up().up()
.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));
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'
}).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
.c('item').attrs({
affiliation: 'member',
jid: _converse.bare_jid,
role: 'participant'
}).up()
.c('status').attrs({code:'110'});
_converse.connection._dataRecv(test_utils.createRequest(presence));
const room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); const room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
expect(room_els.length).toBe(1); expect(room_els.length).toBe(1);
const info_el = _converse.rooms_list_view.el.querySelector(".room-info"); const info_el = _converse.rooms_list_view.el.querySelector(".room-info");
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(() => { let els = modal.el.querySelectorAll('p.room-info');
const modal = view.model.room_details_modal; expect(els[0].textContent).toBe("Name: A Dark Cave")
let els = modal.el.querySelectorAll('p.room-info'); expect(els[1].textContent).toBe("Groupchat address (JID): coven@chat.shakespeare.lit")
expect(els[0].textContent).toBe("Name: A Dark Cave") expect(els[2].textContent).toBe("Description: This is the description")
expect(els[1].textContent).toBe("Groupchat address (JID): coven@chat.shakespeare.lit") expect(els[3].textContent).toBe("Online users: 1")
expect(els[2].textContent).toBe("Description: This is the description") const features_list = modal.el.querySelector('.features-list');
expect(els[3].textContent).toBe("Online users: 1") expect(features_list.textContent.replace(/(\n|\s{2,})/g, '')).toBe(
const features_list = modal.el.querySelector('.features-list'); 'Password protected - This groupchat requires a password before entry'+
expect(features_list.textContent.replace(/(\n|\s{2,})/g, '')).toBe( 'Hidden - This groupchat is not publicly searchable'+
'Password protected - This groupchat requires a password before entry'+ 'Open - Anyone can join this groupchat'+
'Hidden - This groupchat is not publicly searchable'+ 'Temporary - This groupchat will disappear once the last person leaves'+
'Open - Anyone can join this groupchat'+ 'Not anonymous - All other groupchat participants can see your XMPP username'+
'Temporary - This groupchat will disappear once the last person leaves'+ 'Not moderated - Participants entering this groupchat can write right away'
'Not anonymous - All other groupchat participants can see your XMPP username'+ );
'Not moderated - Participants entering this groupchat can write right away' presence = $pres({
); to: 'dummy@localhost/_converse.js-29092160',
const presence = $pres({ from: 'coven@chat.shakespeare.lit/newguy'
to: 'dummy@localhost/_converse.js-29092160', })
from: 'coven@chat.shakespeare.lit/newguy' .c('x', {xmlns: Strophe.NS.MUC_USER})
}) .c('item', {
.c('x', {xmlns: Strophe.NS.MUC_USER}) 'affiliation': 'none',
.c('item', { 'jid': 'newguy@localhost/_converse.js-290929789',
'affiliation': 'none', 'role': 'participant'
'jid': 'newguy@localhost/_converse.js-290929789', });
'role': 'participant' _converse.connection._dataRecv(test_utils.createRequest(presence));
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
els = modal.el.querySelectorAll('p.room-info'); els = modal.el.querySelectorAll('p.room-info');
expect(els[3].textContent).toBe("Online users: 2") expect(els[3].textContent).toBe("Online users: 2")
view.model.set({'subject': {'author': 'someone', 'text': 'Hatching dark plots'}}); view.model.set({'subject': {'author': 'someone', 'text': 'Hatching dark plots'}});
els = modal.el.querySelectorAll('p.room-info'); 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")
expect(els[2].textContent).toBe("Description: This is the description") expect(els[2].textContent).toBe("Description: This is the description")
expect(els[3].textContent).toBe("Topic: Hatching dark plots") expect(els[3].textContent).toBe("Topic: Hatching dark plots")
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') expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Max Frankfurter');
.then(function () { const message_content = view.el.querySelector('.chat-msg__text');
expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Max Frankfurter'); expect(message_content.textContent).toBe(spoiler);
const spoiler_hint_el = view.el.querySelector('.spoiler-hint');
var message_content = view.el.querySelector('.chat-msg__text'); expect(spoiler_hint_el.textContent).toBe(spoiler_hint);
expect(message_content.textContent).toBe(spoiler); done();
var spoiler_hint_el = view.el.querySelector('.spoiler-hint');
expect(spoiler_hint_el.textContent).toBe(spoiler_hint);
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') expect(_.includes(view.el.querySelector('.chat-msg__author').textContent, 'Max Frankfurter')).toBeTruthy();
.then(function () { const message_content = view.el.querySelector('.chat-msg__text');
expect(_.includes(view.el.querySelector('.chat-msg__author').textContent, 'Max Frankfurter')).toBeTruthy(); expect(message_content.textContent).toBe(spoiler);
const spoiler_hint_el = view.el.querySelector('.spoiler-hint');
var message_content = view.el.querySelector('.chat-msg__text'); expect(spoiler_hint_el.textContent).toBe('');
expect(message_content.textContent).toBe(spoiler); done();
var 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", 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,147 +94,145 @@ ...@@ -105,147 +94,145 @@
// 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');
let spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
var spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler'); spoiler_toggle.click();
spoiler_toggle.click();
const textarea = view.el.querySelector('.chat-textarea');
var 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, preventDefault: _.noop,
preventDefault: _.noop, 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
* *
* <message from="dummy@localhost/resource" * <message from="dummy@localhost/resource"
* to="max.frankfurter@localhost" * to="max.frankfurter@localhost"
* type="chat" * type="chat"
* id="4547c38b-d98b-45a5-8f44-b4004dbc335e" * id="4547c38b-d98b-45a5-8f44-b4004dbc335e"
* xmlns="jabber:client"> * xmlns="jabber:client">
* <body>This is the spoiler</body> * <body>This is the spoiler</body>
* <active xmlns="http://jabber.org/protocol/chatstates"/> * <active xmlns="http://jabber.org/protocol/chatstates"/>
* <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();
spoiler_toggle = view.el.querySelector('.spoiler-toggle'); spoiler_toggle = view.el.querySelector('.spoiler-toggle');
expect(spoiler_toggle.textContent).toBe('Show more'); expect(spoiler_toggle.textContent).toBe('Show more');
spoiler_toggle.click(); spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeFalsy(); expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeFalsy();
expect(spoiler_toggle.textContent).toBe('Show less'); expect(spoiler_toggle.textContent).toBe('Show less');
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');
const textarea = view.el.querySelector('.chat-textarea');
var textarea = view.el.querySelector('.chat-textarea'); textarea.value = 'This is the spoiler';
textarea.value = 'This is the spoiler'; const hint_input = view.el.querySelector('.spoiler-hint');
var 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({ target: textarea,
target: textarea, preventDefault: _.noop,
preventDefault: _.noop, keyCode: 13
keyCode: 13
});
expect(view.onMessageSubmitted).toHaveBeenCalled();
/* Test the XML stanza
*
* <message from="dummy@localhost/resource"
* to="max.frankfurter@localhost"
* type="chat"
* id="4547c38b-d98b-45a5-8f44-b4004dbc335e"
* xmlns="jabber:client">
* <body>This is the spoiler</body>
* <active xmlns="http://jabber.org/protocol/chatstates"/>
* <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"]');
expect(_.isNull(spoiler_el)).toBeFalsy();
expect(spoiler_el.textContent).toBe('This is the hint');
var 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');
expect(spoiler_msg_el.textContent).toBe('This is the spoiler');
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
spoiler_toggle = view.el.querySelector('.spoiler-toggle');
expect(spoiler_toggle.textContent).toBe('Show more');
spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeFalsy();
expect(spoiler_toggle.textContent).toBe('Show less');
spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
done();
}); });
expect(view.onMessageSubmitted).toHaveBeenCalled();
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
/* Test the XML stanza
*
* <message from="dummy@localhost/resource"
* to="max.frankfurter@localhost"
* type="chat"
* id="4547c38b-d98b-45a5-8f44-b4004dbc335e"
* xmlns="jabber:client">
* <body>This is the spoiler</body>
* <active xmlns="http://jabber.org/protocol/chatstates"/>
* <spoiler xmlns="urn:xmpp:spoiler:0">This is the hint</spoiler>
* </message>"
*/
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');
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');
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();
spoiler_toggle = view.el.querySelector('.spoiler-toggle');
expect(spoiler_toggle.textContent).toBe('Show more');
spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeFalsy();
expect(spoiler_toggle.textContent).toBe('Show less');
spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
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,19 +29,22 @@ ...@@ -27,19 +29,22 @@
img_src = "data:" + image_type + ";base64," + image, img_src = "data:" + image_type + ";base64," + image,
img = new Image(); img = new Image();
img.onload = () => { return new Promise((resolve, reject) => {
const ctx = canvas_el.getContext('2d'), img.onload = () => {
ratio = img.width / img.height; const ctx = canvas_el.getContext('2d'),
ctx.clearRect(0, 0, canvas_el.width, canvas_el.height); ratio = img.width / img.height;
if (ratio < 1) { ctx.clearRect(0, 0, canvas_el.width, canvas_el.height);
const scaled_img_with = canvas_el.width*ratio, if (ratio < 1) {
x = Math.floor((canvas_el.width-scaled_img_with)/2); const scaled_img_with = canvas_el.width*ratio,
ctx.drawImage(img, x, 0, scaled_img_with, canvas_el.height); x = Math.floor((canvas_el.width-scaled_img_with)/2);
} else { ctx.drawImage(img, x, 0, scaled_img_with, canvas_el.height);
ctx.drawImage(img, 0, 0, canvas_el.width, canvas_el.height*ratio); } else {
} ctx.drawImage(img, 0, 0, canvas_el.width, canvas_el.height*ratio);
}; }
img.src = img_src; resolve();
};
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,22 +77,34 @@ ...@@ -80,22 +77,34 @@
} 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
this.el.addEventListener('animationend', () => u.removeClass('onload', this.el)); // down.
u.addClass('onload', this.el); 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) { replaceElement (msg) {
if (!_.isNil(this.el.parentElement)) { if (!_.isNil(this.el.parentElement)) {
this.el.parentElement.replaceChild(msg, this.el); this.el.parentElement.replaceChild(msg, this.el);
...@@ -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