Pārlūkot izejas kodu

Show error messages via objects

Instead of injecting them directly into the DOM.
JC Brand 5 gadi atpakaļ
vecāks
revīzija
8a7b25584d

+ 15 - 31
spec/muc.js

@@ -1776,7 +1776,6 @@
                         .t("We didn't like the name").nodeTree;
                         .t("We didn't like the name").nodeTree;
 
 
                 const view = _converse.chatboxviews.get('problematic@muc.montague.lit');
                 const view = _converse.chatboxviews.get('problematic@muc.montague.lit');
-                spyOn(view, 'showErrorMessage').and.callThrough();
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 expect(view.el.querySelector('.chatroom-body .disconnect-msg').textContent.trim())
                 expect(view.el.querySelector('.chatroom-body .disconnect-msg').textContent.trim())
                     .toBe('This groupchat no longer exists');
                     .toBe('This groupchat no longer exists');
@@ -3062,7 +3061,7 @@
                     keyCode: 13
                     keyCode: 13
                 });
                 });
                 expect(_converse.connection.send).not.toHaveBeenCalled();
                 expect(_converse.connection.send).not.toHaveBeenCalled();
-                expect(view.el.querySelectorAll('.chat-error').length).toBe(1);
+                await u.waitUntil(() => view.el.querySelectorAll('.chat-error').length);
                 expect(view.el.querySelector('.chat-error').textContent.trim())
                 expect(view.el.querySelector('.chat-error').textContent.trim())
                     .toBe('Error: couldn\'t find a groupchat participant based on your arguments');
                     .toBe('Error: couldn\'t find a groupchat participant based on your arguments');
 
 
@@ -3266,7 +3265,6 @@
                 await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
                 await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
                 const view = _converse.chatboxviews.get('lounge@montague.lit');
                 const view = _converse.chatboxviews.get('lounge@montague.lit');
                 spyOn(view.model, 'setAffiliation').and.callThrough();
                 spyOn(view.model, 'setAffiliation').and.callThrough();
-                spyOn(view, 'showErrorMessage').and.callThrough();
                 spyOn(view, 'validateRoleOrAffiliationChangeArgs').and.callThrough();
                 spyOn(view, 'validateRoleOrAffiliationChangeArgs').and.callThrough();
 
 
                 let presence = $pres({
                 let presence = $pres({
@@ -3290,17 +3288,20 @@
                     keyCode: 13
                     keyCode: 13
                 });
                 });
                 expect(view.validateRoleOrAffiliationChangeArgs).toHaveBeenCalled();
                 expect(view.validateRoleOrAffiliationChangeArgs).toHaveBeenCalled();
-                expect(view.showErrorMessage).toHaveBeenCalledWith(
+                const err_msg = await u.waitUntil(() => view.el.querySelector('.chat-error'));
+                expect(err_msg.textContent.trim()).toBe(
                     "Error: the \"owner\" command takes two arguments, the user's nickname and optionally a reason.");
                     "Error: the \"owner\" command takes two arguments, the user's nickname and optionally a reason.");
+
                 expect(view.model.setAffiliation).not.toHaveBeenCalled();
                 expect(view.model.setAffiliation).not.toHaveBeenCalled();
                 // XXX: Calling onFormSubmitted directly, trying
                 // XXX: Calling onFormSubmitted directly, trying
                 // again via triggering Event doesn't work for some weird
                 // again via triggering Event doesn't work for some weird
                 // reason.
                 // reason.
                 textarea.value = '/owner nobody You\'re responsible';
                 textarea.value = '/owner nobody You\'re responsible';
                 view.onFormSubmitted(new Event('submit'));
                 view.onFormSubmitted(new Event('submit'));
-
-                expect(view.showErrorMessage).toHaveBeenCalledWith(
+                await u.waitUntil(() => view.el.querySelectorAll('.chat-error').length === 2);
+                expect(Array.from(view.el.querySelectorAll('.chat-error')).pop().textContent.trim()).toBe(
                     "Error: couldn't find a groupchat participant based on your arguments");
                     "Error: couldn't find a groupchat participant based on your arguments");
+
                 expect(view.model.setAffiliation).not.toHaveBeenCalled();
                 expect(view.model.setAffiliation).not.toHaveBeenCalled();
 
 
                 // Call now with the correct of arguments.
                 // Call now with the correct of arguments.
@@ -3312,7 +3313,6 @@
 
 
                 expect(view.validateRoleOrAffiliationChangeArgs.calls.count()).toBe(3);
                 expect(view.validateRoleOrAffiliationChangeArgs.calls.count()).toBe(3);
                 expect(view.model.setAffiliation).toHaveBeenCalled();
                 expect(view.model.setAffiliation).toHaveBeenCalled();
-                expect(view.showErrorMessage.calls.count()).toBe(2);
                 // Check that the member list now gets updated
                 // Check that the member list now gets updated
                 expect(sent_IQ.toLocaleString()).toBe(
                 expect(sent_IQ.toLocaleString()).toBe(
                     `<iq id="${IQ_id}" to="lounge@montague.lit" type="set" xmlns="jabber:client">`+
                     `<iq id="${IQ_id}" to="lounge@montague.lit" type="set" xmlns="jabber:client">`+
@@ -3357,7 +3357,6 @@
                 await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
                 await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
                 const view = _converse.chatboxviews.get('lounge@montague.lit');
                 const view = _converse.chatboxviews.get('lounge@montague.lit');
                 spyOn(view.model, 'setAffiliation').and.callThrough();
                 spyOn(view.model, 'setAffiliation').and.callThrough();
-                spyOn(view, 'showErrorMessage').and.callThrough();
                 spyOn(view, 'validateRoleOrAffiliationChangeArgs').and.callThrough();
                 spyOn(view, 'validateRoleOrAffiliationChangeArgs').and.callThrough();
 
 
                 let presence = $pres({
                 let presence = $pres({
@@ -3381,8 +3380,7 @@
                     keyCode: 13
                     keyCode: 13
                 });
                 });
                 expect(view.validateRoleOrAffiliationChangeArgs).toHaveBeenCalled();
                 expect(view.validateRoleOrAffiliationChangeArgs).toHaveBeenCalled();
-                expect(view.showErrorMessage).toHaveBeenCalled();
-                expect(view.el.querySelector('.message:last-child').textContent.trim()).toBe(
+                await u.waitUntil(() => view.el.querySelector('.message:last-child')?.textContent?.trim() ===
                     "Error: the \"ban\" command takes two arguments, the user's nickname and optionally a reason.");
                     "Error: the \"ban\" command takes two arguments, the user's nickname and optionally a reason.");
 
 
                 expect(view.model.setAffiliation).not.toHaveBeenCalled();
                 expect(view.model.setAffiliation).not.toHaveBeenCalled();
@@ -3394,7 +3392,6 @@
                 view.onFormSubmitted(new Event('submit'));
                 view.onFormSubmitted(new Event('submit'));
 
 
                 expect(view.validateRoleOrAffiliationChangeArgs.calls.count()).toBe(2);
                 expect(view.validateRoleOrAffiliationChangeArgs.calls.count()).toBe(2);
-                expect(view.showErrorMessage.calls.count()).toBe(1);
                 expect(view.model.setAffiliation).toHaveBeenCalled();
                 expect(view.model.setAffiliation).toHaveBeenCalled();
                 // Check that the member list now gets updated
                 // Check that the member list now gets updated
                 expect(sent_IQ.toLocaleString()).toBe(
                 expect(sent_IQ.toLocaleString()).toBe(
@@ -3422,7 +3419,7 @@
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
 
 
                 await u.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 2);
                 await u.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 2);
-                expect(view.el.querySelectorAll('.chat-info__message')[0].textContent.trim()).toBe("annoyingGuy has been banned by romeo");
+                expect(view.el.querySelectorAll('.chat-info__message')[1].textContent.trim()).toBe("annoyingGuy has been banned by romeo");
                 expect(view.el.querySelector('.chat-info:last-child q').textContent.trim()).toBe("You're annoying");
                 expect(view.el.querySelector('.chat-info:last-child q').textContent.trim()).toBe("You're annoying");
                 presence = $pres({
                 presence = $pres({
                         'from': 'lounge@montague.lit/joe2',
                         'from': 'lounge@montague.lit/joe2',
@@ -3439,7 +3436,7 @@
 
 
                 textarea.value = '/ban joe22';
                 textarea.value = '/ban joe22';
                 view.onFormSubmitted(new Event('submit'));
                 view.onFormSubmitted(new Event('submit'));
-                expect(view.el.querySelector('.message:last-child').textContent.trim()).toBe(
+                await u.waitUntil(() => view.el.querySelector('.message:last-child')?.textContent?.trim() ===
                     "Error: couldn't find a groupchat participant based on your arguments");
                     "Error: couldn't find a groupchat participant based on your arguments");
                 done();
                 done();
             }));
             }));
@@ -3461,7 +3458,6 @@
                 await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
                 await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
                 const view = _converse.api.chatviews.get(muc_jid);
                 const view = _converse.api.chatviews.get(muc_jid);
                 spyOn(view.model, 'setRole').and.callThrough();
                 spyOn(view.model, 'setRole').and.callThrough();
-                spyOn(view, 'showErrorMessage').and.callThrough();
                 spyOn(view, 'validateRoleOrAffiliationChangeArgs').and.callThrough();
                 spyOn(view, 'validateRoleOrAffiliationChangeArgs').and.callThrough();
 
 
                 let presence = $pres({
                 let presence = $pres({
@@ -3485,7 +3481,7 @@
                     keyCode: 13
                     keyCode: 13
                 });
                 });
                 expect(view.validateRoleOrAffiliationChangeArgs).toHaveBeenCalled();
                 expect(view.validateRoleOrAffiliationChangeArgs).toHaveBeenCalled();
-                expect(view.showErrorMessage).toHaveBeenCalledWith(
+                await u.waitUntil(() => view.el.querySelector('.message:last-child')?.textContent?.trim() ===
                     "Error: the \"kick\" command takes two arguments, the user's nickname and optionally a reason.");
                     "Error: the \"kick\" command takes two arguments, the user's nickname and optionally a reason.");
                 expect(view.model.setRole).not.toHaveBeenCalled();
                 expect(view.model.setRole).not.toHaveBeenCalled();
                 // Call now with the correct amount of arguments.
                 // Call now with the correct amount of arguments.
@@ -3496,7 +3492,6 @@
                 view.onFormSubmitted(new Event('submit'));
                 view.onFormSubmitted(new Event('submit'));
 
 
                 expect(view.validateRoleOrAffiliationChangeArgs.calls.count()).toBe(2);
                 expect(view.validateRoleOrAffiliationChangeArgs.calls.count()).toBe(2);
-                expect(view.showErrorMessage.calls.count()).toBe(1);
                 expect(view.model.setRole).toHaveBeenCalled();
                 expect(view.model.setRole).toHaveBeenCalled();
                 expect(sent_IQ.toLocaleString()).toBe(
                 expect(sent_IQ.toLocaleString()).toBe(
                     `<iq id="${IQ_id}" to="lounge@montague.lit" type="set" xmlns="jabber:client">`+
                     `<iq id="${IQ_id}" to="lounge@montague.lit" type="set" xmlns="jabber:client">`+
@@ -3532,7 +3527,7 @@
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
 
 
                 await u.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 2);
                 await u.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 2);
-                expect(view.el.querySelectorAll('.chat-info__message')[0].textContent.trim()).toBe("annoying guy has been kicked out by romeo");
+                expect(view.el.querySelectorAll('.chat-info__message')[1].textContent.trim()).toBe("annoying guy has been kicked out by romeo");
                 expect(view.el.querySelector('.chat-info:last-child q').textContent.trim()).toBe("You're annoying");
                 expect(view.el.querySelector('.chat-info:last-child q').textContent.trim()).toBe("You're annoying");
                 done();
                 done();
             }));
             }));
@@ -3553,7 +3548,6 @@
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
                 });
                 spyOn(view.model, 'setRole').and.callThrough();
                 spyOn(view.model, 'setRole').and.callThrough();
-                spyOn(view, 'showErrorMessage').and.callThrough();
                 spyOn(view, 'validateRoleOrAffiliationChangeArgs').and.callThrough();
                 spyOn(view, 'validateRoleOrAffiliationChangeArgs').and.callThrough();
 
 
                 // New user enters the groupchat
                 // New user enters the groupchat
@@ -3590,7 +3584,7 @@
                 });
                 });
 
 
                 expect(view.validateRoleOrAffiliationChangeArgs).toHaveBeenCalled();
                 expect(view.validateRoleOrAffiliationChangeArgs).toHaveBeenCalled();
-                expect(view.showErrorMessage).toHaveBeenCalledWith(
+                await u.waitUntil(() => view.el.querySelector('.message:last-child')?.textContent?.trim() ===
                     "Error: the \"op\" command takes two arguments, the user's nickname and optionally a reason.");
                     "Error: the \"op\" command takes two arguments, the user's nickname and optionally a reason.");
 
 
                 expect(view.model.setRole).not.toHaveBeenCalled();
                 expect(view.model.setRole).not.toHaveBeenCalled();
@@ -3602,7 +3596,6 @@
                 view.onFormSubmitted(new Event('submit'));
                 view.onFormSubmitted(new Event('submit'));
 
 
                 expect(view.validateRoleOrAffiliationChangeArgs.calls.count()).toBe(2);
                 expect(view.validateRoleOrAffiliationChangeArgs.calls.count()).toBe(2);
-                expect(view.showErrorMessage.calls.count()).toBe(1);
                 expect(view.model.setRole).toHaveBeenCalled();
                 expect(view.model.setRole).toHaveBeenCalled();
                 expect(sent_IQ.toLocaleString()).toBe(
                 expect(sent_IQ.toLocaleString()).toBe(
                     `<iq id="${IQ_id}" to="lounge@montague.lit" type="set" xmlns="jabber:client">`+
                     `<iq id="${IQ_id}" to="lounge@montague.lit" type="set" xmlns="jabber:client">`+
@@ -3699,7 +3692,6 @@
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                 });
                 });
                 spyOn(view.model, 'setRole').and.callThrough();
                 spyOn(view.model, 'setRole').and.callThrough();
-                spyOn(view, 'showErrorMessage').and.callThrough();
                 spyOn(view, 'validateRoleOrAffiliationChangeArgs').and.callThrough();
                 spyOn(view, 'validateRoleOrAffiliationChangeArgs').and.callThrough();
 
 
                 // New user enters the groupchat
                 // New user enters the groupchat
@@ -3712,7 +3704,7 @@
                  * </x>
                  * </x>
                  * </presence>
                  * </presence>
                  */
                  */
-                var presence = $pres({
+                let presence = $pres({
                         'from': 'lounge@montague.lit/annoyingGuy',
                         'from': 'lounge@montague.lit/annoyingGuy',
                         'id':'27C55F89-1C6A-459A-9EB5-77690145D624',
                         'id':'27C55F89-1C6A-459A-9EB5-77690145D624',
                         'to': 'romeo@montague.lit/desktop'
                         'to': 'romeo@montague.lit/desktop'
@@ -3736,7 +3728,7 @@
                 });
                 });
 
 
                 expect(view.validateRoleOrAffiliationChangeArgs).toHaveBeenCalled();
                 expect(view.validateRoleOrAffiliationChangeArgs).toHaveBeenCalled();
-                expect(view.showErrorMessage).toHaveBeenCalledWith(
+                await u.waitUntil(() => view.el.querySelector('.message:last-child')?.textContent?.trim() ===
                     "Error: the \"mute\" command takes two arguments, the user's nickname and optionally a reason.");
                     "Error: the \"mute\" command takes two arguments, the user's nickname and optionally a reason.");
                 expect(view.model.setRole).not.toHaveBeenCalled();
                 expect(view.model.setRole).not.toHaveBeenCalled();
                 // Call now with the correct amount of arguments.
                 // Call now with the correct amount of arguments.
@@ -3747,7 +3739,6 @@
                 view.onFormSubmitted(new Event('submit'));
                 view.onFormSubmitted(new Event('submit'));
 
 
                 expect(view.validateRoleOrAffiliationChangeArgs.calls.count()).toBe(2);
                 expect(view.validateRoleOrAffiliationChangeArgs.calls.count()).toBe(2);
-                expect(view.showErrorMessage.calls.count()).toBe(1);
                 expect(view.model.setRole).toHaveBeenCalled();
                 expect(view.model.setRole).toHaveBeenCalled();
                 expect(sent_IQ.toLocaleString()).toBe(
                 expect(sent_IQ.toLocaleString()).toBe(
                     `<iq id="${IQ_id}" to="lounge@montague.lit" type="set" xmlns="jabber:client">`+
                     `<iq id="${IQ_id}" to="lounge@montague.lit" type="set" xmlns="jabber:client">`+
@@ -4060,7 +4051,6 @@
                       .c('error').attrs({by:'lounge@montague.lit', type:'auth'})
                       .c('error').attrs({by:'lounge@montague.lit', type:'auth'})
                           .c('forbidden').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
                           .c('forbidden').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
 
 
-                spyOn(view, 'showErrorMessage').and.callThrough();
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 expect(view.el.querySelector('.chatroom-body .disconnect-container .disconnect-msg:last-child').textContent.trim())
                 expect(view.el.querySelector('.chatroom-body .disconnect-container .disconnect-msg:last-child').textContent.trim())
                     .toBe('You have been banned from this groupchat.');
                     .toBe('You have been banned from this groupchat.');
@@ -4084,7 +4074,6 @@
                           .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
                           .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
 
 
                 const view = _converse.chatboxviews.get(muc_jid);
                 const view = _converse.chatboxviews.get(muc_jid);
-                spyOn(view, 'showErrorMessage').and.callThrough();
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 expect(sizzle('.chatroom-body form.chatroom-form label:first', view.el).pop().textContent.trim())
                 expect(sizzle('.chatroom-body form.chatroom-form label:first', view.el).pop().textContent.trim())
                     .toBe('Please choose your nickname');
                     .toBe('Please choose your nickname');
@@ -4128,7 +4117,6 @@
                         .c('conflict').attrs({'xmlns':'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
                         .c('conflict').attrs({'xmlns':'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
 
 
                 const view = _converse.chatboxviews.get(muc_jid);
                 const view = _converse.chatboxviews.get(muc_jid);
-                spyOn(view, 'showErrorMessage').and.callThrough();
                 spyOn(view.model, 'join').and.callThrough();
                 spyOn(view.model, 'join').and.callThrough();
 
 
                 // Simulate repeatedly that there's already someone in the groupchat
                 // Simulate repeatedly that there's already someone in the groupchat
@@ -4191,7 +4179,6 @@
                     }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
                     }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
                       .c('error').attrs({by:'lounge@montague.lit', type:'cancel'})
                       .c('error').attrs({by:'lounge@montague.lit', type:'cancel'})
                           .c('not-allowed').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
                           .c('not-allowed').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
-                spyOn(view, 'showErrorMessage').and.callThrough();
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 expect(view.el.querySelector('.chatroom-body .disconnect-container .disconnect-msg:last-child').textContent.trim())
                 expect(view.el.querySelector('.chatroom-body .disconnect-container .disconnect-msg:last-child').textContent.trim())
                     .toBe('You are not allowed to create new groupchats.');
                     .toBe('You are not allowed to create new groupchats.');
@@ -4233,7 +4220,6 @@
                       .c('error').attrs({by:'lounge@montague.lit', type:'cancel'})
                       .c('error').attrs({by:'lounge@montague.lit', type:'cancel'})
                           .c('not-acceptable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
                           .c('not-acceptable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
 
 
-                spyOn(view, 'showErrorMessage').and.callThrough();
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 expect(view.el.querySelector('.chatroom-body .disconnect-container .disconnect-msg:last-child').textContent.trim())
                 expect(view.el.querySelector('.chatroom-body .disconnect-container .disconnect-msg:last-child').textContent.trim())
                     .toBe("Your nickname doesn't conform to this groupchat's policies.");
                     .toBe("Your nickname doesn't conform to this groupchat's policies.");
@@ -4275,7 +4261,6 @@
                       .c('error').attrs({by:'lounge@montague.lit', type:'cancel'})
                       .c('error').attrs({by:'lounge@montague.lit', type:'cancel'})
                           .c('item-not-found').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
                           .c('item-not-found').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
 
 
-                spyOn(view, 'showErrorMessage').and.callThrough();
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 expect(view.el.querySelector('.chatroom-body .disconnect-container .disconnect-msg:last-child').textContent.trim())
                 expect(view.el.querySelector('.chatroom-body .disconnect-container .disconnect-msg:last-child').textContent.trim())
                     .toBe("This groupchat does not (yet) exist.");
                     .toBe("This groupchat does not (yet) exist.");
@@ -4317,7 +4302,6 @@
                       .c('error').attrs({by:'lounge@montague.lit', type:'cancel'})
                       .c('error').attrs({by:'lounge@montague.lit', type:'cancel'})
                           .c('service-unavailable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
                           .c('service-unavailable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
 
 
-                spyOn(view, 'showErrorMessage').and.callThrough();
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 _converse.connection._dataRecv(test_utils.createRequest(presence));
                 expect(view.el.querySelector('.chatroom-body .disconnect-container .disconnect-msg:last-child').textContent.trim())
                 expect(view.el.querySelector('.chatroom-body .disconnect-container .disconnect-msg:last-child').textContent.trim())
                     .toBe("This groupchat has reached its maximum number of participants.");
                     .toBe("This groupchat has reached its maximum number of participants.");

+ 15 - 11
spec/retractions.js

@@ -706,14 +706,18 @@
                 _converse.connection._dataRecv(test_utils.createRequest(error));
                 _converse.connection._dataRecv(test_utils.createRequest(error));
                 await u.waitUntil(() => view.el.querySelectorAll('.chat-error').length === 1);
                 await u.waitUntil(() => view.el.querySelectorAll('.chat-error').length === 1);
                 await u.waitUntil(() => view.el.querySelectorAll('.chat-msg--retracted').length === 0);
                 await u.waitUntil(() => view.el.querySelectorAll('.chat-msg--retracted').length === 0);
-                expect(view.model.messages.length).toBe(2);
-                expect(view.model.messages.last().get('retracted')).toBeFalsy();
-                expect(view.model.messages.last().get('is_ephemeral')).toBeFalsy();
-                expect(view.model.messages.last().get('editable')).toBeTruthy();
+                expect(view.model.messages.length).toBe(3);
+                expect(view.model.messages.at(1).get('retracted')).toBeFalsy();
+                expect(view.model.messages.at(1).get('is_ephemeral')).toBeFalsy();
+                expect(view.model.messages.at(1).get('editable')).toBeTruthy();
+
+                const err_msg = "Sorry, something went wrong while trying to retract your message."
+                expect(view.model.messages.at(2).get('message')).toBe(err_msg);
+                expect(view.model.messages.at(2).get('type')).toBe('error');
 
 
                 expect(view.el.querySelectorAll('.chat-error').length).toBe(1);
                 expect(view.el.querySelectorAll('.chat-error').length).toBe(1);
                 const errmsg = view.el.querySelector('.chat-error');
                 const errmsg = view.el.querySelector('.chat-error');
-                expect(errmsg.textContent).toBe("Sorry, something went wrong while trying to retract your message.");
+                expect(errmsg.textContent.trim()).toBe("Sorry, something went wrong while trying to retract your message.");
                 done();
                 done();
             }));
             }));
 
 
@@ -746,15 +750,15 @@
                 await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
                 await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
 
 
                 await u.waitUntil(() => view.el.querySelectorAll('.chat-msg--retracted').length === 0);
                 await u.waitUntil(() => view.el.querySelectorAll('.chat-msg--retracted').length === 0);
-                expect(view.model.messages.length).toBe(2);
-                expect(view.model.messages.last().get('retracted')).toBeFalsy();
-                expect(view.model.messages.last().get('is_ephemeral')).toBeFalsy();
-                expect(view.model.messages.last().get('editable')).toBeTruthy();
+                expect(view.model.messages.length).toBe(4);
+                expect(view.model.messages.at(1).get('retracted')).toBeFalsy();
+                expect(view.model.messages.at(1).get('is_ephemeral')).toBeFalsy();
+                expect(view.model.messages.at(1).get('editable')).toBeTruthy();
 
 
                 const error_messages = view.el.querySelectorAll('.chat-error');
                 const error_messages = view.el.querySelectorAll('.chat-error');
                 expect(error_messages.length).toBe(2);
                 expect(error_messages.length).toBe(2);
-                expect(error_messages[0].textContent).toBe("Sorry, something went wrong while trying to retract your message.");
-                expect(error_messages[1].textContent).toBe("Timeout Error: No response from server");
+                expect(error_messages[0].textContent.trim()).toBe("Sorry, something went wrong while trying to retract your message.");
+                expect(error_messages[1].textContent.trim()).toBe("Timeout Error: No response from server");
                 done();
                 done();
             }));
             }));
 
 

+ 0 - 9
src/converse-chatview.js

@@ -15,7 +15,6 @@ import log from "@converse/headless/log";
 import tpl_chatbox from "templates/chatbox.js";
 import tpl_chatbox from "templates/chatbox.js";
 import tpl_chatbox_head from "templates/chatbox_head.js";
 import tpl_chatbox_head from "templates/chatbox_head.js";
 import tpl_chatbox_message_form from "templates/chatbox_message_form.html";
 import tpl_chatbox_message_form from "templates/chatbox_message_form.html";
-import tpl_error_message from "templates/error_message.html";
 import tpl_help_message from "templates/help_message.html";
 import tpl_help_message from "templates/help_message.html";
 import tpl_info from "templates/info.html";
 import tpl_info from "templates/info.html";
 import tpl_new_day from "templates/new_day.html";
 import tpl_new_day from "templates/new_day.html";
@@ -518,14 +517,6 @@ converse.plugins.add('converse-chatview', {
                 return isodate;
                 return isodate;
             },
             },
 
 
-            showErrorMessage (message) {
-                this.msgs_container.insertAdjacentHTML(
-                    'beforeend',
-                    tpl_error_message({'message': message, 'isodate': (new Date()).toISOString() })
-                );
-                this.scrollDown();
-            },
-
             addSpinner (append=false) {
             addSpinner (append=false) {
                 if (this.el.querySelector('.spinner') === null) {
                 if (this.el.querySelector('.spinner') === null) {
                     if (append) {
                     if (append) {

+ 34 - 25
src/converse-muc-views.js

@@ -1008,13 +1008,9 @@ converse.plugins.add('converse-muc-views', {
             retractOwnMessage(message) {
             retractOwnMessage(message) {
                 this.model.retractOwnMessage(message)
                 this.model.retractOwnMessage(message)
                     .catch(e => {
                     .catch(e => {
-                        const errmsg = __('Sorry, something went wrong while trying to retract your message.');
-                        if (u.isErrorStanza(e)) {
-                            this.showErrorMessage(errmsg);
-                        } else {
-                            this.showErrorMessage(errmsg);
-                            this.showErrorMessage(e.message);
-                        }
+                        const message = __('Sorry, something went wrong while trying to retract your message.');
+                        this.model.createMessage({message, 'type': 'error'});
+                        !u.isErrorStanza(e) && this.model.createMessage({'message': e.message, 'type': 'error'});
                         log.error(e);
                         log.error(e);
                     });
                     });
             },
             },
@@ -1327,7 +1323,8 @@ converse.plugins.add('converse-muc-views', {
                     }
                     }
                 }
                 }
                 if (show_error) {
                 if (show_error) {
-                    this.showErrorMessage(__('Forbidden: you do not have the necessary role in order to do that.'))
+                    const message = __('Forbidden: you do not have the necessary role in order to do that.');
+                    this.model.createMessage({message, 'type': 'error'});
                 }
                 }
                 return false;
                 return false;
             },
             },
@@ -1347,16 +1344,19 @@ converse.plugins.add('converse-muc-views', {
                     }
                     }
                 }
                 }
                 if (show_error) {
                 if (show_error) {
-                    this.showErrorMessage(__('Forbidden: you do not have the necessary affiliation in order to do that.'))
+                    const message = __('Forbidden: you do not have the necessary affiliation in order to do that.');
+                    this.model.createMessage({message, 'type': 'error'});
                 }
                 }
                 return false;
                 return false;
             },
             },
 
 
             validateRoleOrAffiliationChangeArgs (command, args) {
             validateRoleOrAffiliationChangeArgs (command, args) {
                 if (!args) {
                 if (!args) {
-                    this.showErrorMessage(
-                        __('Error: the "%1$s" command takes two arguments, the user\'s nickname and optionally a reason.', command)
+                    const message = __(
+                        'Error: the "%1$s" command takes two arguments, the user\'s nickname and optionally a reason.',
+                        command
                     );
                     );
+                    this.model.createMessage({message, 'type': 'error'});
                     return false;
                     return false;
                 }
                 }
                 return true;
                 return true;
@@ -1371,17 +1371,20 @@ converse.plugins.add('converse-muc-views', {
                 }
                 }
                 const [text, references] = this.model.parseTextForReferences(args); // eslint-disable-line no-unused-vars
                 const [text, references] = this.model.parseTextForReferences(args); // eslint-disable-line no-unused-vars
                 if (!references.length) {
                 if (!references.length) {
-                    this.showErrorMessage(__("Error: couldn't find a groupchat participant based on your arguments"));
+                    const message = __("Error: couldn't find a groupchat participant based on your arguments");
+                    this.model.createMessage({message, 'type': 'error'});
                     return;
                     return;
                 }
                 }
                 if (references.length > 1) {
                 if (references.length > 1) {
-                    this.showErrorMessage(__("Error: found multiple groupchat participant based on your arguments"));
+                    const message = __("Error: found multiple groupchat participant based on your arguments");
+                    this.model.createMessage({message, 'type': 'error'});
                     return;
                     return;
                 }
                 }
                 const nick_or_jid = references.pop().value;
                 const nick_or_jid = references.pop().value;
                 const reason = args.split(nick_or_jid, 2)[1];
                 const reason = args.split(nick_or_jid, 2)[1];
                 if (reason && !reason.startsWith(' ')) {
                 if (reason && !reason.startsWith(' ')) {
-                    this.showErrorMessage(__("Error: couldn't find a groupchat participant based on your arguments"));
+                    const message = __("Error: couldn't find a groupchat participant based on your arguments");
+                    this.model.createMessage({message, 'type': 'error'});
                     return;
                     return;
                 }
                 }
                 return nick_or_jid;
                 return nick_or_jid;
@@ -1412,10 +1415,11 @@ converse.plugins.add('converse-muc-views', {
                     if (u.isValidJID(nick_or_jid)) {
                     if (u.isValidJID(nick_or_jid)) {
                         jid = nick_or_jid;
                         jid = nick_or_jid;
                     } else {
                     } else {
-                        this.showErrorMessage(__(
+                        const message = __(
                             "Couldn't find a participant with that nickname. "+
                             "Couldn't find a participant with that nickname. "+
                             "They might have left the groupchat."
                             "They might have left the groupchat."
-                        ));
+                        );
+                        this.model.createMessage({message, 'type': 'error'});
                         return;
                         return;
                     }
                     }
                 }
                 }
@@ -1459,10 +1463,10 @@ converse.plugins.add('converse-muc-views', {
 
 
             onCommandError (err) {
             onCommandError (err) {
                 log.fatal(err);
                 log.fatal(err);
-                this.showErrorMessage(
+                const message =
                     __("Sorry, an error happened while running the command.") + " " +
                     __("Sorry, an error happened while running the command.") + " " +
-                    __("Check your browser's developer console for details.")
-                );
+                    __("Check your browser's developer console for details.");
+                this.model.createMessage({message, 'type': 'error'});
             },
             },
 
 
             getAllowedCommands () {
             getAllowedCommands () {
@@ -1608,7 +1612,9 @@ converse.plugins.add('converse-muc-views', {
                             break;
                             break;
                         } else if (args.length === 0) {
                         } else if (args.length === 0) {
                             // e.g. Your nickname is "coolguy69"
                             // e.g. Your nickname is "coolguy69"
-                            this.showErrorMessage(__('Your nickname is "%1$s"', this.model.get('nick')))
+                            const message = __('Your nickname is "%1$s"', this.model.get('nick'));
+                            this.model.createMessage({message, 'type': 'error'});
+
                         } else {
                         } else {
                             const jid = Strophe.getBareJidFromJid(this.model.get('jid'));
                             const jid = Strophe.getBareJidFromJid(this.model.get('jid'));
                             api.send($pres({
                             api.send($pres({
@@ -1628,10 +1634,13 @@ converse.plugins.add('converse-muc-views', {
                     }
                     }
                     case 'register': {
                     case 'register': {
                         if (args.length > 1) {
                         if (args.length > 1) {
-                            this.showErrorMessage(__('Error: invalid number of arguments'))
+                            this.model.createMessage({
+                                'message': __('Error: invalid number of arguments'),
+                                'type': 'error'
+                            });
                         } else {
                         } else {
                             this.model.registerNickname().then(err_msg => {
                             this.model.registerNickname().then(err_msg => {
-                                if (err_msg) this.showErrorMessage(err_msg)
+                                err_msg && this.model.createMessage({'message': err_msg, 'type': 'error'});
                             });
                             });
                         }
                         }
                         break;
                         break;
@@ -2018,10 +2027,10 @@ converse.plugins.add('converse-muc-views', {
                     await this.model.sendConfiguration(configArray);
                     await this.model.sendConfiguration(configArray);
                 } catch (e) {
                 } catch (e) {
                     log.error(e);
                     log.error(e);
-                    this.showErrorMessage(
+                    const message =
                         __("Sorry, an error occurred while trying to submit the config form.") + " " +
                         __("Sorry, an error occurred while trying to submit the config form.") + " " +
-                        __("Check your browser's developer console for details.")
-                    );
+                        __("Check your browser's developer console for details.");
+                    this.model.createMessage({message, 'type': 'error'});
                 }
                 }
                 await this.model.refreshDiscoInfo();
                 await this.model.refreshDiscoInfo();
                 this.chatroomview.closeForm();
                 this.chatroomview.closeForm();

+ 0 - 1
src/templates/error_message.html

@@ -1 +0,0 @@
-<div class="message chat-info chat-error" data-isodate="{{{o.isodate}}}">{{{o.message}}}</div>