Render converse-chat-content declaratively

This commit is contained in:
JC Brand 2021-02-08 11:27:42 +01:00
parent 18e48be5c4
commit 790caf9f5a
22 changed files with 212 additions and 242 deletions

View File

@ -280,7 +280,7 @@ describe("Chatboxes", function () {
keyCode: 13 // Enter keyCode: 13 // Enter
}; };
view.onKeyDown(ev); view.onKeyDown(ev);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
view.onKeyUp(ev); view.onKeyUp(ev);
expect(counter.textContent).toBe('200'); expect(counter.textContent).toBe('200');

View File

@ -27,7 +27,7 @@ describe("A Chat Message", function () {
preventDefault: function preventDefault () {}, preventDefault: function preventDefault () {},
keyCode: 13 // Enter keyCode: 13 // Enter
}); });
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
expect(view.querySelectorAll('.chat-msg').length).toBe(1); expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.querySelector('.chat-msg__text').textContent) expect(view.querySelector('.chat-msg__text').textContent)
.toBe('But soft, what light through yonder airlock breaks?'); .toBe('But soft, what light through yonder airlock breaks?');
@ -44,14 +44,15 @@ describe("A Chat Message", function () {
await u.waitUntil(() => u.hasClass('correcting', view.querySelector('.chat-msg')), 500); await u.waitUntil(() => u.hasClass('correcting', view.querySelector('.chat-msg')), 500);
spyOn(_converse.connection, 'send'); spyOn(_converse.connection, 'send');
textarea.value = 'But soft, what light through yonder window breaks?'; let new_text = 'But soft, what light through yonder window breaks?';
textarea.value = new_text;
view.onKeyDown({ view.onKeyDown({
target: textarea, target: textarea,
preventDefault: function preventDefault () {}, preventDefault: function preventDefault () {},
keyCode: 13 // Enter keyCode: 13 // Enter
}); });
expect(_converse.connection.send).toHaveBeenCalled(); expect(_converse.connection.send).toHaveBeenCalled();
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelector('.chat-msg__text').textContent.replace(/<!---->/g, '') === new_text);
const msg = _converse.connection.send.calls.all()[0].args[0]; const msg = _converse.connection.send.calls.all()[0].args[0];
expect(msg.toLocaleString()) expect(msg.toLocaleString())
@ -97,13 +98,15 @@ describe("A Chat Message", function () {
expect(view.querySelectorAll('.chat-msg').length).toBe(1); expect(view.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => (u.hasClass('correcting', view.querySelector('.chat-msg')) === false), 500); await u.waitUntil(() => (u.hasClass('correcting', view.querySelector('.chat-msg')) === false), 500);
textarea.value = 'It is the east, and Juliet is the one.'; new_text = 'It is the east, and Juliet is the one.';
textarea.value = new_text;
view.onKeyDown({ view.onKeyDown({
target: textarea, target: textarea,
preventDefault: function preventDefault () {}, preventDefault: function preventDefault () {},
keyCode: 13 // Enter keyCode: 13 // Enter
}); });
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => Array.from(view.querySelectorAll('.chat-msg__text'))
.filter(m => m.textContent.replace(/<!---->/g, '') === new_text).length);
expect(view.querySelectorAll('.chat-msg').length).toBe(2); expect(view.querySelectorAll('.chat-msg').length).toBe(2);
textarea.value = 'Arise, fair sun, and kill the envious moon'; textarea.value = 'Arise, fair sun, and kill the envious moon';
@ -112,18 +115,17 @@ describe("A Chat Message", function () {
preventDefault: function preventDefault () {}, preventDefault: function preventDefault () {},
keyCode: 13 // Enter keyCode: 13 // Enter
}); });
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 3);
expect(view.querySelectorAll('.chat-msg').length).toBe(3);
view.onKeyDown({ view.onKeyDown({
target: textarea, target: textarea,
keyCode: 38 // Up arrow keyCode: 38 // Up arrow
}); });
expect(textarea.value).toBe('Arise, fair sun, and kill the envious moon'); expect(textarea.value).toBe('Arise, fair sun, and kill the envious moon');
await u.waitUntil(() => view.model.messages.at(2).get('correcting') === true);
expect(view.model.messages.at(0).get('correcting')).toBeFalsy(); expect(view.model.messages.at(0).get('correcting')).toBeFalsy();
expect(view.model.messages.at(1).get('correcting')).toBeFalsy(); expect(view.model.messages.at(1).get('correcting')).toBeFalsy();
expect(view.model.messages.at(2).get('correcting')).toBe(true); await u.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg:last', view.el).pop()), 750);
await u.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg:last', view.el).pop()), 500);
textarea.selectionEnd = 0; // Happens by pressing up, textarea.selectionEnd = 0; // Happens by pressing up,
// but for some reason not in tests, so we set it manually. // but for some reason not in tests, so we set it manually.
@ -143,7 +145,6 @@ describe("A Chat Message", function () {
preventDefault: function preventDefault () {}, preventDefault: function preventDefault () {},
keyCode: 13 // Enter keyCode: 13 // Enter
}); });
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => textarea.value === ''); await u.waitUntil(() => textarea.value === '');
const messages = view.querySelectorAll('.chat-msg'); const messages = view.querySelectorAll('.chat-msg');
expect(messages.length).toBe(3); expect(messages.length).toBe(3);
@ -177,12 +178,12 @@ describe("A Chat Message", function () {
preventDefault: function preventDefault () {}, preventDefault: function preventDefault () {},
keyCode: 13 // Enter keyCode: 13 // Enter
}); });
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
expect(view.querySelectorAll('.chat-msg').length).toBe(1); expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.querySelector('.chat-msg__text').textContent) expect(view.querySelector('.chat-msg__text').textContent)
.toBe('But soft, what light through yonder airlock breaks?'); .toBe('But soft, what light through yonder airlock breaks?');
expect(textarea.value).toBe(''); await u.waitUntil(() => textarea.value === '');
const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'}); const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'});
await u.waitUntil(() => view.querySelectorAll('.chat-msg .chat-msg__action').length === 2); await u.waitUntil(() => view.querySelectorAll('.chat-msg .chat-msg__action').length === 2);
@ -546,14 +547,16 @@ describe("A Groupchat Message", function () {
await u.waitUntil(() => u.hasClass('correcting', view.querySelector('.chat-msg'))); await u.waitUntil(() => u.hasClass('correcting', view.querySelector('.chat-msg')));
spyOn(_converse.connection, 'send'); spyOn(_converse.connection, 'send');
textarea.value = 'But soft, what light through yonder window breaks?'; const new_text = 'But soft, what light through yonder window breaks?'
textarea.value = new_text;
view.onKeyDown({ view.onKeyDown({
target: textarea, target: textarea,
preventDefault: function preventDefault () {}, preventDefault: function preventDefault () {},
keyCode: 13 // Enter keyCode: 13 // Enter
}); });
expect(_converse.connection.send).toHaveBeenCalled(); expect(_converse.connection.send).toHaveBeenCalled();
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => Array.from(view.querySelectorAll('.chat-msg__text'))
.filter(m => m.textContent.replace(/<!---->/g, '') === new_text).length);
const msg = _converse.connection.send.calls.all()[0].args[0]; const msg = _converse.connection.send.calls.all()[0].args[0];
expect(msg.toLocaleString()) expect(msg.toLocaleString())
@ -586,7 +589,7 @@ describe("A Groupchat Message", function () {
'to': 'romeo@montague.lit', 'to': 'romeo@montague.lit',
'type': 'groupchat' 'type': 'groupchat'
}).c('body').t('Hello world').tree()); }).c('body').t('Hello world').tree());
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 2);
expect(view.querySelectorAll('.chat-msg').length).toBe(2); expect(view.querySelectorAll('.chat-msg').length).toBe(2);
// Test that pressing the down arrow cancels message correction // Test that pressing the down arrow cancels message correction

View File

@ -263,7 +263,7 @@ describe("Emojis", function () {
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()); .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
await new Promise(resolve => _converse.on('chatBoxViewInitialized', resolve)); await new Promise(resolve => _converse.on('chatBoxViewInitialized', resolve));
const view = _converse.api.chatviews.get(sender_jid); const view = _converse.api.chatviews.get(sender_jid);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
await u.waitUntil(() => u.hasClass('chat-msg__text--larger', view.content.querySelector('.chat-msg__text'))); await u.waitUntil(() => u.hasClass('chat-msg__text--larger', view.content.querySelector('.chat-msg__text')));
_converse.handleMessageStanza($msg({ _converse.handleMessageStanza($msg({
@ -273,7 +273,7 @@ describe("Emojis", function () {
'id': _converse.connection.getUniqueId() 'id': _converse.connection.getUniqueId()
}).c('body').t('😇 Hello world! 😇 😇').up() }).c('body').t('😇 Hello world! 😇 😇').up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()); .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 2);
let sel = '.message:last-child .chat-msg__text'; let sel = '.message:last-child .chat-msg__text';
await u.waitUntil(() => u.hasClass('chat-msg__text--larger', view.content.querySelector(sel))); await u.waitUntil(() => u.hasClass('chat-msg__text--larger', view.content.querySelector(sel)));
@ -287,8 +287,7 @@ describe("Emojis", function () {
preventDefault: function preventDefault () {}, preventDefault: function preventDefault () {},
keyCode: 13 // Enter keyCode: 13 // Enter
}); });
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 3);
expect(view.querySelectorAll('.chat-msg').length).toBe(3);
const last_msg_sel = 'converse-chat-message:last-child .chat-msg__text'; const last_msg_sel = 'converse-chat-message:last-child .chat-msg__text';
await u.waitUntil(() => view.content.querySelector(last_msg_sel).textContent === '💩 😇'); await u.waitUntil(() => view.content.querySelector(last_msg_sel).textContent === '💩 😇');
@ -301,13 +300,15 @@ describe("Emojis", function () {
expect(view.model.messages.at(2).get('correcting')).toBe(true); expect(view.model.messages.at(2).get('correcting')).toBe(true);
sel = 'converse-chat-message:last-child .chat-msg' sel = 'converse-chat-message:last-child .chat-msg'
await u.waitUntil(() => u.hasClass('correcting', view.querySelector(sel)), 500); await u.waitUntil(() => u.hasClass('correcting', view.querySelector(sel)), 500);
textarea.value = textarea.value += 'This is no longer an emoji-only message'; const edited_text = textarea.value += 'This is no longer an emoji-only message';
textarea.value = edited_text;
view.onKeyDown({ view.onKeyDown({
target: textarea, target: textarea,
preventDefault: function preventDefault () {}, preventDefault: function preventDefault () {},
keyCode: 13 // Enter keyCode: 13 // Enter
}); });
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => Array.from(view.querySelectorAll('.chat-msg__text'))
.filter(el => el.textContent === edited_text).length);
expect(view.model.messages.models.length).toBe(3); expect(view.model.messages.models.length).toBe(3);
let message = view.content.querySelector(last_msg_sel); let message = view.content.querySelector(last_msg_sel);
expect(u.hasClass('chat-msg__text--larger', message)).toBe(false); expect(u.hasClass('chat-msg__text--larger', message)).toBe(false);
@ -318,7 +319,7 @@ describe("Emojis", function () {
preventDefault: function preventDefault () {}, preventDefault: function preventDefault () {},
keyCode: 13 // Enter keyCode: 13 // Enter
}); });
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 4);
textarea.value = ':smile: :smiley: :imp:'; textarea.value = ':smile: :smiley: :imp:';
view.onKeyDown({ view.onKeyDown({
@ -326,7 +327,7 @@ describe("Emojis", function () {
preventDefault: function preventDefault () {}, preventDefault: function preventDefault () {},
keyCode: 13 // Enter keyCode: 13 // Enter
}); });
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 5);
message = view.content.querySelector('.message:last-child .chat-msg__text'); message = view.content.querySelector('.message:last-child .chat-msg__text');
expect(u.hasClass('chat-msg__text--larger', message)).toBe(true); expect(u.hasClass('chat-msg__text--larger', message)).toBe(true);

View File

@ -237,7 +237,6 @@ describe("XEP-0363: HTTP File Upload", function () {
'name': "my-juliet.jpg" 'name': "my-juliet.jpg"
}; };
view.model.sendFiles([file]); view.model.sendFiles([file]);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => _.filter(IQ_stanzas, iq => iq.querySelector('iq[to="upload.montague.tld"] request')).length); await u.waitUntil(() => _.filter(IQ_stanzas, iq => iq.querySelector('iq[to="upload.montague.tld"] request')).length);
const iq = IQ_stanzas.pop(); const iq = IQ_stanzas.pop();
@ -270,22 +269,20 @@ describe("XEP-0363: HTTP File Upload", function () {
</slot> </slot>
</iq>`); </iq>`);
spyOn(XMLHttpRequest.prototype, 'send').and.callFake(function () { spyOn(XMLHttpRequest.prototype, 'send').and.callFake(async function () {
const message = view.model.messages.at(0); const message = view.model.messages.at(0);
expect(view.querySelector('.chat-content progress').getAttribute('value')).toBe('0'); const el = await u.waitUntil(() => view.querySelector('.chat-content progress'));
expect(el.getAttribute('value')).toBe('0');
message.set('progress', 0.5); message.set('progress', 0.5);
u.waitUntil(() => view.querySelector('.chat-content progress').getAttribute('value') === '0.5') await u.waitUntil(() => view.querySelector('.chat-content progress').getAttribute('value') === '0.5')
.then(() => { message.set('progress', 1);
message.set('progress', 1); await u.waitUntil(() => view.querySelector('.chat-content progress').getAttribute('value') === '1')
u.waitUntil(() => view.querySelector('.chat-content progress').getAttribute('value') === '1') message.save({
}).then(() => { 'upload': _converse.SUCCESS,
message.save({ 'oob_url': message.get('get'),
'upload': _converse.SUCCESS, 'message': message.get('get')
'oob_url': message.get('get'),
'message': message.get('get')
});
return new Promise(resolve => view.model.messages.once('rendered', resolve));
}); });
await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
}); });
let sent_stanza; let sent_stanza;
spyOn(_converse.connection, 'send').and.callFake(stanza => (sent_stanza = stanza)); spyOn(_converse.connection, 'send').and.callFake(stanza => (sent_stanza = stanza));
@ -319,8 +316,7 @@ describe("XEP-0363: HTTP File Upload", function () {
done(); done();
})); }));
it("is uploaded and sent out from a groupchat", mock.initConverse(async (done, _converse) => { it("is uploaded and sent out from a groupchat", mock.initConverse(['chatBoxesFetched'], {} ,async (done, _converse) => {
const base_url = 'https://conversejs.org'; const base_url = 'https://conversejs.org';
await mock.waitUntilDiscoConfirmed( await mock.waitUntilDiscoConfirmed(
_converse, _converse.domain, _converse, _converse.domain,
@ -346,7 +342,6 @@ describe("XEP-0363: HTTP File Upload", function () {
'name': "my-juliet.jpg" 'name': "my-juliet.jpg"
}; };
view.model.sendFiles([file]); view.model.sendFiles([file]);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => _.filter(IQ_stanzas, iq => iq.querySelector('iq[to="upload.montague.tld"] request')).length); await u.waitUntil(() => _.filter(IQ_stanzas, iq => iq.querySelector('iq[to="upload.montague.tld"] request')).length);
const iq = IQ_stanzas.pop(); const iq = IQ_stanzas.pop();
@ -378,22 +373,20 @@ describe("XEP-0363: HTTP File Upload", function () {
</slot> </slot>
</iq>`); </iq>`);
spyOn(XMLHttpRequest.prototype, 'send').and.callFake(function () { spyOn(XMLHttpRequest.prototype, 'send').and.callFake(async function () {
const message = view.model.messages.at(0); const message = view.model.messages.at(0);
expect(view.querySelector('.chat-content progress').getAttribute('value')).toBe('0'); const el = await u.waitUntil(() => view.querySelector('.chat-content progress'));
expect(el.getAttribute('value')).toBe('0');
message.set('progress', 0.5); message.set('progress', 0.5);
u.waitUntil(() => view.querySelector('.chat-content progress').getAttribute('value') === '0.5') await u.waitUntil(() => view.querySelector('.chat-content progress').getAttribute('value') === '0.5')
.then(() => { message.set('progress', 1);
message.set('progress', 1); await u.waitUntil(() => view.querySelector('.chat-content progress')?.getAttribute('value') === '1')
u.waitUntil(() => view.querySelector('.chat-content progress')?.getAttribute('value') === '1') message.save({
}).then(() => { 'upload': _converse.SUCCESS,
message.save({ 'oob_url': message.get('get'),
'upload': _converse.SUCCESS, 'message': message.get('get')
'oob_url': message.get('get'),
'message': message.get('get')
});
return new Promise(resolve => view.model.messages.once('rendered', resolve));
}); });
await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
}); });
let sent_stanza; let sent_stanza;
spyOn(_converse.connection, 'send').and.callFake(stanza => (sent_stanza = stanza)); spyOn(_converse.connection, 'send').and.callFake(stanza => (sent_stanza = stanza));
@ -570,8 +563,7 @@ describe("XEP-0363: HTTP File Upload", function () {
'name': "my-juliet.jpg" 'name': "my-juliet.jpg"
}; };
view.model.sendFiles([file]); view.model.sendFiles([file]);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => IQ_stanzas.filter(iq => iq.querySelector('iq[to="upload.montague.tld"] request')).length)
await u.waitUntil(() => _.filter(IQ_stanzas, iq => iq.querySelector('iq[to="upload.montague.tld"] request')).length)
const iq = IQ_stanzas.pop(); const iq = IQ_stanzas.pop();
expect(Strophe.serialize(iq)).toBe( expect(Strophe.serialize(iq)).toBe(
`<iq from="romeo@montague.lit/orchard" `+ `<iq from="romeo@montague.lit/orchard" `+
@ -604,7 +596,8 @@ describe("XEP-0363: HTTP File Upload", function () {
spyOn(XMLHttpRequest.prototype, 'send').and.callFake(async () => { spyOn(XMLHttpRequest.prototype, 'send').and.callFake(async () => {
const message = view.model.messages.at(0); const message = view.model.messages.at(0);
expect(view.querySelector('.chat-content progress').getAttribute('value')).toBe('0'); const el = await u.waitUntil(() => view.querySelector('.chat-content progress'));
expect(el.getAttribute('value')).toBe('0');
message.set('progress', 0.5); message.set('progress', 0.5);
await u.waitUntil(() => view.querySelector('.chat-content progress').getAttribute('value') === '0.5'); await u.waitUntil(() => view.querySelector('.chat-content progress').getAttribute('value') === '0.5');
message.set('progress', 1); message.set('progress', 1);

View File

@ -64,7 +64,7 @@ describe("Message Archive Management", function () {
.c('count').t('16'); .c('count').t('16');
_converse.connection._dataRecv(mock.createRequest(iq_result)); _converse.connection._dataRecv(mock.createRequest(iq_result));
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 2);
expect(view.model.messages.length).toBe(2); expect(view.model.messages.length).toBe(2);
while (sent_IQs.length) { sent_IQs.pop(); } while (sent_IQs.length) { sent_IQs.pop(); }
@ -379,7 +379,8 @@ describe("Message Archive Management", function () {
.c('count').t('16'); .c('count').t('16');
_converse.connection._dataRecv(mock.createRequest(iq_result)); _converse.connection._dataRecv(mock.createRequest(iq_result));
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => Array.from(view.querySelectorAll('.chat-msg__text'))
.filter(el => el.textContent === "Thrice the brinded cat hath mew'd.").length, 1000);
expect(view.model.messages.length).toBe(1); expect(view.model.messages.length).toBe(1);
expect(view.model.messages.at(0).get('message')).toBe("Thrice the brinded cat hath mew'd."); expect(view.model.messages.at(0).get('message')).toBe("Thrice the brinded cat hath mew'd.");
done(); done();
@ -433,7 +434,7 @@ describe("Message Archive Management", function () {
.c('count').t('16'); .c('count').t('16');
_converse.connection._dataRecv(mock.createRequest(iq_result)); _converse.connection._dataRecv(mock.createRequest(iq_result));
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 2);
expect(view.model.messages.length).toBe(2); expect(view.model.messages.length).toBe(2);
expect(view.model.messages.at(0).get('message')).toBe("Meet me at the dance"); expect(view.model.messages.at(0).get('message')).toBe("Meet me at the dance");
expect(view.model.messages.at(1).get('message')).toBe("Thrice the brinded cat hath mew'd."); expect(view.model.messages.at(1).get('message')).toBe("Thrice the brinded cat hath mew'd.");

View File

@ -91,7 +91,7 @@ describe("A XEP-0333 Chat Marker", function () {
<stanza-id xmlns="urn:xmpp:sid:0" id="IxVDLJ0RYbWcWvqC" by="${_converse.bare_jid}"/> <stanza-id xmlns="urn:xmpp:sid:0" id="IxVDLJ0RYbWcWvqC" by="${_converse.bare_jid}"/>
</message>`); </message>`);
_converse.connection._dataRecv(mock.createRequest(stanza)); _converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
expect(view.querySelectorAll('.chat-msg').length).toBe(1); expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.model.messages.length).toBe(1); expect(view.model.messages.length).toBe(1);
@ -130,9 +130,9 @@ describe("A XEP-0333 Chat Marker", function () {
preventDefault: function preventDefault () {}, preventDefault: function preventDefault () {},
keyCode: 13 // Enter keyCode: 13 // Enter
}); });
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
expect(view.querySelectorAll('.chat-msg').length).toBe(1); expect(view.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.querySelector('.chat-msg .chat-msg__body').textContent.trim()) expect(view.querySelector('.chat-msg .chat-msg__text').textContent.trim())
.toBe("But soft, what light through yonder airlock breaks?"); .toBe("But soft, what light through yonder airlock breaks?");
const msg_obj = view.model.messages.at(0); const msg_obj = view.model.messages.at(0);

View File

@ -23,8 +23,8 @@ describe("A Groupchat Message", function () {
}).c('body').t(message).tree(); }).c('body').t(message).tree();
await view.model.handleMessageStanza(msg); await view.model.handleMessageStanza(msg);
await u.waitUntil(() => sizzle('.chat-msg:last .chat-msg__text', view.content).pop()); await u.waitUntil(() => sizzle('.chat-msg:last .chat-msg__text', view.content).pop());
await u.waitUntil(() => view.querySelector('.chat-msg__text').textContent.trim() === 'is tired');
expect(view.querySelector('.chat-msg__author').textContent.includes('**Dyon van de Wege')).toBeTruthy(); expect(view.querySelector('.chat-msg__author').textContent.includes('**Dyon van de Wege')).toBeTruthy();
expect(view.querySelector('.chat-msg__text').textContent.trim()).toBe('is tired');
message = '/me is as well'; message = '/me is as well';
msg = $msg({ msg = $msg({
@ -35,8 +35,8 @@ describe("A Groupchat Message", function () {
}).c('body').t(message).tree(); }).c('body').t(message).tree();
await view.model.handleMessageStanza(msg); await view.model.handleMessageStanza(msg);
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 2); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 2);
await u.waitUntil(() => Array.from(view.querySelectorAll('.chat-msg__text')).pop().textContent.trim() === 'is as well');
expect(sizzle('.chat-msg__author:last', view).pop().textContent.includes('**Romeo Montague')).toBeTruthy(); expect(sizzle('.chat-msg__author:last', view).pop().textContent.includes('**Romeo Montague')).toBeTruthy();
expect(sizzle('.chat-msg__text:last', view).pop().textContent.trim()).toBe('is as well');
// Check rendering of a mention inside a me message // Check rendering of a mention inside a me message
const msg_text = "/me mentions romeo"; const msg_text = "/me mentions romeo";
@ -77,8 +77,8 @@ describe("A Message", function () {
const view = _converse.chatboxviews.get(sender_jid); const view = _converse.chatboxviews.get(sender_jid);
await u.waitUntil(() => view.querySelector('.chat-msg__text')); await u.waitUntil(() => view.querySelector('.chat-msg__text'));
expect(view.querySelectorAll('.chat-msg--action').length).toBe(1); expect(view.querySelectorAll('.chat-msg--action').length).toBe(1);
await u.waitUntil(() => view.querySelector('.chat-msg__text').textContent.trim() === 'is tired');
expect(view.querySelector('.chat-msg__author').textContent.includes('**Mercutio')).toBeTruthy(); expect(view.querySelector('.chat-msg__author').textContent.includes('**Mercutio')).toBeTruthy();
expect(view.querySelector('.chat-msg__text').textContent).toBe('is tired');
message = '/me is as well'; message = '/me is as well';
await mock.sendMessage(view, message); await mock.sendMessage(view, message);

View File

@ -1,6 +1,6 @@
/*global mock, converse */ /*global mock, converse */
const { Promise, Strophe, $msg, $pres } = converse.env; const { Strophe, $msg, $pres } = converse.env;
const u = converse.env.utils; const u = converse.env.utils;
@ -22,7 +22,7 @@ describe("An incoming groupchat message", function () {
type: 'groupchat' type: 'groupchat'
}).c('body').t(message).tree(); }).c('body').t(message).tree();
await view.model.handleMessageStanza(msg); await view.model.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
expect(u.hasClass('mentioned', view.querySelector('.chat-msg'))).toBeTruthy(); expect(u.hasClass('mentioned', view.querySelector('.chat-msg'))).toBeTruthy();
done(); done();
})); }));
@ -58,12 +58,12 @@ describe("An incoming groupchat message", function () {
.c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'11', 'end':'14', 'type':'mention', 'uri':'xmpp:romeo@montague.lit'}).up() .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'11', 'end':'14', 'type':'mention', 'uri':'xmpp:romeo@montague.lit'}).up()
.c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'15', 'end':'23', 'type':'mention', 'uri':'xmpp:mr.robot@montague.lit'}).nodeTree; .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'15', 'end':'23', 'type':'mention', 'uri':'xmpp:mr.robot@montague.lit'}).nodeTree;
await view.model.handleMessageStanza(msg); await view.model.handleMessageStanza(msg);
let message = await u.waitUntil(() => view.querySelector('.chat-msg__text')); await u.waitUntil(() => view.querySelector('.chat-msg__text')?.innerHTML.replace(/<!---->/g, '') ===
expect(message.classList.length).toEqual(1);
expect(message.innerHTML.replace(/<!---->/g, '')).toBe(
'hello <span class="mention">z3r0</span> '+ 'hello <span class="mention">z3r0</span> '+
'<span class="mention mention--self badge badge-info">tom</span> '+ '<span class="mention mention--self badge badge-info">tom</span> '+
'<span class="mention">mr.robot</span>, how are you?'); '<span class="mention">mr.robot</span>, how are you?');
let message = view.querySelector('.chat-msg__text')
expect(message.classList.length).toEqual(1);
msg = $msg({ msg = $msg({
from: 'lounge@montague.lit/sw0rdf1sh', from: 'lounge@montague.lit/sw0rdf1sh',
@ -113,10 +113,10 @@ describe("An incoming groupchat message", function () {
.c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'16', 'end':'24', 'type':'mention', 'uri':'xmpp:mr.robot@montague.lit'}).nodeTree; .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'16', 'end':'24', 'type':'mention', 'uri':'xmpp:mr.robot@montague.lit'}).nodeTree;
await view.model.handleMessageStanza(msg); await view.model.handleMessageStanza(msg);
const message = await u.waitUntil(() => view.querySelector('.chat-msg__text')); await u.waitUntil(() => view.querySelector('.chat-msg__text')?.innerHTML.replace(/<!---->/g, '') ===
expect(message.classList.length).toEqual(1);
expect(message.innerHTML.replace(/<!---->/g, '')).toBe(
'<blockquote>hello <span class="mention">z3r0</span> <span class="mention mention--self badge badge-info">tom</span> <span class="mention">mr.robot</span>, how are you?</blockquote>'); '<blockquote>hello <span class="mention">z3r0</span> <span class="mention mention--self badge badge-info">tom</span> <span class="mention">mr.robot</span>, how are you?</blockquote>');
const message = view.querySelector('.chat-msg__text');
expect(message.classList.length).toEqual(1);
done(); done();
})); }));
}); });
@ -316,7 +316,7 @@ describe("A sent groupchat message", function () {
} }
spyOn(_converse.connection, 'send'); spyOn(_converse.connection, 'send');
view.onKeyDown(enter_event); view.onKeyDown(enter_event);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
const msg = _converse.connection.send.calls.all()[1].args[0]; const msg = _converse.connection.send.calls.all()[1].args[0];
expect(msg.toLocaleString()) expect(msg.toLocaleString())
.toBe(`<message from="romeo@montague.lit/orchard" id="${msg.nodeTree.getAttribute("id")}" `+ .toBe(`<message from="romeo@montague.lit/orchard" id="${msg.nodeTree.getAttribute("id")}" `+
@ -375,7 +375,7 @@ describe("A sent groupchat message", function () {
} }
spyOn(_converse.connection, 'send'); spyOn(_converse.connection, 'send');
view.onKeyDown(enter_event); view.onKeyDown(enter_event);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
const last_msg_sel = 'converse-chat-message:last-child .chat-msg__text'; const last_msg_sel = 'converse-chat-message:last-child .chat-msg__text';
await u.waitUntil(() => await u.waitUntil(() =>
@ -458,7 +458,7 @@ describe("A sent groupchat message", function () {
'keyCode': 13 // Enter 'keyCode': 13 // Enter
} }
view.onKeyDown(enter_event); view.onKeyDown(enter_event);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
const msg = _converse.connection.send.calls.all()[1].args[0]; const msg = _converse.connection.send.calls.all()[1].args[0];
expect(msg.toLocaleString()) expect(msg.toLocaleString())

View File

@ -111,7 +111,7 @@ describe("A Chat Message", function () {
.c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2017-12-31T22:08:25Z'}) .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2017-12-31T22:08:25Z'})
.tree(); .tree();
_converse.handleMessageStanza(msg); _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 2);
msg = $msg({ msg = $msg({
'xmlns': 'jabber:client', 'xmlns': 'jabber:client',
@ -123,7 +123,7 @@ describe("A Chat Message", function () {
.c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-01T13:18:23Z'}) .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-01T13:18:23Z'})
.tree(); .tree();
_converse.handleMessageStanza(msg); _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 3);
msg = $msg({ msg = $msg({
'xmlns': 'jabber:client', 'xmlns': 'jabber:client',
@ -135,7 +135,7 @@ describe("A Chat Message", function () {
.c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-01T13:18:23Z'}) .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-01T13:18:23Z'})
.tree(); .tree();
_converse.handleMessageStanza(msg); _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 4);
msg = $msg({ msg = $msg({
'xmlns': 'jabber:client', 'xmlns': 'jabber:client',
@ -147,7 +147,7 @@ describe("A Chat Message", function () {
.c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-02T12:18:23Z'}) .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-02T12:18:23Z'})
.tree(); .tree();
_converse.handleMessageStanza(msg); _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 5);
msg = $msg({ msg = $msg({
'xmlns': 'jabber:client', 'xmlns': 'jabber:client',
@ -159,7 +159,7 @@ describe("A Chat Message", function () {
.c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-02T22:28:23Z'}) .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-02T22:28:23Z'})
.tree(); .tree();
_converse.handleMessageStanza(msg); _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 6);
// Insert <composing> message, to also check that // Insert <composing> message, to also check that
// text messages are inserted correctly with // text messages are inserted correctly with
@ -185,8 +185,9 @@ describe("A Chat Message", function () {
.c('composing', {'xmlns': Strophe.NS.CHATSTATES}).up() .c('composing', {'xmlns': Strophe.NS.CHATSTATES}).up()
.c('body').t("latest message") .c('body').t("latest message")
.tree(); .tree();
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 7);
view.clearSpinner(); //cleanup view.clearSpinner(); //cleanup
expect(view.content.querySelectorAll('.date-separator').length).toEqual(4); expect(view.content.querySelectorAll('.date-separator').length).toEqual(4);
@ -435,7 +436,7 @@ describe("A Chat Message", function () {
.c('delay', { xmlns:'urn:xmpp:delay', from: 'montague.lit', stamp: one_day_ago.toISOString() }) .c('delay', { xmlns:'urn:xmpp:delay', from: 'montague.lit', stamp: one_day_ago.toISOString() })
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree(); .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length);
expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object)); expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
expect(chatbox.messages.length).toEqual(1); expect(chatbox.messages.length).toEqual(1);
@ -446,16 +447,16 @@ describe("A Chat Message", function () {
expect(msg_obj.get('sender')).toEqual('them'); expect(msg_obj.get('sender')).toEqual('them');
expect(msg_obj.get('is_delayed')).toEqual(true); expect(msg_obj.get('is_delayed')).toEqual(true);
await u.waitUntil(() => chatbox.vcard.get('fullname') === 'Juliet Capulet') await u.waitUntil(() => chatbox.vcard.get('fullname') === 'Juliet Capulet')
expect(view.msgs_container.querySelector('.chat-msg .chat-msg__text').textContent).toEqual(message); expect(view.querySelector('.chat-msg .chat-msg__text').textContent).toEqual(message);
expect(view.msgs_container.querySelector('.chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy(); expect(view.querySelector('.chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
expect(view.msgs_container.querySelector('span.chat-msg__author').textContent.trim()).toBe('Juliet Capulet'); expect(view.querySelector('span.chat-msg__author').textContent.trim()).toBe('Juliet Capulet');
expect(view.msgs_container.querySelectorAll('.date-separator').length).toEqual(1); expect(view.querySelectorAll('.date-separator').length).toEqual(1);
let day = view.msgs_container.querySelector('.date-separator'); let day = view.querySelector('.date-separator');
expect(day.getAttribute('class')).toEqual('message date-separator'); expect(day.getAttribute('class')).toEqual('message date-separator');
expect(day.getAttribute('data-isodate')).toEqual(dayjs(one_day_ago.startOf('day')).toISOString()); expect(day.getAttribute('data-isodate')).toEqual(dayjs(one_day_ago.startOf('day')).toISOString());
let time = view.msgs_container.querySelector('time.separator-text'); let time = view.querySelector('time.separator-text');
expect(time.textContent).toEqual(dayjs(one_day_ago.startOf('day')).format("dddd MMM Do YYYY")); expect(time.textContent).toEqual(dayjs(one_day_ago.startOf('day')).format("dddd MMM Do YYYY"));
message = 'This is a current message'; message = 'This is a current message';
@ -467,19 +468,19 @@ describe("A Chat Message", function () {
}).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();
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 2);
expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object)); expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
// Check that there is a <time> element, with the required props. // Check that there is a <time> element, with the required props.
expect(view.msgs_container.querySelectorAll('time.separator-text').length).toEqual(2); // There are now two time elements expect(view.querySelectorAll('time.separator-text').length).toEqual(2); // There are now two time elements
const message_date = new Date(); const message_date = new Date();
day = sizzle('.date-separator:last', view.msgs_container); day = sizzle('.date-separator:last', view);
expect(day.length).toEqual(1); expect(day.length).toEqual(1);
expect(day[0].getAttribute('class')).toEqual('message date-separator'); expect(day[0].getAttribute('class')).toEqual('message date-separator');
expect(day[0].getAttribute('data-isodate')).toEqual(dayjs(message_date).startOf('day').toISOString()); expect(day[0].getAttribute('data-isodate')).toEqual(dayjs(message_date).startOf('day').toISOString());
time = sizzle('time.separator-text:last', view.msgs_container).pop(); time = sizzle('time.separator-text:last', view).pop();
expect(time.textContent).toEqual(dayjs(message_date).startOf('day').format("dddd MMM Do YYYY")); expect(time.textContent).toEqual(dayjs(message_date).startOf('day').format("dddd MMM Do YYYY"));
// Normal checks for the 2nd message // Normal checks for the 2nd message
@ -489,12 +490,12 @@ describe("A Chat Message", function () {
expect(msg_obj.get('fullname')).toBeUndefined(); expect(msg_obj.get('fullname')).toBeUndefined();
expect(msg_obj.get('sender')).toEqual('them'); expect(msg_obj.get('sender')).toEqual('them');
expect(msg_obj.get('is_delayed')).toEqual(false); expect(msg_obj.get('is_delayed')).toEqual(false);
const msg_txt = sizzle('.chat-msg:last .chat-msg__text', view.msgs_container).pop().textContent; const msg_txt = sizzle('.chat-msg:last .chat-msg__text', view).pop().textContent;
expect(msg_txt).toEqual(message); expect(msg_txt).toEqual(message);
expect(view.msgs_container.querySelector('converse-chat-message:last-child .chat-msg__text').textContent).toEqual(message); expect(view.querySelector('converse-chat-message:last-child .chat-msg__text').textContent).toEqual(message);
expect(view.msgs_container.querySelector('converse-chat-message:last-child .chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy(); expect(view.querySelector('converse-chat-message:last-child .chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
expect(view.msgs_container.querySelector('converse-chat-message:last-child .chat-msg__author').textContent.trim()).toBe('Juliet Capulet'); expect(view.querySelector('converse-chat-message:last-child .chat-msg__author').textContent.trim()).toBe('Juliet Capulet');
done(); done();
})); }));
@ -528,7 +529,7 @@ describe("A Chat Message", function () {
spyOn(view.model, 'sendMessage').and.callThrough(); spyOn(view.model, 'sendMessage').and.callThrough();
mock.sendMessage(view, message); mock.sendMessage(view, message);
expect(view.model.sendMessage).toHaveBeenCalled(); expect(view.model.sendMessage).toHaveBeenCalled();
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
const msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop(); const msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message); expect(msg.textContent).toEqual(message);
await u.waitUntil(() => msg.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg.innerHTML.replace(/<!---->/g, '') ===
@ -547,9 +548,8 @@ describe("A Chat Message", function () {
const view = _converse.api.chatviews.get(contact_jid); const view = _converse.api.chatviews.get(contact_jid);
let message = 'This message contains a hyperlink with forbidden query params: https://www.opkode.com/?id=0&utm_content=1&utm_medium=2&s=1'; let message = 'This message contains a hyperlink with forbidden query params: https://www.opkode.com/?id=0&utm_content=1&utm_medium=2&s=1';
mock.sendMessage(view, message); mock.sendMessage(view, message);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
let msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop(); let msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
await u.waitUntil(() => msg.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg.innerHTML.replace(/<!---->/g, '') ===
'This message contains a hyperlink with forbidden query params: <a target="_blank" rel="noopener" href="https://www.opkode.com/?id=0">https://www.opkode.com/?id=0</a>'); 'This message contains a hyperlink with forbidden query params: <a target="_blank" rel="noopener" href="https://www.opkode.com/?id=0">https://www.opkode.com/?id=0</a>');
@ -557,9 +557,9 @@ describe("A Chat Message", function () {
_converse.api.settings.set('filter_url_query_params', 'utm_medium'); _converse.api.settings.set('filter_url_query_params', 'utm_medium');
message = 'Another message with a hyperlink with forbidden query params: https://www.opkode.com/?id=0&utm_content=1&utm_medium=2&s=1'; message = 'Another message with a hyperlink with forbidden query params: https://www.opkode.com/?id=0&utm_content=1&utm_medium=2&s=1';
mock.sendMessage(view, message); mock.sendMessage(view, message);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 2);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop(); msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message); expect(msg.textContent).toEqual('Another message with a hyperlink with forbidden query params: https://www.opkode.com/?id=0&utm_content=1&s=1');
await u.waitUntil(() => msg.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg.innerHTML.replace(/<!---->/g, '') ===
'Another message with a hyperlink with forbidden query params: '+ 'Another message with a hyperlink with forbidden query params: '+
'<a target="_blank" rel="noopener" href="https://www.opkode.com/?id=0&amp;utm_content=1&amp;s=1">https://www.opkode.com/?id=0&amp;utm_content=1&amp;s=1</a>'); '<a target="_blank" rel="noopener" href="https://www.opkode.com/?id=0&amp;utm_content=1&amp;s=1">https://www.opkode.com/?id=0&amp;utm_content=1&amp;s=1</a>');
@ -577,7 +577,7 @@ describe("A Chat Message", function () {
<body>Hey\nHave you heard the news?</body> <body>Hey\nHave you heard the news?</body>
</message>`); </message>`);
_converse.connection._dataRecv(mock.createRequest(stanza)); _converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
expect(view.content.querySelector('.chat-msg__text').innerHTML.replace(/<!---->/g, '')).toBe('Hey\nHave you heard the news?'); expect(view.content.querySelector('.chat-msg__text').innerHTML.replace(/<!---->/g, '')).toBe('Hey\nHave you heard the news?');
stanza = u.toStanza(` stanza = u.toStanza(`
<message from="${contact_jid}" <message from="${contact_jid}"
@ -586,8 +586,9 @@ describe("A Chat Message", function () {
<body>Hey\n\n\nHave you heard the news?</body> <body>Hey\n\n\nHave you heard the news?</body>
</message>`); </message>`);
_converse.connection._dataRecv(mock.createRequest(stanza)); _converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 2);
await u.waitUntil(() => view.content.querySelector('converse-chat-message:last-child .chat-msg__text').innerHTML.replace(/<!---->/g, '') === 'Hey\n\n\nHave you heard the news?'); const text = view.content.querySelector('converse-chat-message:last-child .chat-msg__text').innerHTML.replace(/<!---->/g, '');
expect(text).toBe('Hey\n\u200B\nHave you heard the news?');
stanza = u.toStanza(` stanza = u.toStanza(`
<message from="${contact_jid}" <message from="${contact_jid}"
type="chat" type="chat"
@ -595,7 +596,7 @@ describe("A Chat Message", function () {
<body>Hey\nHave you heard\nthe news?</body> <body>Hey\nHave you heard\nthe news?</body>
</message>`); </message>`);
_converse.connection._dataRecv(mock.createRequest(stanza)); _converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 3);
expect(view.content.querySelector('converse-chat-message:last-child .chat-msg__text').innerHTML.replace(/<!---->/g, '')).toBe('Hey\nHave you heard\nthe news?'); expect(view.content.querySelector('converse-chat-message:last-child .chat-msg__text').innerHTML.replace(/<!---->/g, '')).toBe('Hey\nHave you heard\nthe news?');
stanza = u.toStanza(` stanza = u.toStanza(`
@ -605,7 +606,7 @@ describe("A Chat Message", function () {
<body>Hey\nHave you heard\n\n\nthe news?\nhttps://conversejs.org</body> <body>Hey\nHave you heard\n\n\nthe news?\nhttps://conversejs.org</body>
</message>`); </message>`);
_converse.connection._dataRecv(mock.createRequest(stanza)); _converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 4);
await u.waitUntil(() => { await u.waitUntil(() => {
const text = view.content.querySelector('converse-chat-message:last-child .chat-msg__text').innerHTML.replace(/<!---->/g, ''); const text = view.content.querySelector('converse-chat-message:last-child .chat-msg__text').innerHTML.replace(/<!---->/g, '');
return text === 'Hey\nHave you heard\n\u200B\nthe news?\n<a target="_blank" rel="noopener" href="https://conversejs.org/">https://conversejs.org</a>'; return text === 'Hey\nHave you heard\n\u200B\nthe news?\n<a target="_blank" rel="noopener" href="https://conversejs.org/">https://conversejs.org</a>';
@ -1067,7 +1068,7 @@ describe("A Chat Message", function () {
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
const view = await u.waitUntil(() => _converse.api.chatviews.get(sender_jid)); const view = await u.waitUntil(() => _converse.api.chatviews.get(sender_jid));
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length);
expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object)); expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
// Check that the chatbox and its view now exist // Check that the chatbox and its view now exist
@ -1081,7 +1082,7 @@ describe("A Chat Message", function () {
expect(_converse.api.vcard.get).toHaveBeenCalled(); expect(_converse.api.vcard.get).toHaveBeenCalled();
await u.waitUntil(() => chatbox.vcard.get('fullname') === mock.cur_names[0]) await u.waitUntil(() => chatbox.vcard.get('fullname') === mock.cur_names[0])
author_el = view.querySelector('.chat-msg__author'); author_el = view.querySelector('.chat-msg__author');
expect( _.includes(author_el.textContent.trim(), 'Mercutio')).toBeTruthy(); expect(author_el.textContent.trim().includes('Mercutio')).toBeTruthy();
done(); done();
})); }));
}); });
@ -1119,7 +1120,7 @@ describe("A Chat Message", function () {
_converse.allow_non_roster_messaging = true; _converse.allow_non_roster_messaging = true;
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
view = _converse.chatboxviews.get(sender_jid); view = _converse.chatboxviews.get(sender_jid);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length);
expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object)); expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
// Check that the chatbox and its view now exist // Check that the chatbox and its view now exist
chatbox = await _converse.api.chats.get(sender_jid); chatbox = await _converse.api.chats.get(sender_jid);
@ -1170,7 +1171,7 @@ describe("A Chat Message", function () {
let msg_text = 'This message will not be sent, due to an error'; let msg_text = 'This message will not be sent, due to an error';
const view = _converse.api.chatviews.get(sender_jid); const view = _converse.api.chatviews.get(sender_jid);
const message = await view.model.sendMessage(msg_text); const message = await view.model.sendMessage(msg_text);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length);
let msg_txt = sizzle('.chat-msg:last .chat-msg__text', view.content).pop().textContent; let msg_txt = sizzle('.chat-msg:last .chat-msg__text', view.content).pop().textContent;
expect(msg_txt).toEqual(msg_text); expect(msg_txt).toEqual(msg_text);
@ -1253,18 +1254,14 @@ describe("A Chat Message", function () {
.t('Something else went wrong as well'); .t('Something else went wrong as well');
_converse.connection._dataRecv(mock.createRequest(stanza)); _converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => view.model.messages.length > 2); await u.waitUntil(() => view.model.messages.length > 2);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__error').length === 3);
expect(view.content.querySelectorAll('.chat-msg__error').length).toEqual(3);
// Ensure messages with error are not editable // Ensure messages with error are not editable or retractable
document.querySelectorAll('.chat-msg__actions').forEach(elem => { await u.waitUntil(() => !view.model.messages.models.reduce((acc, m) => acc || m.get('editable'), false), 1000);
expect(elem.querySelector('.chat-msg__action-edit')).toBe(null) view.querySelectorAll('.chat-msg').forEach(el => {
expect(el.querySelector('.chat-msg__action-edit')).toBe(null)
expect(el.querySelector('.chat-msg__action-retract')).toBe(null)
}) })
view.model.messages.forEach(message => {
const isEditable = message.get('editable');
isEditable && expect(isEditable).toBe(false);
})
done(); done();
})); }));
@ -1302,7 +1299,7 @@ describe("A Chat Message", function () {
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.chatboxviews.get(contact_jid);
const msg_text = 'This message will show!'; const msg_text = 'This message will show!';
await view.model.sendMessage(msg_text); await view.model.sendMessage(msg_text);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
expect(view.content.querySelectorAll('.chat-error').length).toEqual(0); expect(view.content.querySelectorAll('.chat-error').length).toEqual(0);
done(); done();
})); }));

View File

@ -664,7 +664,7 @@ describe("Groupchats", function () {
<body>This is a message</body> <body>This is a message</body>
</message>`); </message>`);
_converse.connection._dataRecv(mock.createRequest(stanza)); _converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
expect(sizzle('.chat-msg__subject', view).length).toBe(1); expect(sizzle('.chat-msg__subject', view).length).toBe(1);
expect(sizzle('.chat-msg__subject', view).pop().textContent.trim()).toBe('This is a message subject'); expect(sizzle('.chat-msg__subject', view).pop().textContent.trim()).toBe('This is a message subject');
expect(sizzle('.chat-msg__text').length).toBe(1); expect(sizzle('.chat-msg__text').length).toBe(1);
@ -695,7 +695,7 @@ describe("Groupchats", function () {
<body>This is a message</body> <body>This is a message</body>
</message>`); </message>`);
_converse.connection._dataRecv(mock.createRequest(stanza)); _converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
expect(sizzle('.chat-msg__subject', view).length).toBe(1); expect(sizzle('.chat-msg__subject', view).length).toBe(1);
expect(sizzle('.chat-msg__subject', view).pop().textContent.trim()).toBe('This is a message subject'); expect(sizzle('.chat-msg__subject', view).pop().textContent.trim()).toBe('This is a message subject');
expect(sizzle('.chat-msg__text').length).toBe(1); expect(sizzle('.chat-msg__text').length).toBe(1);
@ -1009,7 +1009,7 @@ describe("Groupchats", function () {
'type': 'groupchat' 'type': 'groupchat'
}).c('body').t('hello world').tree(); }).c('body').t('hello world').tree();
_converse.connection._dataRecv(mock.createRequest(msg)); _converse.connection._dataRecv(mock.createRequest(msg));
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
// Add another entrant, otherwise the above message will be // Add another entrant, otherwise the above message will be
// collapsed if "newguy" leaves immediately again // collapsed if "newguy" leaves immediately again
@ -2112,7 +2112,7 @@ describe("Groupchats", function () {
preventDefault: function preventDefault () {}, preventDefault: function preventDefault () {},
keyCode: 13 keyCode: 13
}); });
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
expect(_converse.api.trigger).toHaveBeenCalledWith('messageSend', jasmine.any(_converse.Message)); expect(_converse.api.trigger).toHaveBeenCalledWith('messageSend', jasmine.any(_converse.Message));
expect(view.content.querySelectorAll('.chat-msg').length).toBe(1); expect(view.content.querySelectorAll('.chat-msg').length).toBe(1);
@ -2170,7 +2170,7 @@ describe("Groupchats", function () {
type: 'groupchat', type: 'groupchat',
id: u.getUniqueId(), id: u.getUniqueId(),
}).c('body').t(message).tree()); }).c('body').t(message).tree());
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 21);
// Now check that the message appears inside the chatbox in the DOM // Now check that the message appears inside the chatbox in the DOM
const msg_txt = sizzle('.chat-msg:last .chat-msg__text', view.content).pop().textContent; const msg_txt = sizzle('.chat-msg:last .chat-msg__text', view.content).pop().textContent;
expect(msg_txt).toEqual(message); expect(msg_txt).toEqual(message);
@ -2858,8 +2858,8 @@ describe("Groupchats", function () {
'role': 'participant' 'role': 'participant'
}); });
_converse.connection._dataRecv(mock.createRequest(presence)); _converse.connection._dataRecv(mock.createRequest(presence));
const csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() ===
expect(csntext.trim()).toEqual("romeo and annoyingGuy have entered the groupchat"); "romeo and annoyingGuy have entered the groupchat");
presence = $pres({ presence = $pres({
'from': 'lounge@montague.lit/annoyingGuy', 'from': 'lounge@montague.lit/annoyingGuy',
@ -2906,6 +2906,7 @@ describe("Groupchats", function () {
Array.from(view.querySelectorAll('.chat-info__message')).pop()?.textContent.trim() === Array.from(view.querySelectorAll('.chat-info__message')).pop()?.textContent.trim() ===
"annoyingGuy is no longer a member of this groupchat" "annoyingGuy is no longer a member of this groupchat"
); );
expect(1).toBe(1);
done(); done();
})); }));
@ -3614,8 +3615,8 @@ describe("Groupchats", function () {
'role': 'participant' 'role': 'participant'
}); });
_converse.connection._dataRecv(mock.createRequest(presence)); _converse.connection._dataRecv(mock.createRequest(presence));
const csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() ===
expect(csntext.trim()).toEqual("romeo and trustworthyguy have entered the groupchat"); "romeo and trustworthyguy have entered the groupchat");
const textarea = view.querySelector('.chat-textarea') const textarea = view.querySelector('.chat-textarea')
textarea.value = '/op'; textarea.value = '/op';
@ -3753,8 +3754,8 @@ describe("Groupchats", function () {
'role': 'participant' 'role': 'participant'
}); });
_converse.connection._dataRecv(mock.createRequest(presence)); _converse.connection._dataRecv(mock.createRequest(presence));
const csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() ===
expect(csntext.trim()).toEqual("romeo and annoyingGuy have entered the groupchat"); "romeo and annoyingGuy have entered the groupchat");
const textarea = view.querySelector('.chat-textarea') const textarea = view.querySelector('.chat-textarea')
textarea.value = '/mute'; textarea.value = '/mute';

View File

@ -174,8 +174,8 @@ describe("A Groupchat Message", function () {
.c('active', {'xmlns': "http://jabber.org/protocol/chatstates"}) .c('active', {'xmlns': "http://jabber.org/protocol/chatstates"})
.tree(); .tree();
await view.model.handleMessageStanza(msg); await view.model.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); const el = await u.waitUntil(() => view.querySelector('.chat-msg__text'));
expect(view.querySelector('.chat-msg')).not.toBe(null); expect(el.textContent).toBe(message);
done(); done();
})); }));
@ -399,10 +399,9 @@ describe("A Groupchat Message", function () {
type: 'groupchat' type: 'groupchat'
}).c('body').t('Another message!').tree(); }).c('body').t('Another message!').tree();
await view.model.handleMessageStanza(msg); await view.model.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 2);
expect(view.model.messages.last().occupant.get('affiliation')).toBe('member'); expect(view.model.messages.last().occupant.get('affiliation')).toBe('member');
expect(view.model.messages.last().occupant.get('role')).toBe('participant'); expect(view.model.messages.last().occupant.get('role')).toBe('participant');
expect(view.querySelectorAll('.chat-msg').length).toBe(2);
expect(sizzle('.chat-msg', view.el).pop().classList.value.trim()).toBe('message chat-msg groupchat chat-msg--with-avatar participant member'); expect(sizzle('.chat-msg', view.el).pop().classList.value.trim()).toBe('message chat-msg groupchat chat-msg--with-avatar participant member');
presence = $pres({ presence = $pres({
@ -436,7 +435,7 @@ describe("A Groupchat Message", function () {
type: 'groupchat' type: 'groupchat'
}).c('body').t('Message from someone not in the MUC right now').tree(); }).c('body').t('Message from someone not in the MUC right now').tree();
await view.model.handleMessageStanza(msg); await view.model.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 4);
expect(view.model.messages.last().occupant).toBeUndefined(); expect(view.model.messages.last().occupant).toBeUndefined();
// Check that there's a new "add" event handler, for when the occupant appears. // Check that there's a new "add" event handler, for when the occupant appears.
expect(view.model.occupants._events.add.length).toBe(add_events+1); expect(view.model.occupants._events.add.length).toBe(add_events+1);

View File

@ -790,7 +790,7 @@ describe("Message Retractions", function () {
expect(view.model.messages.length).toBe(1); expect(view.model.messages.length).toBe(1);
expect(view.model.messages.at(0).get('editable')).toBe(true); expect(view.model.messages.at(0).get('editable')).toBe(true);
const retract_button = await u.waitUntil(() => view.msgs_container.querySelector('.chat-msg__content .chat-msg__action-retract')); const retract_button = await u.waitUntil(() => view.querySelector('.chat-msg__content .chat-msg__action-retract'));
retract_button.click(); retract_button.click();
await u.waitUntil(() => u.isVisible(document.querySelector('#converse-modals .modal'))); await u.waitUntil(() => u.isVisible(document.querySelector('#converse-modals .modal')));
const submit_button = document.querySelector('#converse-modals .modal button[type="submit"]'); const submit_button = document.querySelector('#converse-modals .modal button[type="submit"]');

View File

@ -1,6 +1,6 @@
/*global mock, converse */ /*global mock, converse */
const { u, Promise, $msg } = converse.env; const { u, $msg } = converse.env;
describe("An incoming chat Message", function () { describe("An incoming chat Message", function () {
@ -79,7 +79,7 @@ describe("An incoming chat Message", function () {
msg_text = "This *message _contains_* styling hints! \`Here's *some* code\`"; msg_text = "This *message _contains_* styling hints! \`Here's *some* code\`";
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
msg_el = view.querySelector('converse-chat-message-body'); msg_el = view.querySelector('converse-chat-message-body');
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
@ -92,8 +92,8 @@ describe("An incoming chat Message", function () {
msg_text = "Here's a ~strikethrough section~"; msg_text = "Here's a ~strikethrough section~";
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 2);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
@ -103,7 +103,7 @@ describe("An incoming chat Message", function () {
msg_text = "~Check out this site: https://conversejs.org~" msg_text = "~Check out this site: https://conversejs.org~"
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 3);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
@ -116,7 +116,7 @@ describe("An incoming chat Message", function () {
msg_text = `*${base_url}/logo/conversejs-filled.svg*`; msg_text = `*${base_url}/logo/conversejs-filled.svg*`;
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 4);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
@ -128,7 +128,7 @@ describe("An incoming chat Message", function () {
msg_text = `~ Hello! :poop: ~`; msg_text = `~ Hello! :poop: ~`;
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 5);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
@ -138,7 +138,7 @@ describe("An incoming chat Message", function () {
msg_text = "This *is not a styling hint \n * _But this is_!"; msg_text = "This *is not a styling hint \n * _But this is_!";
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 6);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
@ -148,7 +148,7 @@ describe("An incoming chat Message", function () {
msg_text = `(There are three blocks in this body marked by parens,)\n (but there is no *formatting)\n (as spans* may not escape blocks.)\n ~strikethrough~`; msg_text = `(There are three blocks in this body marked by parens,)\n (but there is no *formatting)\n (as spans* may not escape blocks.)\n ~strikethrough~`;
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 7);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
@ -161,7 +161,7 @@ describe("An incoming chat Message", function () {
msg_text = `__ hello world _`; msg_text = `__ hello world _`;
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 8);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
@ -171,7 +171,7 @@ describe("An incoming chat Message", function () {
msg_text = `Go to ~https://conversejs.org~now _please_`; msg_text = `Go to ~https://conversejs.org~now _please_`;
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 9);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
@ -180,7 +180,7 @@ describe("An incoming chat Message", function () {
msg_text = `Go to _https://converse_js.org_ _please_`; msg_text = `Go to _https://converse_js.org_ _please_`;
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 10);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
@ -204,7 +204,7 @@ describe("An incoming chat Message", function () {
msg_text = `Here's a code block: \n\`\`\`\nInside the code-block, <code>hello</code> we don't enable *styling hints* like ~these~\n\`\`\``; msg_text = `Here's a code block: \n\`\`\`\nInside the code-block, <code>hello</code> we don't enable *styling hints* like ~these~\n\`\`\``;
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
@ -216,7 +216,7 @@ describe("An incoming chat Message", function () {
msg_text = "```\nignored\n(println \"Hello, world!\")\n```\nThis should show up as monospace, preformatted text ^"; msg_text = "```\nignored\n(println \"Hello, world!\")\n```\nThis should show up as monospace, preformatted text ^";
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 2);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
@ -229,7 +229,7 @@ describe("An incoming chat Message", function () {
msg_text = "```ignored\n (println \"Hello, world!\")\n ```\n\n This should not show up as monospace, *preformatted* text ^"; msg_text = "```ignored\n (println \"Hello, world!\")\n ```\n\n This should not show up as monospace, *preformatted* text ^";
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 3);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
@ -252,7 +252,7 @@ describe("An incoming chat Message", function () {
msg_text = `> This is quoted text\n>This is also quoted\nThis is not quoted`; msg_text = `> This is quoted text\n>This is also quoted\nThis is not quoted`;
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 1);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
@ -261,7 +261,7 @@ describe("An incoming chat Message", function () {
msg_text = `> This is *quoted* text\n>This is \`also _quoted_\`\nThis is not quoted`; msg_text = `> This is *quoted* text\n>This is \`also _quoted_\`\nThis is not quoted`;
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 2);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
@ -272,7 +272,7 @@ describe("An incoming chat Message", function () {
msg_text = `> > This is doubly quoted text`; msg_text = `> > This is doubly quoted text`;
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 3);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === "<blockquote><blockquote>This is doubly quoted text</blockquote></blockquote>"); await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === "<blockquote><blockquote>This is doubly quoted text</blockquote></blockquote>");
@ -280,7 +280,7 @@ describe("An incoming chat Message", function () {
msg_text = `>> This is doubly quoted text`; msg_text = `>> This is doubly quoted text`;
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 4);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === "<blockquote><blockquote>This is doubly quoted text</blockquote></blockquote>"); await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === "<blockquote><blockquote>This is doubly quoted text</blockquote></blockquote>");
@ -288,7 +288,7 @@ describe("An incoming chat Message", function () {
msg_text = ">```\n>ignored\n> <span></span> (println \"Hello, world!\")\n>```\n> This should show up as monospace, preformatted text ^"; msg_text = ">```\n>ignored\n> <span></span> (println \"Hello, world!\")\n>```\n> This should show up as monospace, preformatted text ^";
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 5);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
@ -302,7 +302,7 @@ describe("An incoming chat Message", function () {
msg_text = '> ```\n> (println "Hello, world!")\n\nThe entire blockquote is a preformatted text block, but this line is plaintext!'; msg_text = '> ```\n> (println "Hello, world!")\n\nThe entire blockquote is a preformatted text block, but this line is plaintext!';
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 6);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
@ -312,7 +312,7 @@ describe("An incoming chat Message", function () {
msg_text = '> Also, icons.js is loaded from /dist, instead of dist.\nhttps://conversejs.org/docs/html/configuration.html#assets-path' msg_text = '> Also, icons.js is loaded from /dist, instead of dist.\nhttps://conversejs.org/docs/html/configuration.html#assets-path'
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 7);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
@ -322,7 +322,7 @@ describe("An incoming chat Message", function () {
msg_text = '> Where is it located?\ngeo:37.786971,-122.399677'; msg_text = '> Where is it located?\ngeo:37.786971,-122.399677';
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 8);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
@ -333,7 +333,7 @@ describe("An incoming chat Message", function () {
msg_text = '> What do you think of it?\n :poop:'; msg_text = '> What do you think of it?\n :poop:';
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 9);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
@ -342,7 +342,7 @@ describe("An incoming chat Message", function () {
msg_text = '> What do you think of it?\n~hello~'; msg_text = '> What do you think of it?\n~hello~';
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 10);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===
@ -351,7 +351,7 @@ describe("An incoming chat Message", function () {
msg_text = 'hello world > this is not a quote'; msg_text = 'hello world > this is not a quote';
msg = mock.createChatMessage(_converse, contact_jid, msg_text) msg = mock.createChatMessage(_converse, contact_jid, msg_text)
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 11);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === 'hello world &gt; this is not a quote'); await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === 'hello world &gt; this is not a quote');
@ -379,7 +379,7 @@ describe("An incoming chat Message", function () {
}).nodeTree; }).nodeTree;
await _converse.handleMessageStanza(msg); await _converse.handleMessageStanza(msg);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 12);
msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop();
expect(msg_el.innerText).toBe(msg_text); expect(msg_el.innerText).toBe(msg_text);
await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') === await u.waitUntil(() => msg_el.innerHTML.replace(/<!---->/g, '') ===

View File

@ -9,18 +9,29 @@ export default class ChatContent extends CustomElement {
static get properties () { static get properties () {
return { return {
chatview: { type: Object}, chatview: { type: Object}
messages: { type: Array}, }
notifications: { type: String } }
connectedCallback () {
super.connectedCallback();
const model = this.chatview.model;
this.listenTo(model.messages, 'add', this.requestUpdate);
this.listenTo(model.messages, 'change', this.requestUpdate);
this.listenTo(model.messages, 'remove', this.requestUpdate);
this.listenTo(model.messages, 'reset', this.requestUpdate);
this.listenTo(model.notifications, 'change', this.requestUpdate);
if (model.occupants) {
this.listenTo(model.occupants, 'change', this.requestUpdate);
} }
} }
render () { render () {
const notifications = xss.filterXSS(this.notifications, {'whiteList': {}}); const notifications = xss.filterXSS(this.chatview.getNotifications(), {'whiteList': {}});
return html` return html`
<converse-message-history <converse-message-history
.chatview=${this.chatview} .chatview=${this.chatview}
.messages=${this.messages}> .messages=${[...this.chatview.model.messages.models]}>
</converse-message-history> </converse-message-history>
<div class="chat-content__notifications">${unsafeHTML(notifications)}</div> <div class="chat-content__notifications">${unsafeHTML(notifications)}</div>
`; `;

View File

@ -90,19 +90,6 @@ export default class Message extends CustomElement {
vcard && this.listenTo(vcard, 'change', () => this.requestUpdate()); vcard && this.listenTo(vcard, 'change', () => this.requestUpdate());
} }
updated () {
// XXX: This is ugly but tests rely on this event.
// For "normal" chat messages the event is fired in
// src/templates/directives/body.js
if (
this.show_spinner ||
(this.model.get('file') && !this.model.get('oob_url')) ||
(['error', 'info'].includes(this.message_type))
) {
this.model.collection?.trigger('rendered', this.model);
}
}
renderInfoMessage () { renderInfoMessage () {
const isodate = dayjs(this.model.get('time')).toISOString(); const isodate = dayjs(this.model.get('time')).toISOString();
const i18n_retry = __('Retry'); const i18n_retry = __('Retry');

View File

@ -89,7 +89,8 @@ const MessageMixin = {
*/ */
mayBeRetracted () { mayBeRetracted () {
const is_own_message = this.get('sender') === 'me'; const is_own_message = this.get('sender') === 'me';
return is_own_message && ['all', 'own'].includes(api.settings.get('allow_message_retraction')); const not_canceled = this.get('error_type') !== 'cancel';
return is_own_message && not_canceled && ['all', 'own'].includes(api.settings.get('allow_message_retraction'));
}, },
safeDestroy () { safeDestroy () {

View File

@ -61,10 +61,7 @@ export default class ChatView extends BaseChatView {
// Need to be registered after render has been called. // Need to be registered after render has been called.
this.listenTo(this.model.messages, 'add', this.onMessageAdded); this.listenTo(this.model.messages, 'add', this.onMessageAdded);
this.listenTo(this.model.messages, 'remove', this.renderChatHistory);
this.listenTo(this.model.messages, 'rendered', this.maybeScrollDown); this.listenTo(this.model.messages, 'rendered', this.maybeScrollDown);
this.listenTo(this.model.messages, 'reset', this.renderChatHistory);
this.listenTo(this.model.notifications, 'change', this.renderNotifications);
this.listenTo(this.model, 'change:show_help_messages', this.renderHelpMessages); this.listenTo(this.model, 'change:show_help_messages', this.renderHelpMessages);
await this.model.messages.fetched; await this.model.messages.fetched;
@ -79,13 +76,15 @@ export default class ChatView extends BaseChatView {
} }
render () { render () {
const result = tpl_chatbox(Object.assign(this.model.toJSON(), { 'markScrolled': ev => this.markScrolled(ev) })); const result = tpl_chatbox(Object.assign(
this.model.toJSON(), {
'markScrolled': ev => this.markScrolled(ev),
'chatview': this
})
);
render(result, this); render(result, this);
this.content = this.querySelector('.chat-content'); this.content = this.querySelector('.chat-content');
this.notifications = this.querySelector('.chat-content__notifications');
this.msgs_container = this.querySelector('.chat-content__messages');
this.help_container = this.querySelector('.chat-content__help'); this.help_container = this.querySelector('.chat-content__help');
this.renderChatContent();
this.renderMessageForm(); this.renderMessageForm();
this.renderHeading(); this.renderHeading();
return this; return this;

View File

@ -51,17 +51,13 @@ class HeadlinesView extends BaseChatView {
this.setAttribute('id', this.model.get('box_id')); this.setAttribute('id', this.model.get('box_id'));
const result = tpl_chatbox( const result = tpl_chatbox(
Object.assign(this.model.toJSON(), { Object.assign(this.model.toJSON(), {
info_close: '', chatview: this,
label_personal_message: '',
show_send_button: false, show_send_button: false,
show_toolbar: false, show_toolbar: false,
unread_msgs: ''
}) })
); );
render(result, this); render(result, this);
this.content = this.querySelector('.chat-content'); this.content = this.querySelector('.chat-content');
this.msgs_container = this.querySelector('.chat-content__messages');
this.renderChatContent();
this.renderHeading(); this.renderHeading();
return this; return this;
} }

View File

@ -108,20 +108,14 @@ export default class MUCView extends BaseChatView {
// Need to be registered after render has been called. // Need to be registered after render has been called.
this.listenTo(this.model, 'change:show_help_messages', this.renderHelpMessages); this.listenTo(this.model, 'change:show_help_messages', this.renderHelpMessages);
this.listenTo(this.model.messages, 'add', this.onMessageAdded); this.listenTo(this.model.messages, 'add', this.onMessageAdded);
this.listenTo(this.model.messages, 'change', this.renderChatHistory);
this.listenTo(this.model.messages, 'remove', this.renderChatHistory);
this.listenTo(this.model.messages, 'reset', this.renderChatHistory);
this.listenTo(this.model.notifications, 'change', this.renderNotifications);
this.model.occupants.forEach(o => this.onOccupantAdded(o)); this.model.occupants.forEach(o => this.onOccupantAdded(o));
this.listenTo(this.model.occupants, 'add', this.onOccupantAdded); this.listenTo(this.model.occupants, 'add', this.onOccupantAdded);
this.listenTo(this.model.occupants, 'change', this.renderChatHistory);
this.listenTo(this.model.occupants, 'change:affiliation', this.onOccupantAffiliationChanged); this.listenTo(this.model.occupants, 'change:affiliation', this.onOccupantAffiliationChanged);
this.listenTo(this.model.occupants, 'change:role', this.onOccupantRoleChanged); this.listenTo(this.model.occupants, 'change:role', this.onOccupantRoleChanged);
this.listenTo(this.model.occupants, 'change:show', this.showJoinOrLeaveNotification); this.listenTo(this.model.occupants, 'change:show', this.showJoinOrLeaveNotification);
this.listenTo(this.model.occupants, 'remove', this.onOccupantRemoved); this.listenTo(this.model.occupants, 'remove', this.onOccupantRemoved);
this.renderChatContent();
// Register later due to await // Register later due to await
const user_settings = await _converse.api.user.settings.getModel(); const user_settings = await _converse.api.user.settings.getModel();
this.listenTo(user_settings, 'change:mucs_with_hidden_subject', this.renderHeading); this.listenTo(user_settings, 'change:mucs_with_hidden_subject', this.renderHeading);
@ -143,6 +137,7 @@ export default class MUCView extends BaseChatView {
render( render(
tpl_chatroom({ tpl_chatroom({
sidebar_hidden, sidebar_hidden,
'chatview': this,
'model': this.model, 'model': this.model,
'occupants': this.model.occupants, 'occupants': this.model.occupants,
'show_sidebar': 'show_sidebar':
@ -157,7 +152,6 @@ export default class MUCView extends BaseChatView {
this.notifications = this.querySelector('.chat-content__notifications'); this.notifications = this.querySelector('.chat-content__notifications');
this.content = this.querySelector('.chat-content'); this.content = this.querySelector('.chat-content');
this.msgs_container = this.querySelector('.chat-content__messages');
this.help_container = this.querySelector('.chat-content__help'); this.help_container = this.querySelector('.chat-content__help');
this.renderBottomPanel(); this.renderBottomPanel();

View File

@ -15,16 +15,6 @@ export default class BaseChatView extends ElementView {
initDebounced () { initDebounced () {
this.markScrolled = debounce(this._markScrolled, 100); this.markScrolled = debounce(this._markScrolled, 100);
this.debouncedScrollDown = debounce(this.scrollDown, 100); this.debouncedScrollDown = debounce(this.scrollDown, 100);
// For tests that use Jasmine.Clock we want to turn of
// debouncing, since setTimeout breaks.
if (api.settings.get('debounced_content_rendering')) {
this.renderChatHistory = debounce(() => this.renderChatContent(false), 100);
this.renderNotifications = debounce(() => this.renderChatContent(true), 100);
} else {
this.renderChatHistory = () => this.renderChatContent(false);
this.renderNotifications = () => this.renderChatContent(true);
}
} }
async renderHeading () { async renderHeading () {
@ -32,20 +22,6 @@ export default class BaseChatView extends ElementView {
render(tpl, this.querySelector('.chat-head-chatbox')); render(tpl, this.querySelector('.chat-head-chatbox'));
} }
renderChatContent (msgs_by_ref = false) {
if (!this.tpl_chat_content) {
this.tpl_chat_content = o => {
return html`
<converse-chat-content .chatview=${this} .messages=${o.messages} notifications=${o.notifications}>
</converse-chat-content>
`;
};
}
const msg_models = this.model.messages.models;
const messages = msgs_by_ref ? msg_models : Array.from(msg_models);
render(this.tpl_chat_content({ messages, 'notifications': this.getNotifications() }), this.msgs_container);
}
renderHelpMessages () { renderHelpMessages () {
render( render(
html` html`
@ -183,7 +159,8 @@ export default class BaseChatView extends ElementView {
maintainScrollTop () { maintainScrollTop () {
const pos = this.model.get('scrollTop'); const pos = this.model.get('scrollTop');
if (pos) { if (pos) {
this.msgs_container.scrollTop = pos; const msgs_container = this.querySelector('.chat-content__messages');
msgs_container.scrollTop = pos;
} else { } else {
this.scrollDown(); this.scrollDown();
} }
@ -312,8 +289,6 @@ export default class BaseChatView extends ElementView {
} }
onMessageAdded (message) { onMessageAdded (message) {
this.renderChatHistory();
if (u.isNewMessage(message)) { if (u.isNewMessage(message)) {
if (message.get('sender') === 'me') { if (message.get('sender') === 'me') {
// We remove the "scrolled" flag so that the chat area // We remove the "scrolled" flag so that the chat area
@ -403,13 +378,14 @@ export default class BaseChatView extends ElementView {
_markScrolled (ev) { _markScrolled (ev) {
let scrolled = true; let scrolled = true;
let scrollTop = null; let scrollTop = null;
const msgs_container = this.querySelector('.chat-content__messages');
const is_at_bottom = const is_at_bottom =
this.msgs_container.scrollTop + this.msgs_container.clientHeight >= this.msgs_container.scrollHeight - 62; // sigh... msgs_container.scrollTop + msgs_container.clientHeight >= msgs_container.scrollHeight - 62; // sigh...
if (is_at_bottom) { if (is_at_bottom) {
scrolled = false; scrolled = false;
this.onScrolledDown(); this.onScrolledDown();
} else if (this.msgs_container.scrollTop === 0) { } else if (msgs_container.scrollTop === 0) {
/** /**
* Triggered once the chat's message area has been scrolled to the top * Triggered once the chat's message area has been scrolled to the top
* @event _converse#chatBoxScrolledUp * @event _converse#chatBoxScrolledUp
@ -439,11 +415,12 @@ export default class BaseChatView extends ElementView {
'scrollTop': null 'scrollTop': null
}); });
} }
if (this.msgs_container.scrollTo) { const msgs_container = this.querySelector('.chat-content__messages');
const behavior = this.msgs_container.scrollTop ? 'smooth' : 'auto'; if (msgs_container.scrollTo) {
this.msgs_container.scrollTo({ 'top': this.msgs_container.scrollHeight, behavior }); const behavior = msgs_container.scrollTop ? 'smooth' : 'auto';
msgs_container.scrollTo({ 'top': msgs_container.scrollHeight, behavior });
} else { } else {
this.msgs_container.scrollTop = this.msgs_container.scrollHeight; msgs_container.scrollTop = msgs_container.scrollHeight;
} }
this.onScrolledDown(); this.onScrolledDown();
} }
@ -524,14 +501,16 @@ export default class BaseChatView extends ElementView {
if (api.settings.get('view_mode') === 'overlayed') { if (api.settings.get('view_mode') === 'overlayed') {
// XXX: Chrome flexbug workaround. The .chat-content area // XXX: Chrome flexbug workaround. The .chat-content area
// doesn't resize when the textarea is resized to its original size. // doesn't resize when the textarea is resized to its original size.
this.msgs_container.parentElement.style.display = 'none'; const msgs_container = this.querySelector('.chat-content__messages');
msgs_container.parentElement.style.display = 'none';
} }
textarea.removeAttribute('disabled'); textarea.removeAttribute('disabled');
u.removeClass('disabled', textarea); u.removeClass('disabled', textarea);
if (api.settings.get('view_mode') === 'overlayed') { if (api.settings.get('view_mode') === 'overlayed') {
// XXX: Chrome flexbug workaround. // XXX: Chrome flexbug workaround.
this.msgs_container.parentElement.style.display = ''; const msgs_container = this.querySelector('.chat-content__messages');
msgs_container.parentElement.style.display = '';
} }
// Suppress events, otherwise superfluous CSN gets set // Suppress events, otherwise superfluous CSN gets set
// immediately after the message, causing rate-limiting issues. // immediately after the message, causing rate-limiting issues.

View File

@ -6,7 +6,11 @@ export default (o) => html`
<div class="chat-head chat-head-chatbox row no-gutters"></div> <div class="chat-head chat-head-chatbox row no-gutters"></div>
<div class="chat-body"> <div class="chat-body">
<div class="chat-content ${ o.show_send_button ? 'chat-content-sendbutton' : '' }" aria-live="polite"> <div class="chat-content ${ o.show_send_button ? 'chat-content-sendbutton' : '' }" aria-live="polite">
<div class="chat-content__messages" @scroll=${o.markScrolled}></div> <converse-chat-content
class="chat-content__messages"
.chatview=${o.chatview}
@scroll=${o.markScrolled}></converse-chat-content>
<div class="chat-content__help"></div> <div class="chat-content__help"></div>
</div> </div>
<div class="bottom-panel"> <div class="bottom-panel">

View File

@ -7,7 +7,11 @@ export default (o) => html`
<div class="chat-body chatroom-body row no-gutters"> <div class="chat-body chatroom-body row no-gutters">
<div class="chat-area col"> <div class="chat-area col">
<div class="chat-content ${ o.show_send_button ? 'chat-content-sendbutton' : '' }" aria-live="polite"> <div class="chat-content ${ o.show_send_button ? 'chat-content-sendbutton' : '' }" aria-live="polite">
<div class="chat-content__messages" @scroll=${o.markScrolled}></div> <converse-chat-content
class="chat-content__messages"
.chatview=${o.chatview}
@scroll=${o.markScrolled}></converse-chat-content>
<div class="chat-content__help"></div> <div class="chat-content__help"></div>
</div> </div>
<div class="bottom-panel"></div> <div class="bottom-panel"></div>