diff --git a/spec/chatbox.js b/spec/chatbox.js index 8046a30ac..af82ff226 100644 --- a/spec/chatbox.js +++ b/spec/chatbox.js @@ -280,7 +280,7 @@ describe("Chatboxes", function () { keyCode: 13 // Enter }; 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); expect(counter.textContent).toBe('200'); diff --git a/spec/corrections.js b/spec/corrections.js index 96f6b8787..ca676168a 100644 --- a/spec/corrections.js +++ b/spec/corrections.js @@ -27,7 +27,7 @@ describe("A Chat Message", function () { preventDefault: function preventDefault () {}, 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.querySelector('.chat-msg__text').textContent) .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); 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({ target: textarea, preventDefault: function preventDefault () {}, keyCode: 13 // Enter }); 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]; expect(msg.toLocaleString()) @@ -97,13 +98,15 @@ describe("A Chat Message", function () { expect(view.querySelectorAll('.chat-msg').length).toBe(1); 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({ target: textarea, preventDefault: function preventDefault () {}, 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); textarea.value = 'Arise, fair sun, and kill the envious moon'; @@ -112,18 +115,17 @@ describe("A Chat Message", function () { preventDefault: function preventDefault () {}, keyCode: 13 // Enter }); - await new Promise(resolve => view.model.messages.once('rendered', resolve)); - expect(view.querySelectorAll('.chat-msg').length).toBe(3); + await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 3); view.onKeyDown({ target: textarea, keyCode: 38 // Up arrow }); 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(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()), 500); + await u.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg:last', view.el).pop()), 750); textarea.selectionEnd = 0; // Happens by pressing up, // but for some reason not in tests, so we set it manually. @@ -143,7 +145,6 @@ describe("A Chat Message", function () { preventDefault: function preventDefault () {}, keyCode: 13 // Enter }); - await new Promise(resolve => view.model.messages.once('rendered', resolve)); await u.waitUntil(() => textarea.value === ''); const messages = view.querySelectorAll('.chat-msg'); expect(messages.length).toBe(3); @@ -177,12 +178,12 @@ describe("A Chat Message", function () { preventDefault: function preventDefault () {}, 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.querySelector('.chat-msg__text').textContent) .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?'}); 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'))); 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({ target: textarea, preventDefault: function preventDefault () {}, keyCode: 13 // Enter }); 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]; expect(msg.toLocaleString()) @@ -586,7 +589,7 @@ describe("A Groupchat Message", function () { 'to': 'romeo@montague.lit', 'type': 'groupchat' }).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); // Test that pressing the down arrow cancels message correction diff --git a/spec/emojis.js b/spec/emojis.js index 2d552c3a7..53a587b46 100644 --- a/spec/emojis.js +++ b/spec/emojis.js @@ -263,7 +263,7 @@ describe("Emojis", function () { .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()); await new Promise(resolve => _converse.on('chatBoxViewInitialized', resolve)); 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'))); _converse.handleMessageStanza($msg({ @@ -273,7 +273,7 @@ describe("Emojis", function () { 'id': _converse.connection.getUniqueId() }).c('body').t('😇 Hello world! 😇 😇').up() .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'; await u.waitUntil(() => u.hasClass('chat-msg__text--larger', view.content.querySelector(sel))); @@ -287,8 +287,7 @@ describe("Emojis", function () { preventDefault: function preventDefault () {}, keyCode: 13 // Enter }); - await new Promise(resolve => view.model.messages.once('rendered', resolve)); - expect(view.querySelectorAll('.chat-msg').length).toBe(3); + await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 3); const last_msg_sel = 'converse-chat-message:last-child .chat-msg__text'; 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); sel = 'converse-chat-message:last-child .chat-msg' 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({ target: textarea, preventDefault: function preventDefault () {}, 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); let message = view.content.querySelector(last_msg_sel); expect(u.hasClass('chat-msg__text--larger', message)).toBe(false); @@ -318,7 +319,7 @@ describe("Emojis", function () { preventDefault: function preventDefault () {}, 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:'; view.onKeyDown({ @@ -326,7 +327,7 @@ describe("Emojis", function () { preventDefault: function preventDefault () {}, 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'); expect(u.hasClass('chat-msg__text--larger', message)).toBe(true); diff --git a/spec/http-file-upload.js b/spec/http-file-upload.js index 9de6e4cbb..816b01c49 100644 --- a/spec/http-file-upload.js +++ b/spec/http-file-upload.js @@ -237,7 +237,6 @@ describe("XEP-0363: HTTP File Upload", function () { 'name': "my-juliet.jpg" }; 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); const iq = IQ_stanzas.pop(); @@ -270,22 +269,20 @@ describe("XEP-0363: HTTP File Upload", function () { `); - spyOn(XMLHttpRequest.prototype, 'send').and.callFake(function () { + spyOn(XMLHttpRequest.prototype, 'send').and.callFake(async function () { 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); - u.waitUntil(() => view.querySelector('.chat-content progress').getAttribute('value') === '0.5') - .then(() => { - message.set('progress', 1); - u.waitUntil(() => view.querySelector('.chat-content progress').getAttribute('value') === '1') - }).then(() => { - message.save({ - 'upload': _converse.SUCCESS, - 'oob_url': message.get('get'), - 'message': message.get('get') - }); - return new Promise(resolve => view.model.messages.once('rendered', resolve)); + await u.waitUntil(() => view.querySelector('.chat-content progress').getAttribute('value') === '0.5') + message.set('progress', 1); + await u.waitUntil(() => view.querySelector('.chat-content progress').getAttribute('value') === '1') + message.save({ + 'upload': _converse.SUCCESS, + 'oob_url': message.get('get'), + 'message': message.get('get') }); + await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length); }); let sent_stanza; spyOn(_converse.connection, 'send').and.callFake(stanza => (sent_stanza = stanza)); @@ -319,8 +316,7 @@ describe("XEP-0363: HTTP File Upload", function () { 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'; await mock.waitUntilDiscoConfirmed( _converse, _converse.domain, @@ -346,7 +342,6 @@ describe("XEP-0363: HTTP File Upload", function () { 'name': "my-juliet.jpg" }; 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); const iq = IQ_stanzas.pop(); @@ -378,22 +373,20 @@ describe("XEP-0363: HTTP File Upload", function () { `); - spyOn(XMLHttpRequest.prototype, 'send').and.callFake(function () { + spyOn(XMLHttpRequest.prototype, 'send').and.callFake(async function () { 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); - u.waitUntil(() => view.querySelector('.chat-content progress').getAttribute('value') === '0.5') - .then(() => { - message.set('progress', 1); - u.waitUntil(() => view.querySelector('.chat-content progress')?.getAttribute('value') === '1') - }).then(() => { - message.save({ - 'upload': _converse.SUCCESS, - 'oob_url': message.get('get'), - 'message': message.get('get') - }); - return new Promise(resolve => view.model.messages.once('rendered', resolve)); + await u.waitUntil(() => view.querySelector('.chat-content progress').getAttribute('value') === '0.5') + message.set('progress', 1); + await u.waitUntil(() => view.querySelector('.chat-content progress')?.getAttribute('value') === '1') + message.save({ + 'upload': _converse.SUCCESS, + 'oob_url': message.get('get'), + 'message': message.get('get') }); + await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length); }); let sent_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" }; 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(() => IQ_stanzas.filter(iq => iq.querySelector('iq[to="upload.montague.tld"] request')).length) const iq = IQ_stanzas.pop(); expect(Strophe.serialize(iq)).toBe( ` { 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); await u.waitUntil(() => view.querySelector('.chat-content progress').getAttribute('value') === '0.5'); message.set('progress', 1); diff --git a/spec/mam.js b/spec/mam.js index 93eec9c41..a90f4bead 100644 --- a/spec/mam.js +++ b/spec/mam.js @@ -64,7 +64,7 @@ describe("Message Archive Management", function () { .c('count').t('16'); _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); while (sent_IQs.length) { sent_IQs.pop(); } @@ -379,7 +379,8 @@ describe("Message Archive Management", function () { .c('count').t('16'); _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.at(0).get('message')).toBe("Thrice the brinded cat hath mew'd."); done(); @@ -433,7 +434,7 @@ describe("Message Archive Management", function () { .c('count').t('16'); _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.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."); diff --git a/spec/markers.js b/spec/markers.js index 12aa380fc..0c7c267ca 100644 --- a/spec/markers.js +++ b/spec/markers.js @@ -91,7 +91,7 @@ describe("A XEP-0333 Chat Marker", function () { `); _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.model.messages.length).toBe(1); @@ -130,9 +130,9 @@ describe("A XEP-0333 Chat Marker", function () { preventDefault: function preventDefault () {}, 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.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?"); const msg_obj = view.model.messages.at(0); diff --git a/spec/me-messages.js b/spec/me-messages.js index 2868718c0..7a24db937 100644 --- a/spec/me-messages.js +++ b/spec/me-messages.js @@ -23,8 +23,8 @@ describe("A Groupchat Message", function () { }).c('body').t(message).tree(); await view.model.handleMessageStanza(msg); 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__text').textContent.trim()).toBe('is tired'); message = '/me is as well'; msg = $msg({ @@ -35,8 +35,8 @@ describe("A Groupchat Message", function () { }).c('body').t(message).tree(); await view.model.handleMessageStanza(msg); 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__text:last', view).pop().textContent.trim()).toBe('is as well'); // Check rendering of a mention inside a me message const msg_text = "/me mentions romeo"; @@ -77,8 +77,8 @@ describe("A Message", function () { const view = _converse.chatboxviews.get(sender_jid); await u.waitUntil(() => view.querySelector('.chat-msg__text')); 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__text').textContent).toBe('is tired'); message = '/me is as well'; await mock.sendMessage(view, message); diff --git a/spec/mentions.js b/spec/mentions.js index 79c6732c3..fef5e292b 100644 --- a/spec/mentions.js +++ b/spec/mentions.js @@ -1,6 +1,6 @@ /*global mock, converse */ -const { Promise, Strophe, $msg, $pres } = converse.env; +const { Strophe, $msg, $pres } = converse.env; const u = converse.env.utils; @@ -22,7 +22,7 @@ describe("An incoming groupchat message", function () { type: 'groupchat' }).c('body').t(message).tree(); 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(); 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':'15', 'end':'23', 'type':'mention', 'uri':'xmpp:mr.robot@montague.lit'}).nodeTree; await view.model.handleMessageStanza(msg); - let message = await u.waitUntil(() => view.querySelector('.chat-msg__text')); - expect(message.classList.length).toEqual(1); - expect(message.innerHTML.replace(//g, '')).toBe( + await u.waitUntil(() => view.querySelector('.chat-msg__text')?.innerHTML.replace(//g, '') === 'hello z3r0 '+ 'tom '+ 'mr.robot, how are you?'); + let message = view.querySelector('.chat-msg__text') + expect(message.classList.length).toEqual(1); msg = $msg({ 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; await view.model.handleMessageStanza(msg); - const message = await u.waitUntil(() => view.querySelector('.chat-msg__text')); - expect(message.classList.length).toEqual(1); - expect(message.innerHTML.replace(//g, '')).toBe( + await u.waitUntil(() => view.querySelector('.chat-msg__text')?.innerHTML.replace(//g, '') === '
hello z3r0 tom mr.robot, how are you?
'); + const message = view.querySelector('.chat-msg__text'); + expect(message.classList.length).toEqual(1); done(); })); }); @@ -316,7 +316,7 @@ describe("A sent groupchat message", function () { } spyOn(_converse.connection, 'send'); 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]; expect(msg.toLocaleString()) .toBe(` 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'; await u.waitUntil(() => @@ -458,7 +458,7 @@ describe("A sent groupchat message", function () { 'keyCode': 13 // Enter } 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]; expect(msg.toLocaleString()) diff --git a/spec/messages.js b/spec/messages.js index a045f7bc4..b75b2113c 100644 --- a/spec/messages.js +++ b/spec/messages.js @@ -111,7 +111,7 @@ describe("A Chat Message", function () { .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2017-12-31T22:08:25Z'}) .tree(); _converse.handleMessageStanza(msg); - await new Promise(resolve => view.model.messages.once('rendered', resolve)); + await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 2); msg = $msg({ 'xmlns': 'jabber:client', @@ -123,7 +123,7 @@ describe("A Chat Message", function () { .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-01T13:18:23Z'}) .tree(); _converse.handleMessageStanza(msg); - await new Promise(resolve => view.model.messages.once('rendered', resolve)); + await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 3); msg = $msg({ 'xmlns': 'jabber:client', @@ -135,7 +135,7 @@ describe("A Chat Message", function () { .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-01T13:18:23Z'}) .tree(); _converse.handleMessageStanza(msg); - await new Promise(resolve => view.model.messages.once('rendered', resolve)); + await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 4); msg = $msg({ 'xmlns': 'jabber:client', @@ -147,7 +147,7 @@ describe("A Chat Message", function () { .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-02T12:18:23Z'}) .tree(); _converse.handleMessageStanza(msg); - await new Promise(resolve => view.model.messages.once('rendered', resolve)); + await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 5); msg = $msg({ 'xmlns': 'jabber:client', @@ -159,7 +159,7 @@ describe("A Chat Message", function () { .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-02T22:28:23Z'}) .tree(); _converse.handleMessageStanza(msg); - await new Promise(resolve => view.model.messages.once('rendered', resolve)); + await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 6); // Insert message, to also check that // text messages are inserted correctly with @@ -185,8 +185,9 @@ describe("A Chat Message", function () { .c('composing', {'xmlns': Strophe.NS.CHATSTATES}).up() .c('body').t("latest message") .tree(); + 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 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('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree(); 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(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('is_delayed')).toEqual(true); await u.waitUntil(() => chatbox.vcard.get('fullname') === 'Juliet Capulet') - expect(view.msgs_container.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.msgs_container.querySelector('span.chat-msg__author').textContent.trim()).toBe('Juliet Capulet'); + expect(view.querySelector('.chat-msg .chat-msg__text').textContent).toEqual(message); + expect(view.querySelector('.chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy(); + expect(view.querySelector('span.chat-msg__author').textContent.trim()).toBe('Juliet Capulet'); - expect(view.msgs_container.querySelectorAll('.date-separator').length).toEqual(1); - let day = view.msgs_container.querySelector('.date-separator'); + expect(view.querySelectorAll('.date-separator').length).toEqual(1); + let day = view.querySelector('.date-separator'); expect(day.getAttribute('class')).toEqual('message date-separator'); 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")); message = 'This is a current message'; @@ -467,19 +468,19 @@ describe("A Chat Message", function () { }).c('body').t(message).up() .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree(); 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)); // Check that there is a `); _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?'); stanza = u.toStanza(` Hey\n\n\nHave you heard the news? `); _converse.connection._dataRecv(mock.createRequest(stanza)); - await new Promise(resolve => view.model.messages.once('rendered', resolve)); - 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?'); + await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 2); + 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(` Hey\nHave you heard\nthe news? `); _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?'); stanza = u.toStanza(` @@ -605,7 +606,7 @@ describe("A Chat Message", function () { Hey\nHave you heard\n\n\nthe news?\nhttps://conversejs.org `); _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(() => { 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?\nhttps://conversejs.org'; @@ -1067,7 +1068,7 @@ describe("A Chat Message", function () { await _converse.handleMessageStanza(msg); 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)); // Check that the chatbox and its view now exist @@ -1081,7 +1082,7 @@ describe("A Chat Message", function () { expect(_converse.api.vcard.get).toHaveBeenCalled(); await u.waitUntil(() => chatbox.vcard.get('fullname') === mock.cur_names[0]) author_el = view.querySelector('.chat-msg__author'); - expect( _.includes(author_el.textContent.trim(), 'Mercutio')).toBeTruthy(); + expect(author_el.textContent.trim().includes('Mercutio')).toBeTruthy(); done(); })); }); @@ -1119,7 +1120,7 @@ describe("A Chat Message", function () { _converse.allow_non_roster_messaging = true; await _converse.handleMessageStanza(msg); 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)); // Check that the chatbox and its view now exist 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'; const view = _converse.api.chatviews.get(sender_jid); 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; expect(msg_txt).toEqual(msg_text); @@ -1253,18 +1254,14 @@ describe("A Chat Message", function () { .t('Something else went wrong as well'); _converse.connection._dataRecv(mock.createRequest(stanza)); await u.waitUntil(() => view.model.messages.length > 2); - await new Promise(resolve => view.model.messages.once('rendered', resolve)); - expect(view.content.querySelectorAll('.chat-msg__error').length).toEqual(3); + await u.waitUntil(() => view.querySelectorAll('.chat-msg__error').length === 3); - // Ensure messages with error are not editable - document.querySelectorAll('.chat-msg__actions').forEach(elem => { - expect(elem.querySelector('.chat-msg__action-edit')).toBe(null) + // Ensure messages with error are not editable or retractable + await u.waitUntil(() => !view.model.messages.models.reduce((acc, m) => acc || m.get('editable'), false), 1000); + 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(); })); @@ -1302,7 +1299,7 @@ describe("A Chat Message", function () { const view = _converse.chatboxviews.get(contact_jid); const msg_text = 'This message will show!'; 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); done(); })); diff --git a/spec/muc.js b/spec/muc.js index c0053056b..60d026dbc 100644 --- a/spec/muc.js +++ b/spec/muc.js @@ -664,7 +664,7 @@ describe("Groupchats", function () { This is a message `); _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).pop().textContent.trim()).toBe('This is a message subject'); expect(sizzle('.chat-msg__text').length).toBe(1); @@ -695,7 +695,7 @@ describe("Groupchats", function () { This is a message `); _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).pop().textContent.trim()).toBe('This is a message subject'); expect(sizzle('.chat-msg__text').length).toBe(1); @@ -1009,7 +1009,7 @@ describe("Groupchats", function () { 'type': 'groupchat' }).c('body').t('hello world').tree(); _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 // collapsed if "newguy" leaves immediately again @@ -2112,7 +2112,7 @@ describe("Groupchats", function () { preventDefault: function preventDefault () {}, 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(view.content.querySelectorAll('.chat-msg').length).toBe(1); @@ -2170,7 +2170,7 @@ describe("Groupchats", function () { type: 'groupchat', id: u.getUniqueId(), }).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 const msg_txt = sizzle('.chat-msg:last .chat-msg__text', view.content).pop().textContent; expect(msg_txt).toEqual(message); @@ -2858,8 +2858,8 @@ describe("Groupchats", function () { 'role': 'participant' }); _converse.connection._dataRecv(mock.createRequest(presence)); - const csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent); - expect(csntext.trim()).toEqual("romeo and annoyingGuy have entered the groupchat"); + await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === + "romeo and annoyingGuy have entered the groupchat"); presence = $pres({ 'from': 'lounge@montague.lit/annoyingGuy', @@ -2906,6 +2906,7 @@ describe("Groupchats", function () { Array.from(view.querySelectorAll('.chat-info__message')).pop()?.textContent.trim() === "annoyingGuy is no longer a member of this groupchat" ); + expect(1).toBe(1); done(); })); @@ -3614,8 +3615,8 @@ describe("Groupchats", function () { 'role': 'participant' }); _converse.connection._dataRecv(mock.createRequest(presence)); - const csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent); - expect(csntext.trim()).toEqual("romeo and trustworthyguy have entered the groupchat"); + await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === + "romeo and trustworthyguy have entered the groupchat"); const textarea = view.querySelector('.chat-textarea') textarea.value = '/op'; @@ -3753,8 +3754,8 @@ describe("Groupchats", function () { 'role': 'participant' }); _converse.connection._dataRecv(mock.createRequest(presence)); - const csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent); - expect(csntext.trim()).toEqual("romeo and annoyingGuy have entered the groupchat"); + await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === + "romeo and annoyingGuy have entered the groupchat"); const textarea = view.querySelector('.chat-textarea') textarea.value = '/mute'; diff --git a/spec/muc_messages.js b/spec/muc_messages.js index f41dfa965..718926dc1 100644 --- a/spec/muc_messages.js +++ b/spec/muc_messages.js @@ -174,8 +174,8 @@ describe("A Groupchat Message", function () { .c('active', {'xmlns': "http://jabber.org/protocol/chatstates"}) .tree(); await view.model.handleMessageStanza(msg); - await new Promise(resolve => view.model.messages.once('rendered', resolve)); - expect(view.querySelector('.chat-msg')).not.toBe(null); + const el = await u.waitUntil(() => view.querySelector('.chat-msg__text')); + expect(el.textContent).toBe(message); done(); })); @@ -399,10 +399,9 @@ describe("A Groupchat Message", function () { type: 'groupchat' }).c('body').t('Another message!').tree(); 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('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'); presence = $pres({ @@ -436,7 +435,7 @@ describe("A Groupchat Message", function () { type: 'groupchat' }).c('body').t('Message from someone not in the MUC right now').tree(); 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(); // 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); diff --git a/spec/retractions.js b/spec/retractions.js index ae6749220..06fc0a33c 100644 --- a/spec/retractions.js +++ b/spec/retractions.js @@ -790,7 +790,7 @@ describe("Message Retractions", function () { expect(view.model.messages.length).toBe(1); 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(); await u.waitUntil(() => u.isVisible(document.querySelector('#converse-modals .modal'))); const submit_button = document.querySelector('#converse-modals .modal button[type="submit"]'); diff --git a/spec/styling.js b/spec/styling.js index f0a590e91..b41c3bbb0 100644 --- a/spec/styling.js +++ b/spec/styling.js @@ -1,6 +1,6 @@ /*global mock, converse */ -const { u, Promise, $msg } = converse.env; +const { u, $msg } = converse.env; 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 = mock.createChatMessage(_converse, contact_jid, msg_text) 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'); expect(msg_el.innerText).toBe(msg_text); 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 = mock.createChatMessage(_converse, contact_jid, msg_text) - await _converse.handleMessageStanza(msg); - await new Promise(resolve => view.model.messages.once('rendered', resolve)); + await _converse.handleMessageStanza(msg); + await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 2); msg_el = Array.from(view.querySelectorAll('converse-chat-message-body')).pop(); expect(msg_el.innerText).toBe(msg_text); 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 = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); 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 = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); await u.waitUntil(() => msg_el.innerHTML.replace(//g, '') === @@ -128,7 +128,7 @@ describe("An incoming chat Message", function () { msg_text = `~ Hello! :poop: ~`; msg = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); 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 = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); 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 = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); await u.waitUntil(() => msg_el.innerHTML.replace(//g, '') === @@ -161,7 +161,7 @@ describe("An incoming chat Message", function () { msg_text = `__ hello world _`; msg = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); 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 = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); 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 = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); 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, hello we don't enable *styling hints* like ~these~\n\`\`\``; msg = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); 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 = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); 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 = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); 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 = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); 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 = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); 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 = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); await u.waitUntil(() => msg_el.innerHTML.replace(//g, '') === "
This is doubly quoted text
"); @@ -280,7 +280,7 @@ describe("An incoming chat Message", function () { msg_text = `>> This is doubly quoted text`; msg = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); await u.waitUntil(() => msg_el.innerHTML.replace(//g, '') === "
This is doubly quoted text
"); @@ -288,7 +288,7 @@ describe("An incoming chat Message", function () { msg_text = ">```\n>ignored\n> (println \"Hello, world!\")\n>```\n> This should show up as monospace, preformatted text ^"; msg = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); 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 = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); 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 = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); 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 = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); 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 = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); 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 = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); 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 = mock.createChatMessage(_converse, contact_jid, msg_text) 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(); expect(msg_el.innerText).toBe(msg_text); await u.waitUntil(() => msg_el.innerHTML.replace(//g, '') === 'hello world > this is not a quote'); @@ -379,7 +379,7 @@ describe("An incoming chat Message", function () { }).nodeTree; 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(); expect(msg_el.innerText).toBe(msg_text); await u.waitUntil(() => msg_el.innerHTML.replace(//g, '') === diff --git a/src/components/chat_content.js b/src/components/chat_content.js index 369729032..527811abf 100644 --- a/src/components/chat_content.js +++ b/src/components/chat_content.js @@ -9,18 +9,29 @@ export default class ChatContent extends CustomElement { static get properties () { return { - chatview: { type: Object}, - messages: { type: Array}, - notifications: { type: String } + chatview: { type: Object} + } + } + + 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 () { - const notifications = xss.filterXSS(this.notifications, {'whiteList': {}}); + const notifications = xss.filterXSS(this.chatview.getNotifications(), {'whiteList': {}}); return html` + .messages=${[...this.chatview.model.messages.models]}>
${unsafeHTML(notifications)}
`; diff --git a/src/components/message.js b/src/components/message.js index 9e4f28fe3..5f0441f3b 100644 --- a/src/components/message.js +++ b/src/components/message.js @@ -90,19 +90,6 @@ export default class Message extends CustomElement { 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 () { const isodate = dayjs(this.model.get('time')).toISOString(); const i18n_retry = __('Retry'); diff --git a/src/headless/plugins/chat/message.js b/src/headless/plugins/chat/message.js index 54f0196d3..bbd742a03 100644 --- a/src/headless/plugins/chat/message.js +++ b/src/headless/plugins/chat/message.js @@ -89,7 +89,8 @@ const MessageMixin = { */ mayBeRetracted () { 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 () { diff --git a/src/plugins/chatview/view.js b/src/plugins/chatview/view.js index 8ebbc620e..e87d7af46 100644 --- a/src/plugins/chatview/view.js +++ b/src/plugins/chatview/view.js @@ -61,10 +61,7 @@ export default class ChatView extends BaseChatView { // Need to be registered after render has been called. 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, 'reset', this.renderChatHistory); - this.listenTo(this.model.notifications, 'change', this.renderNotifications); this.listenTo(this.model, 'change:show_help_messages', this.renderHelpMessages); await this.model.messages.fetched; @@ -79,13 +76,15 @@ export default class ChatView extends BaseChatView { } 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); 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.renderChatContent(); this.renderMessageForm(); this.renderHeading(); return this; diff --git a/src/plugins/headlines-view/view.js b/src/plugins/headlines-view/view.js index 16a82c84b..4eec7fca7 100644 --- a/src/plugins/headlines-view/view.js +++ b/src/plugins/headlines-view/view.js @@ -51,17 +51,13 @@ class HeadlinesView extends BaseChatView { this.setAttribute('id', this.model.get('box_id')); const result = tpl_chatbox( Object.assign(this.model.toJSON(), { - info_close: '', - label_personal_message: '', + chatview: this, show_send_button: false, show_toolbar: false, - unread_msgs: '' }) ); render(result, this); this.content = this.querySelector('.chat-content'); - this.msgs_container = this.querySelector('.chat-content__messages'); - this.renderChatContent(); this.renderHeading(); return this; } diff --git a/src/plugins/muc-views/muc.js b/src/plugins/muc-views/muc.js index 14b9738b3..3df11d086 100644 --- a/src/plugins/muc-views/muc.js +++ b/src/plugins/muc-views/muc.js @@ -108,20 +108,14 @@ export default class MUCView extends BaseChatView { // Need to be registered after render has been called. this.listenTo(this.model, 'change:show_help_messages', this.renderHelpMessages); 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.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:role', this.onOccupantRoleChanged); this.listenTo(this.model.occupants, 'change:show', this.showJoinOrLeaveNotification); this.listenTo(this.model.occupants, 'remove', this.onOccupantRemoved); - this.renderChatContent(); // Register later due to await const user_settings = await _converse.api.user.settings.getModel(); this.listenTo(user_settings, 'change:mucs_with_hidden_subject', this.renderHeading); @@ -143,6 +137,7 @@ export default class MUCView extends BaseChatView { render( tpl_chatroom({ sidebar_hidden, + 'chatview': this, 'model': this.model, 'occupants': this.model.occupants, 'show_sidebar': @@ -157,7 +152,6 @@ export default class MUCView extends BaseChatView { this.notifications = this.querySelector('.chat-content__notifications'); this.content = this.querySelector('.chat-content'); - this.msgs_container = this.querySelector('.chat-content__messages'); this.help_container = this.querySelector('.chat-content__help'); this.renderBottomPanel(); diff --git a/src/shared/chatview.js b/src/shared/chatview.js index 94928e75e..32e3e3d13 100644 --- a/src/shared/chatview.js +++ b/src/shared/chatview.js @@ -15,16 +15,6 @@ export default class BaseChatView extends ElementView { initDebounced () { this.markScrolled = debounce(this._markScrolled, 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 () { @@ -32,20 +22,6 @@ export default class BaseChatView extends ElementView { render(tpl, this.querySelector('.chat-head-chatbox')); } - renderChatContent (msgs_by_ref = false) { - if (!this.tpl_chat_content) { - this.tpl_chat_content = o => { - return html` - - - `; - }; - } - 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 () { render( html` @@ -183,7 +159,8 @@ export default class BaseChatView extends ElementView { maintainScrollTop () { const pos = this.model.get('scrollTop'); if (pos) { - this.msgs_container.scrollTop = pos; + const msgs_container = this.querySelector('.chat-content__messages'); + msgs_container.scrollTop = pos; } else { this.scrollDown(); } @@ -312,8 +289,6 @@ export default class BaseChatView extends ElementView { } onMessageAdded (message) { - this.renderChatHistory(); - if (u.isNewMessage(message)) { if (message.get('sender') === 'me') { // We remove the "scrolled" flag so that the chat area @@ -403,13 +378,14 @@ export default class BaseChatView extends ElementView { _markScrolled (ev) { let scrolled = true; let scrollTop = null; + const msgs_container = this.querySelector('.chat-content__messages'); 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) { scrolled = false; 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 * @event _converse#chatBoxScrolledUp @@ -439,11 +415,12 @@ export default class BaseChatView extends ElementView { 'scrollTop': null }); } - if (this.msgs_container.scrollTo) { - const behavior = this.msgs_container.scrollTop ? 'smooth' : 'auto'; - this.msgs_container.scrollTo({ 'top': this.msgs_container.scrollHeight, behavior }); + const msgs_container = this.querySelector('.chat-content__messages'); + if (msgs_container.scrollTo) { + const behavior = msgs_container.scrollTop ? 'smooth' : 'auto'; + msgs_container.scrollTo({ 'top': msgs_container.scrollHeight, behavior }); } else { - this.msgs_container.scrollTop = this.msgs_container.scrollHeight; + msgs_container.scrollTop = msgs_container.scrollHeight; } this.onScrolledDown(); } @@ -524,14 +501,16 @@ export default class BaseChatView extends ElementView { if (api.settings.get('view_mode') === 'overlayed') { // XXX: Chrome flexbug workaround. The .chat-content area // 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'); u.removeClass('disabled', textarea); if (api.settings.get('view_mode') === 'overlayed') { // 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 // immediately after the message, causing rate-limiting issues. diff --git a/src/templates/chatbox.js b/src/templates/chatbox.js index 62f0118c4..a5b7d2b9d 100644 --- a/src/templates/chatbox.js +++ b/src/templates/chatbox.js @@ -6,7 +6,11 @@ export default (o) => html`
-
+ +
diff --git a/src/templates/chatroom.js b/src/templates/chatroom.js index 41a4a01f1..f0b16b79e 100644 --- a/src/templates/chatroom.js +++ b/src/templates/chatroom.js @@ -7,7 +7,11 @@ export default (o) => html`
-
+ +