Revert flexbox, column-reverse changes

Unfortunately this doesn't work on Firefox and there's no proper
workaround. https://github.com/philipwalton/flexbugs/issues/108

Reverts:

Revert "Bugfix. Properly insert error messages and spinner"
This reverts commit 6a419cc145.

Revert "Use flexbox to keep the chat scrolled down"
This reverts commit dd91d3cc55.
This commit is contained in:
JC Brand 2019-06-19 11:04:09 +02:00
parent 303a8b63af
commit 36549bf61d
9 changed files with 310 additions and 272 deletions

View File

@ -61,8 +61,6 @@
- Removed events `statusChanged` and `statusMessageChanged`. Instead, you can - Removed events `statusChanged` and `statusMessageChanged`. Instead, you can
listen on the `change:status` or `change:status\_message` events on listen on the `change:status` or `change:status\_message` events on
`_converse.xmppstatus`. `_converse.xmppstatus`.
- Use flexbox instead of JavaScript to keep chat scrolled down. Due to this
change, messages are now inserted into the DOM in reverse order than before.
### API changes ### API changes

View File

@ -207,8 +207,6 @@
margin-bottom: 0.25em; margin-bottom: 0.25em;
} }
.chat-content { .chat-content {
display: flex;
flex-direction: column-reverse;
padding: 1em 0; padding: 1em 0;
height: 100%; height: 100%;
font-size: var(--message-font-size); font-size: var(--message-font-size);

View File

@ -77,9 +77,10 @@
} }
&.chat-msg { &.chat-msg {
display: flex; display: inline-flex;
width: 100%; width: 100%;
flex-direction: row; flex-direction: row;
overflow: auto; // Ensures that content stays inside
padding: 0.125rem 1rem; padding: 0.125rem 1rem;
&.onload { &.onload {
@ -151,6 +152,7 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
width: 100%;
} }
.chat-msg__message { .chat-msg__message {
@ -267,7 +269,7 @@
} }
&.chat-msg--action { &.chat-msg--action {
.chat-msg__content { .chat-msg__content {
flex-wrap: wrap; flex-wrap: nowrap;
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
} }

View File

@ -44,7 +44,7 @@
}).c('body').t('hello world').tree(); }).c('body').t('hello world').tree();
await _converse.chatboxes.onMessage(msg); await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => view.content.querySelectorAll('.chat-msg').length); await test_utils.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
expect(view.content.firstElementChild.textContent.trim().indexOf('hello world')).not.toBe(-1); expect(view.content.lastElementChild.textContent.trim().indexOf('hello world')).not.toBe(-1);
done(); done();
})); }));
@ -78,22 +78,22 @@
message = '/me is as well'; message = '/me is as well';
await test_utils.sendMessage(view, message); await test_utils.sendMessage(view, message);
expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(2); expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(2);
await test_utils.waitUntil(() => sizzle('.chat-msg__author:first', view.el).pop().textContent.trim() === '**Romeo Montague'); await test_utils.waitUntil(() => sizzle('.chat-msg__author:last', view.el).pop().textContent.trim() === '**Romeo Montague');
const last_el = sizzle('.chat-msg__text:first', view.el).pop(); const last_el = sizzle('.chat-msg__text:last', view.el).pop();
expect(last_el.textContent).toBe('is as well'); expect(last_el.textContent).toBe('is as well');
expect(u.hasClass('chat-msg--followup', last_el)).toBe(false); expect(u.hasClass('chat-msg--followup', last_el)).toBe(false);
// Check that /me messages after a normal message don't // Check that /me messages after a normal message don't
// get the 'chat-msg--followup' class. // get the 'chat-msg--followup' class.
message = 'This a normal message'; message = 'This a normal message';
await test_utils.sendMessage(view, message); await test_utils.sendMessage(view, message);
let message_el = view.el.querySelector('.message:first-child'); let message_el = view.el.querySelector('.message:last-child');
expect(u.hasClass('chat-msg--followup', message_el)).toBeFalsy(); expect(u.hasClass('chat-msg--followup', message_el)).toBeFalsy();
message = '/me wrote a 3rd person message'; message = '/me wrote a 3rd person message';
await test_utils.sendMessage(view, message); await test_utils.sendMessage(view, message);
message_el = view.el.querySelector('.message:first-child'); message_el = view.el.querySelector('.message:last-child');
expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(3); expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(3);
expect(sizzle('.chat-msg__text:first', view.el).pop().textContent).toBe('wrote a 3rd person message'); expect(sizzle('.chat-msg__text:last', view.el).pop().textContent).toBe('wrote a 3rd person message');
expect(u.isVisible(sizzle('.chat-msg__author:first', view.el).pop())).toBeTruthy(); expect(u.isVisible(sizzle('.chat-msg__author:last', view.el).pop())).toBeTruthy();
expect(u.hasClass('chat-msg--followup', message_el)).toBeFalsy(); expect(u.hasClass('chat-msg--followup', message_el)).toBeFalsy();
done(); done();
})); }));
@ -267,7 +267,7 @@
const jid = el.textContent.replace(/ /g,'.').toLowerCase() + '@montague.lit'; const jid = el.textContent.replace(/ /g,'.').toLowerCase() + '@montague.lit';
spyOn(_converse.api, "trigger"); spyOn(_converse.api, "trigger");
el.click(); el.click();
await test_utils.waitUntil(() => _converse.api.trigger.calls.count(), 1000); await test_utils.waitUntil(() => _converse.api.trigger.calls.count(), 500);
expect(_converse.chatboxes.length).toEqual(2); expect(_converse.chatboxes.length).toEqual(2);
expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxFocused', jasmine.any(Object)); expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxFocused', jasmine.any(Object));
done(); done();

View File

@ -260,7 +260,7 @@
expect(view.model.messages.at(0).get('correcting')).toBeFalsy(); expect(view.model.messages.at(0).get('correcting')).toBeFalsy();
expect(view.model.messages.at(1).get('correcting')).toBeFalsy(); expect(view.model.messages.at(1).get('correcting')).toBeFalsy();
expect(view.model.messages.at(2).get('correcting')).toBe(true); expect(view.model.messages.at(2).get('correcting')).toBe(true);
await test_utils.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg:first', view.el).pop()), 500); await test_utils.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg:last', view.el).pop()), 500);
textarea.selectionEnd = 0; // Happens by pressing up, textarea.selectionEnd = 0; // Happens by pressing up,
// but for some reason not in tests, so we set it manually. // but for some reason not in tests, so we set it manually.
@ -285,11 +285,11 @@
expect(textarea.value).toBe(''); expect(textarea.value).toBe('');
const messages = view.el.querySelectorAll('.chat-msg'); const messages = view.el.querySelectorAll('.chat-msg');
expect(messages.length).toBe(3); expect(messages.length).toBe(3);
expect(messages[2].querySelector('.chat-msg__text').textContent) expect(messages[0].querySelector('.chat-msg__text').textContent)
.toBe('But soft, what light through yonder window breaks?'); .toBe('But soft, what light through yonder window breaks?');
expect(messages[1].querySelector('.chat-msg__text').textContent) expect(messages[1].querySelector('.chat-msg__text').textContent)
.toBe('It is the east, and Juliet is the sun.'); .toBe('It is the east, and Juliet is the sun.');
expect(messages[0].querySelector('.chat-msg__text').textContent) expect(messages[2].querySelector('.chat-msg__text').textContent)
.toBe('Arise, fair sun, and kill the envious moon'); .toBe('Arise, fair sun, and kill the envious moon');
expect(view.model.messages.at(0).get('correcting')).toBeFalsy(); expect(view.model.messages.at(0).get('correcting')).toBeFalsy();
@ -434,53 +434,53 @@
view.clearSpinner(); //cleanup view.clearSpinner(); //cleanup
expect(chat_content.querySelectorAll('.date-separator').length).toEqual(4); expect(chat_content.querySelectorAll('.date-separator').length).toEqual(4);
let day = sizzle('.date-separator:last', chat_content).pop(); let day = sizzle('.date-separator:first', chat_content).pop();
expect(day.getAttribute('data-isodate')).toEqual(dayjs('2017-12-31T00:00:00').toISOString()); expect(day.getAttribute('data-isodate')).toEqual(dayjs('2017-12-31T00:00:00').toISOString());
let time = sizzle('time:last', chat_content).pop(); let time = sizzle('time:first', chat_content).pop();
expect(time.textContent).toEqual('Sunday Dec 31st 2017') expect(time.textContent).toEqual('Sunday Dec 31st 2017')
day = sizzle('.date-separator:last', chat_content).pop(); day = sizzle('.date-separator:first', chat_content).pop();
expect(day.previousElementSibling.querySelector('.chat-msg__text').textContent).toBe('Older message'); expect(day.nextElementSibling.querySelector('.chat-msg__text').textContent).toBe('Older message');
let el = sizzle('.chat-msg:last', chat_content).pop().querySelector('.chat-msg__text') let el = sizzle('.chat-msg:first', chat_content).pop().querySelector('.chat-msg__text')
expect(u.hasClass('chat-msg--followup', el)).toBe(false); expect(u.hasClass('chat-msg--followup', el)).toBe(false);
expect(el.textContent).toEqual('Older message'); expect(el.textContent).toEqual('Older message');
time = sizzle('time.separator-text:eq(2)', chat_content).pop(); time = sizzle('time.separator-text:eq(1)', chat_content).pop();
expect(time.textContent).toEqual("Monday Jan 1st 2018"); expect(time.textContent).toEqual("Monday Jan 1st 2018");
day = sizzle('.date-separator:eq(2)', chat_content).pop(); day = sizzle('.date-separator:eq(1)', chat_content).pop();
expect(day.getAttribute('data-isodate')).toEqual(dayjs('2018-01-01T00:00:00').toISOString()); expect(day.getAttribute('data-isodate')).toEqual(dayjs('2018-01-01T00:00:00').toISOString());
expect(day.previousElementSibling.querySelector('.chat-msg__text').textContent).toBe('Inbetween message'); expect(day.nextElementSibling.querySelector('.chat-msg__text').textContent).toBe('Inbetween message');
el = sizzle('.chat-msg:eq(5)', chat_content).pop(); el = sizzle('.chat-msg:eq(1)', chat_content).pop();
expect(el.querySelector('.chat-msg__text').textContent).toEqual('Inbetween message'); expect(el.querySelector('.chat-msg__text').textContent).toEqual('Inbetween message');
expect(el.previousElementSibling.querySelector('.chat-msg__text').textContent).toEqual('another inbetween message'); expect(el.nextElementSibling.querySelector('.chat-msg__text').textContent).toEqual('another inbetween message');
el = sizzle('.chat-msg:eq(4)', chat_content).pop(); el = sizzle('.chat-msg:eq(2)', chat_content).pop();
expect(el.querySelector('.chat-msg__text').textContent) expect(el.querySelector('.chat-msg__text').textContent)
.toEqual('another inbetween message'); .toEqual('another inbetween message');
expect(u.hasClass('chat-msg--followup', el)).toBe(true); expect(u.hasClass('chat-msg--followup', el)).toBe(true);
time = sizzle('time.separator-text:nth(1)', chat_content).pop(); time = sizzle('time.separator-text:nth(2)', chat_content).pop();
expect(time.textContent).toEqual("Tuesday Jan 2nd 2018"); expect(time.textContent).toEqual("Tuesday Jan 2nd 2018");
day = sizzle('.date-separator:nth(1)', chat_content).pop(); day = sizzle('.date-separator:nth(2)', chat_content).pop();
expect(day.getAttribute('data-isodate')).toEqual(dayjs('2018-01-02T00:00:00').toISOString()); expect(day.getAttribute('data-isodate')).toEqual(dayjs('2018-01-02T00:00:00').toISOString());
expect(day.previousElementSibling.querySelector('.chat-msg__text').textContent).toBe('An earlier message on the next day'); expect(day.nextElementSibling.querySelector('.chat-msg__text').textContent).toBe('An earlier message on the next day');
el = sizzle('.chat-msg:eq(3)', chat_content).pop(); el = sizzle('.chat-msg:eq(3)', chat_content).pop();
expect(el.querySelector('.chat-msg__text').textContent).toEqual('An earlier message on the next day'); expect(el.querySelector('.chat-msg__text').textContent).toEqual('An earlier message on the next day');
expect(u.hasClass('chat-msg--followup', el)).toBe(false); expect(u.hasClass('chat-msg--followup', el)).toBe(false);
el = sizzle('.chat-msg:eq(2)', chat_content).pop(); el = sizzle('.chat-msg:eq(4)', chat_content).pop();
expect(el.querySelector('.chat-msg__text').textContent).toEqual('message'); expect(el.querySelector('.chat-msg__text').textContent).toEqual('message');
expect(el.previousElementSibling.querySelector('.chat-msg__text').textContent).toEqual('newer message from the next day'); expect(el.nextElementSibling.querySelector('.chat-msg__text').textContent).toEqual('newer message from the next day');
expect(u.hasClass('chat-msg--followup', el)).toBe(false); expect(u.hasClass('chat-msg--followup', el)).toBe(false);
day = sizzle('.date-separator:first', chat_content).pop(); day = sizzle('.date-separator:last', chat_content).pop();
expect(day.getAttribute('data-isodate')).toEqual(dayjs().startOf('day').toISOString()); expect(day.getAttribute('data-isodate')).toEqual(dayjs().startOf('day').toISOString());
expect(day.previousElementSibling.querySelector('.chat-msg__text').textContent).toBe('latest message'); expect(day.nextElementSibling.querySelector('.chat-msg__text').textContent).toBe('latest message');
expect(u.hasClass('chat-msg--followup', el)).toBe(false); expect(u.hasClass('chat-msg--followup', el)).toBe(false);
done(); done();
})); }));
@ -800,12 +800,12 @@
expect(chat_content.querySelectorAll('time.separator-text').length).toEqual(2); // There are now two time elements expect(chat_content.querySelectorAll('time.separator-text').length).toEqual(2); // There are now two time elements
const message_date = new Date(); const message_date = new Date();
day = sizzle('.date-separator:first', chat_content); day = sizzle('.date-separator:last', chat_content);
expect(day.length).toEqual(1); expect(day.length).toEqual(1);
expect(day[0].getAttribute('class')).toEqual('message date-separator'); expect(day[0].getAttribute('class')).toEqual('message date-separator');
expect(day[0].getAttribute('data-isodate')).toEqual(dayjs(message_date).startOf('day').toISOString()); expect(day[0].getAttribute('data-isodate')).toEqual(dayjs(message_date).startOf('day').toISOString());
time = sizzle('time.separator-text:first', chat_content).pop(); time = sizzle('time.separator-text:last', chat_content).pop();
expect(time.textContent).toEqual(dayjs(message_date).startOf('day').format("dddd MMM Do YYYY")); expect(time.textContent).toEqual(dayjs(message_date).startOf('day').format("dddd MMM Do YYYY"));
// Normal checks for the 2nd message // Normal checks for the 2nd message
@ -815,12 +815,12 @@
expect(msg_obj.get('fullname')).toBeUndefined(); expect(msg_obj.get('fullname')).toBeUndefined();
expect(msg_obj.get('sender')).toEqual('them'); expect(msg_obj.get('sender')).toEqual('them');
expect(msg_obj.get('is_delayed')).toEqual(false); expect(msg_obj.get('is_delayed')).toEqual(false);
const msg_txt = sizzle('.chat-msg:first .chat-msg__text', chat_content).pop().textContent; const msg_txt = sizzle('.chat-msg:last .chat-msg__text', chat_content).pop().textContent;
expect(msg_txt).toEqual(message); expect(msg_txt).toEqual(message);
expect(chat_content.querySelector('.chat-msg:first-child .chat-msg__text').textContent).toEqual(message); expect(chat_content.querySelector('.chat-msg:last-child .chat-msg__text').textContent).toEqual(message);
expect(chat_content.querySelector('.chat-msg:first-child .chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy(); expect(chat_content.querySelector('.chat-msg:last-child .chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
expect(chat_content.querySelector('.chat-msg:first-child .chat-msg__author').textContent.trim()).toBe('Juliet Capulet'); expect(chat_content.querySelector('.chat-msg:last-child .chat-msg__author').textContent.trim()).toBe('Juliet Capulet');
done(); done();
})); }));
@ -911,21 +911,21 @@
message = 'http://www.opkode.com/"onmouseover="alert(1)"whatever'; message = 'http://www.opkode.com/"onmouseover="alert(1)"whatever';
await test_utils.sendMessage(view, message); await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:first .chat-msg__text', view.el).pop(); msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message); expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual('<a target="_blank" rel="noopener" href="http://www.opkode.com/%22onmouseover=%22alert%281%29%22whatever">http://www.opkode.com/"onmouseover="alert(1)"whatever</a>'); expect(msg.innerHTML).toEqual('<a target="_blank" rel="noopener" href="http://www.opkode.com/%22onmouseover=%22alert%281%29%22whatever">http://www.opkode.com/"onmouseover="alert(1)"whatever</a>');
message = "https://en.wikipedia.org/wiki/Ender's_Game"; message = "https://en.wikipedia.org/wiki/Ender's_Game";
await test_utils.sendMessage(view, message); await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:first .chat-msg__text', view.el).pop(); msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message); expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual('<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Ender%27s_Game">'+message+'</a>'); expect(msg.innerHTML).toEqual('<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Ender%27s_Game">'+message+'</a>');
message = "<https://bugs.documentfoundation.org/show_bug.cgi?id=123737>"; message = "<https://bugs.documentfoundation.org/show_bug.cgi?id=123737>";
await test_utils.sendMessage(view, message); await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:first .chat-msg__text', view.el).pop(); msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message); expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual( expect(msg.innerHTML).toEqual(
`&lt;<a target="_blank" rel="noopener" href="https://bugs.documentfoundation.org/show_bug.cgi?id=123737">https://bugs.documentfoundation.org/show_bug.cgi?id=123737</a>&gt;`); `&lt;<a target="_blank" rel="noopener" href="https://bugs.documentfoundation.org/show_bug.cgi?id=123737">https://bugs.documentfoundation.org/show_bug.cgi?id=123737</a>&gt;`);
@ -933,7 +933,7 @@
message = '<http://www.opkode.com/"onmouseover="alert(1)"whatever>'; message = '<http://www.opkode.com/"onmouseover="alert(1)"whatever>';
await test_utils.sendMessage(view, message); await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:first .chat-msg__text', view.el).pop(); msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message); expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual( expect(msg.innerHTML).toEqual(
'&lt;<a target="_blank" rel="noopener" href="http://www.opkode.com/%22onmouseover=%22alert%281%29%22whatever">http://www.opkode.com/"onmouseover="alert(1)"whatever</a>&gt;'); '&lt;<a target="_blank" rel="noopener" href="http://www.opkode.com/%22onmouseover=%22alert%281%29%22whatever">http://www.opkode.com/"onmouseover="alert(1)"whatever</a>&gt;');
@ -941,7 +941,7 @@
message = `https://www.google.com/maps/place/Kochstraat+6,+2041+CE+Zandvoort/@52.3775999,4.548971,3a,15y,170.85h,88.39t/data=!3m6!1e1!3m4!1sQ7SdHo_bPLPlLlU8GSGWaQ!2e0!7i13312!8i6656!4m5!3m4!1s0x47c5ec1e56f845ad:0x1de0bc4a5771fb08!8m2!3d52.3773668!4d4.5489388!5m1!1e2` message = `https://www.google.com/maps/place/Kochstraat+6,+2041+CE+Zandvoort/@52.3775999,4.548971,3a,15y,170.85h,88.39t/data=!3m6!1e1!3m4!1sQ7SdHo_bPLPlLlU8GSGWaQ!2e0!7i13312!8i6656!4m5!3m4!1s0x47c5ec1e56f845ad:0x1de0bc4a5771fb08!8m2!3d52.3773668!4d4.5489388!5m1!1e2`
await test_utils.sendMessage(view, message); await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:first .chat-msg__text', view.el).pop(); msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message); expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual( expect(msg.innerHTML).toEqual(
`<a target="_blank" rel="noopener" href="https://www.google.com/maps/place/Kochstraat+6,+2041+CE+Zandvoort/@52.3775999,4.548971,3a,15y,170.85h,88.39t/data=%213m6%211e1%213m4%211sQ7SdHo_bPLPlLlU8GSGWaQ%212e0%217i13312%218i6656%214m5%213m4%211s0x47c5ec1e56f845ad:0x1de0bc4a5771fb08%218m2%213d52.3773668%214d4.5489388%215m1%211e2">https://www.google.com/maps/place/Kochstraat+6,+2041+CE+Zandvoort/@52.3775999,4.548971,3a,15y,170.85h,88.39t/data=!3m6!1e1!3m4!1sQ7SdHo_bPLPlLlU8GSGWaQ!2e0!7i13312!8i6656!4m5!3m4!1s0x47c5ec1e56f845ad:0x1de0bc4a5771fb08!8m2!3d52.3773668!4d4.5489388!5m1!1e2</a>`); `<a target="_blank" rel="noopener" href="https://www.google.com/maps/place/Kochstraat+6,+2041+CE+Zandvoort/@52.3775999,4.548971,3a,15y,170.85h,88.39t/data=%213m6%211e1%213m4%211sQ7SdHo_bPLPlLlU8GSGWaQ%212e0%217i13312%218i6656%214m5%213m4%211s0x47c5ec1e56f845ad:0x1de0bc4a5771fb08%218m2%213d52.3773668%214d4.5489388%215m1%211e2">https://www.google.com/maps/place/Kochstraat+6,+2041+CE+Zandvoort/@52.3775999,4.548971,3a,15y,170.85h,88.39t/data=!3m6!1e1!3m4!1sQ7SdHo_bPLPlLlU8GSGWaQ!2e0!7i13312!8i6656!4m5!3m4!1s0x47c5ec1e56f845ad:0x1de0bc4a5771fb08!8m2!3d52.3773668!4d4.5489388!5m1!1e2</a>`);
@ -998,7 +998,7 @@
</message>`); </message>`);
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
await new Promise((resolve, reject) => view.once('messageInserted', resolve)); await new Promise((resolve, reject) => view.once('messageInserted', resolve));
expect(chat_content.querySelector('.message:first-child .chat-msg__text').innerHTML).toBe('Hey<br><br>Have you heard the news?'); expect(chat_content.querySelector('.message:last-child .chat-msg__text').innerHTML).toBe('Hey<br><br>Have you heard the news?');
stanza = u.toStanza(` stanza = u.toStanza(`
<message from="${contact_jid}" <message from="${contact_jid}"
type="chat" type="chat"
@ -1007,7 +1007,7 @@
</message>`); </message>`);
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
await new Promise((resolve, reject) => view.once('messageInserted', resolve)); await new Promise((resolve, reject) => view.once('messageInserted', resolve));
expect(chat_content.querySelector('.message:first-child .chat-msg__text').innerHTML).toBe('Hey<br>Have you heard<br>the news?'); expect(chat_content.querySelector('.message:last-child .chat-msg__text').innerHTML).toBe('Hey<br>Have you heard<br>the news?');
done(); done();
})); }));
@ -1026,7 +1026,7 @@
test_utils.sendMessage(view, message); test_utils.sendMessage(view, message);
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length, 1000) await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length, 1000)
expect(view.model.sendMessage).toHaveBeenCalled(); expect(view.model.sendMessage).toHaveBeenCalled();
let msg = sizzle('.chat-content .chat-msg:first .chat-msg__text').pop(); let msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop();
expect(msg.innerHTML.trim()).toEqual( expect(msg.innerHTML.trim()).toEqual(
'<!-- src/templates/image.html -->\n'+ '<!-- src/templates/image.html -->\n'+
'<a href="'+base_url+'/logo/conversejs-filled.svg" target="_blank" rel="noopener"><img class="chat-image img-thumbnail"'+ '<a href="'+base_url+'/logo/conversejs-filled.svg" target="_blank" rel="noopener"><img class="chat-image img-thumbnail"'+
@ -1035,7 +1035,7 @@
test_utils.sendMessage(view, message); test_utils.sendMessage(view, message);
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length === 2, 1000); await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length === 2, 1000);
expect(view.model.sendMessage).toHaveBeenCalled(); expect(view.model.sendMessage).toHaveBeenCalled();
msg = sizzle('.chat-content .chat-msg:first .chat-msg__text').pop(); msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop();
expect(msg.innerHTML.trim()).toEqual( expect(msg.innerHTML.trim()).toEqual(
'<!-- src/templates/image.html -->\n'+ '<!-- src/templates/image.html -->\n'+
'<a href="'+base_url+'/logo/conversejs-filled.svg?param1=val1&amp;param2=val2" target="_blank" rel="noopener"><img'+ '<a href="'+base_url+'/logo/conversejs-filled.svg?param1=val1&amp;param2=val2" target="_blank" rel="noopener"><img'+
@ -1046,7 +1046,7 @@
test_utils.sendMessage(view, message); test_utils.sendMessage(view, message);
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length === 4, 1000); await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length === 4, 1000);
expect(view.model.sendMessage).toHaveBeenCalled(); expect(view.model.sendMessage).toHaveBeenCalled();
msg = sizzle('.chat-content .chat-msg:first .chat-msg__text').pop(); msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop();
expect(msg.textContent.trim()).toEqual('hello world'); expect(msg.textContent.trim()).toEqual('hello world');
expect(msg.querySelectorAll('img').length).toEqual(2); expect(msg.querySelectorAll('img').length).toEqual(2);
@ -1076,10 +1076,10 @@
expect(chatbox.messages.models.length, 1); expect(chatbox.messages.models.length, 1);
const msg_object = chatbox.messages.models[0]; const msg_object = chatbox.messages.models[0];
const msg_author = view.el.querySelector('.chat-content .chat-msg:first-child .chat-msg__author'); const msg_author = view.el.querySelector('.chat-content .chat-msg:last-child .chat-msg__author');
expect(msg_author.textContent.trim()).toBe('Romeo Montague'); expect(msg_author.textContent.trim()).toBe('Romeo Montague');
const msg_time = view.el.querySelector('.chat-content .chat-msg:first-child .chat-msg__time'); const msg_time = view.el.querySelector('.chat-content .chat-msg:last-child .chat-msg__time');
const time = dayjs(msg_object.get('time')).format(_converse.time_format); const time = dayjs(msg_object.get('time')).format(_converse.time_format);
expect(msg_time.textContent).toBe(time); expect(msg_time.textContent).toBe(time);
done(); done();
@ -1165,19 +1165,19 @@
expect(chat_content.querySelectorAll('.message').length).toBe(6); expect(chat_content.querySelectorAll('.message').length).toBe(6);
expect(chat_content.querySelectorAll('.chat-msg').length).toBe(5); expect(chat_content.querySelectorAll('.chat-msg').length).toBe(5);
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(false); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(5) .chat-msg__text').textContent).toBe("A message"); expect(chat_content.querySelector('.message:nth-child(2) .chat-msg__text').textContent).toBe("A message");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(true); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(4) .chat-msg__text').textContent).toBe(
"Another message 3 minutes later");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(3) .chat-msg__text').textContent).toBe( expect(chat_content.querySelector('.message:nth-child(3) .chat-msg__text').textContent).toBe(
"Another message 3 minutes later");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(4) .chat-msg__text').textContent).toBe(
"Another message 14 minutes since we started"); "Another message 14 minutes since we started");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(true); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(2) .chat-msg__text').textContent).toBe( expect(chat_content.querySelector('.message:nth-child(5) .chat-msg__text').textContent).toBe(
"Another message 1 minute and 1 second since the previous one"); "Another message 1 minute and 1 second since the previous one");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(1)'))).toBe(false); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(1) .chat-msg__text').textContent).toBe( expect(chat_content.querySelector('.message:nth-child(6) .chat-msg__text').textContent).toBe(
"Another message within 10 minutes, but from a different person"); "Another message within 10 minutes, but from a different person");
// Let's add a delayed, inbetween message // Let's add a delayed, inbetween message
@ -1197,21 +1197,21 @@
expect(chat_content.querySelectorAll('.message').length).toBe(7); expect(chat_content.querySelectorAll('.message').length).toBe(7);
expect(chat_content.querySelectorAll('.chat-msg').length).toBe(6); expect(chat_content.querySelectorAll('.chat-msg').length).toBe(6);
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(false); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(6) .chat-msg__text').textContent).toBe("A message"); expect(chat_content.querySelector('.message:nth-child(2) .chat-msg__text').textContent).toBe("A message");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(true); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(5) .chat-msg__text').textContent).toBe( expect(chat_content.querySelector('.message:nth-child(3) .chat-msg__text').textContent).toBe(
"Another message 3 minutes later"); "Another message 3 minutes later");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(true); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(4) .chat-msg__text').textContent).toBe( expect(chat_content.querySelector('.message:nth-child(4) .chat-msg__text').textContent).toBe(
"A delayed message, sent 5 minutes since we started"); "A delayed message, sent 5 minutes since we started");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(3) .chat-msg__text').textContent).toBe( expect(chat_content.querySelector('.message:nth-child(5) .chat-msg__text').textContent).toBe(
"Another message 14 minutes since we started"); "Another message 14 minutes since we started");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(true); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(2) .chat-msg__text').textContent).toBe( expect(chat_content.querySelector('.message:nth-child(6) .chat-msg__text').textContent).toBe(
"Another message 1 minute and 1 second since the previous one"); "Another message 1 minute and 1 second since the previous one");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(1)'))).toBe(false); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(7)'))).toBe(false);
_converse.chatboxes.onMessage($msg({'id': 'aeb213', 'to': _converse.bare_jid}) _converse.chatboxes.onMessage($msg({'id': 'aeb213', 'to': _converse.bare_jid})
.c('forwarded', {'xmlns': 'urn:xmpp:forward:0'}) .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
@ -1227,25 +1227,25 @@
expect(chat_content.querySelectorAll('.message').length).toBe(8); expect(chat_content.querySelectorAll('.message').length).toBe(8);
expect(chat_content.querySelectorAll('.chat-msg').length).toBe(7); expect(chat_content.querySelectorAll('.chat-msg').length).toBe(7);
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(7)'))).toBe(false); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(7) .chat-msg__text').textContent).toBe("A message"); expect(chat_content.querySelector('.message:nth-child(2) .chat-msg__text').textContent).toBe("A message");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(6) .chat-msg__text').textContent).toBe(
"Another message 3 minutes later");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(5) .chat-msg__text').textContent).toBe(
"A carbon message 4 minutes later");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(4) .chat-msg__text').textContent).toBe(
"A delayed message, sent 5 minutes since we started");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(3) .chat-msg__text').textContent).toBe( expect(chat_content.querySelector('.message:nth-child(3) .chat-msg__text').textContent).toBe(
"Another message 3 minutes later");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(4) .chat-msg__text').textContent).toBe(
"A carbon message 4 minutes later");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(5) .chat-msg__text').textContent).toBe(
"A delayed message, sent 5 minutes since we started");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(6) .chat-msg__text').textContent).toBe(
"Another message 14 minutes since we started"); "Another message 14 minutes since we started");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(true); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(7)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(2) .chat-msg__text').textContent).toBe( expect(chat_content.querySelector('.message:nth-child(7) .chat-msg__text').textContent).toBe(
"Another message 1 minute and 1 second since the previous one"); "Another message 1 minute and 1 second since the previous one");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(1)'))).toBe(false); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(8)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(1) .chat-msg__text').textContent).toBe( expect(chat_content.querySelector('.message:nth-child(8) .chat-msg__text').textContent).toBe(
"Another message within 10 minutes, but from a different person"); "Another message within 10 minutes, but from a different person");
jasmine.clock().uninstall(); jasmine.clock().uninstall();
@ -1683,7 +1683,7 @@
}); });
view.model.sendMessage(msg_text); view.model.sendMessage(msg_text);
await new Promise((resolve, reject) => view.once('messageInserted', resolve)); await new Promise((resolve, reject) => view.once('messageInserted', resolve));
msg_txt = sizzle('.chat-msg:first .chat-msg__text', chat_content).pop().textContent; msg_txt = sizzle('.chat-msg:last .chat-msg__text', chat_content).pop().textContent;
expect(msg_txt).toEqual(msg_text); expect(msg_txt).toEqual(msg_text);
/* <message xmlns="jabber:client" /* <message xmlns="jabber:client"
@ -1751,7 +1751,7 @@
}); });
view.model.sendMessage(msg_text); view.model.sendMessage(msg_text);
await new Promise((resolve, reject) => view.once('messageInserted', resolve)); await new Promise((resolve, reject) => view.once('messageInserted', resolve));
msg_txt = sizzle('.chat-msg:first .chat-msg__text', chat_content).pop().textContent; msg_txt = sizzle('.chat-msg:last .chat-msg__text', chat_content).pop().textContent;
expect(msg_txt).toEqual(msg_text); expect(msg_txt).toEqual(msg_text);
// A different error message will however render // A different error message will however render
@ -1831,20 +1831,14 @@
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()); .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
promises.push(new Promise((resolve, reject) => view.once('messageInserted', resolve))); promises.push(new Promise((resolve, reject) => view.once('messageInserted', resolve)));
} }
await Promise.all(promises); await Promise.all(promises);
// XXX Fails on Travis // XXX Fails on Travis
// await test_utils.waitUntil(() => view.content.scrollTop, 1000) // await test_utils.waitUntil(() => view.content.scrollTop, 1000)
await test_utils.waitUntil(() => !view.model.get('auto_scrolled'), 500); await test_utils.waitUntil(() => !view.model.get('auto_scrolled'), 500);
view.content.scrollTop = 0; view.content.scrollTop = 0;
// XXX Fails on Travis // XXX Fails on Travis
// await test_utils.waitUntil(() => view.scrolled, 900); // await test_utils.waitUntil(() => view.model.get('scrolled'), 900);
view.scrolled = true; view.model.set('scrolled', true);
const text = Array.from(view.el.querySelectorAll('.chat-content .chat-msg__text'))
.map(e => e.textContent)
.join(' ');
expect(text).toBe(_.range(20).reverse().map(n => `Message: ${n}`).join(' '));
const message = 'This message is received while the chat area is scrolled up'; const message = 'This message is received while the chat area is scrolled up';
_converse.chatboxes.onMessage($msg({ _converse.chatboxes.onMessage($msg({
@ -1858,10 +1852,10 @@
await test_utils.waitUntil(() => view.model.messages.length > 20, 1000); await test_utils.waitUntil(() => view.model.messages.length > 20, 1000);
// Now check that the message appears inside the chatbox in the DOM // Now check that the message appears inside the chatbox in the DOM
const chat_content = view.el.querySelector('.chat-content'); const chat_content = view.el.querySelector('.chat-content');
const msg_txt = sizzle('.chat-content .chat-msg:first .chat-msg__text', view.el).pop().textContent; const msg_txt = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop().textContent;
expect(msg_txt).toEqual(message); expect(msg_txt).toEqual(message);
await test_utils.waitUntil(() => u.isVisible(view.el.querySelector('.new-msgs-indicator')), 900); await test_utils.waitUntil(() => u.isVisible(view.el.querySelector('.new-msgs-indicator')), 900);
expect(view.scrolled).toBe(true); expect(view.model.get('scrolled')).toBe(true);
expect(view.content.scrollTop).toBe(0); expect(view.content.scrollTop).toBe(0);
expect(u.isVisible(view.el.querySelector('.new-msgs-indicator'))).toBeTruthy(); expect(u.isVisible(view.el.querySelector('.new-msgs-indicator'))).toBeTruthy();
// Scroll down again // Scroll down again
@ -1962,9 +1956,9 @@
</message>`); </message>`);
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
await new Promise((resolve, reject) => view.once('messageInserted', resolve)); await new Promise((resolve, reject) => view.once('messageInserted', resolve));
msg = view.el.querySelector('.chat-msg:first-child .chat-msg__text'); msg = view.el.querySelector('.chat-msg:last-child .chat-msg__text');
expect(msg.innerHTML).toEqual('<!-- message gets added here via renderMessage -->'); // Emtpy expect(msg.innerHTML).toEqual('<!-- message gets added here via renderMessage -->'); // Emtpy
media = view.el.querySelector('.chat-msg:first-child .chat-msg__media'); media = view.el.querySelector('.chat-msg:last-child .chat-msg__media');
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual( expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
'<!-- src/templates/audio.html -->'+ '<!-- src/templates/audio.html -->'+
'<audio controls="" src="https://montague.lit/audio.mp3"></audio>'+ '<audio controls="" src="https://montague.lit/audio.mp3"></audio>'+
@ -2012,9 +2006,9 @@
</message>`); </message>`);
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
await new Promise((resolve, reject) => view.once('messageInserted', resolve)); await new Promise((resolve, reject) => view.once('messageInserted', resolve));
msg = view.el.querySelector('.chat-msg:first-child .chat-msg__text'); msg = view.el.querySelector('.chat-msg:last-child .chat-msg__text');
expect(msg.innerHTML).toEqual('<!-- message gets added here via renderMessage -->'); // Emtpy expect(msg.innerHTML).toEqual('<!-- message gets added here via renderMessage -->'); // Emtpy
media = view.el.querySelector('.chat-msg:first-child .chat-msg__media'); media = view.el.querySelector('.chat-msg:last-child .chat-msg__media');
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual( expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
'<!-- src/templates/video.html -->'+ '<!-- src/templates/video.html -->'+
'<video controls="" src="https://montague.lit/video.mp4" style="max-height: 50vh"></video>'+ '<video controls="" src="https://montague.lit/video.mp4" style="max-height: 50vh"></video>'+
@ -2355,7 +2349,7 @@
expect(view.model.messages.last().get('affiliation')).toBe('member'); expect(view.model.messages.last().get('affiliation')).toBe('member');
expect(view.model.messages.last().get('role')).toBe('participant'); expect(view.model.messages.last().get('role')).toBe('participant');
expect(view.el.querySelectorAll('.chat-msg').length).toBe(2); expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
expect(sizzle('.chat-msg__author:first', view.el).pop().classList.value.trim()).toBe('chat-msg__author participant'); expect(sizzle('.chat-msg__author', view.el).pop().classList.value.trim()).toBe('chat-msg__author participant');
presence = $pres({ presence = $pres({
to:'romeo@montague.lit/orchard', to:'romeo@montague.lit/orchard',
@ -2561,7 +2555,7 @@
expect(textarea.value).toBe('But soft, what light through yonder window breaks?'); expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
expect(view.model.messages.at(0).get('correcting')).toBe(true); expect(view.model.messages.at(0).get('correcting')).toBe(true);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(2); expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
await test_utils.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg.groupchat:last', view.el).pop()), 500); await test_utils.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
expect(textarea.value).toBe('But soft, what light through yonder window breaks?'); expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
view.onKeyDown({ view.onKeyDown({
target: textarea, target: textarea,

View File

@ -375,8 +375,8 @@
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2); await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2);
const info_texts = Array.from(view.el.querySelectorAll('.chat-content .chat-info')).map(e => e.textContent); const info_texts = Array.from(view.el.querySelectorAll('.chat-content .chat-info')).map(e => e.textContent);
expect(info_texts[1]).toBe('A new groupchat has been created'); expect(info_texts[0]).toBe('A new groupchat has been created');
expect(info_texts[0]).toBe('nicky has entered the groupchat'); expect(info_texts[1]).toBe('nicky has entered the groupchat');
// An instant room is created by saving the default configuratoin. // An instant room is created by saving the default configuratoin.
// //
@ -482,9 +482,9 @@
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
await test_utils.waitUntil(() => chat_content.querySelectorAll('.chat-info').length === 2); await test_utils.waitUntil(() => chat_content.querySelectorAll('.chat-info').length === 2);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
.toBe("This groupchat is not anonymous");
expect(sizzle('div.chat-info:first', chat_content).pop().textContent) expect(sizzle('div.chat-info:first', chat_content).pop().textContent)
.toBe("This groupchat is not anonymous");
expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
.toBe("some1 has entered the groupchat"); .toBe("some1 has entered the groupchat");
done(); done();
})); }));
@ -495,7 +495,7 @@
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) { async function (done, _converse) {
await test_utils.openAndEnterChatRoom(_converse, "coven", 'chat.shakespeare.lit', 'some1'); await test_utils.openChatRoom(_converse, "coven", 'chat.shakespeare.lit', 'some1');
const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
const chat_content = view.el.querySelector('.chat-content'); const chat_content = view.el.querySelector('.chat-content');
/* We don't show join/leave messages for existing occupants. We /* We don't show join/leave messages for existing occupants. We
@ -512,10 +512,7 @@
'role': 'participant' 'role': 'participant'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
const info_msgs = sizzle('.chat-info', chat_content); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(0);
expect(info_msgs.length).toBe(2);
expect(info_msgs.pop().textContent).toBe('some1 has entered the groupchat');
expect(info_msgs.pop().textContent).toBe('oldguy has entered the groupchat');
/* <presence to="romeo@montague.lit/_converse.js-29092160" /* <presence to="romeo@montague.lit/_converse.js-29092160"
* from="coven@chat.shakespeare.lit/some1"> * from="coven@chat.shakespeare.lit/some1">
@ -536,7 +533,7 @@
}).up() }).up()
.c('status', {code: '110'}); .c('status', {code: '110'});
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info:last', chat_content).pop().textContent) expect(sizzle('div.chat-info:first', chat_content).pop().textContent)
.toBe("some1 has entered the groupchat"); .toBe("some1 has entered the groupchat");
presence = $pres({ presence = $pres({
@ -550,8 +547,8 @@
'role': 'participant' 'role': 'participant'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(3); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent) expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
.toBe("newguy has entered the groupchat"); .toBe("newguy has entered the groupchat");
const msg = $msg({ const msg = $msg({
@ -576,8 +573,8 @@
'role': 'participant' 'role': 'participant'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(4); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(3);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent) expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
.toBe("newgirl has entered the groupchat"); .toBe("newgirl has entered the groupchat");
// Don't show duplicate join messages // Don't show duplicate join messages
@ -591,7 +588,7 @@
'role': 'participant' 'role': 'participant'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(4); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(3);
/* <presence /* <presence
* from='coven@chat.shakespeare.lit/thirdwitch' * from='coven@chat.shakespeare.lit/thirdwitch'
@ -618,8 +615,8 @@
'role': 'none' 'role': 'none'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(5); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(4);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe( expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(
'newguy has left the groupchat. '+ 'newguy has left the groupchat. '+
'"Disconnected: Replaced by new connection"'); '"Disconnected: Replaced by new connection"');
@ -635,9 +632,8 @@
'role': 'participant' 'role': 'participant'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(4);
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(5); let msg_el = sizzle('div.chat-info:last', chat_content).pop();
let msg_el = sizzle('div.chat-info:first', chat_content).pop();
expect(msg_el.textContent).toBe("newguy has left and re-entered the groupchat"); expect(msg_el.textContent).toBe("newguy has left and re-entered the groupchat");
expect(msg_el.getAttribute('data-leavejoin')).toBe('newguy'); expect(msg_el.getAttribute('data-leavejoin')).toBe('newguy');
@ -653,8 +649,8 @@
'role': 'none' 'role': 'none'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(5); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(4);
msg_el = sizzle('.chat-info:first', chat_content).pop(); msg_el = sizzle('div.chat-info', chat_content).pop();
expect(msg_el.textContent).toBe('newguy has left the groupchat'); expect(msg_el.textContent).toBe('newguy has left the groupchat');
expect(msg_el.getAttribute('data-leave')).toBe('newguy'); expect(msg_el.getAttribute('data-leave')).toBe('newguy');
@ -669,8 +665,8 @@
'role': 'participant' 'role': 'participant'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('.chat-info').length).toBe(6); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(5);
expect(sizzle('.chat-info:first', chat_content).pop().textContent) expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
.toBe("nomorenicks has entered the groupchat"); .toBe("nomorenicks has entered the groupchat");
presence = $pres({ presence = $pres({
@ -684,8 +680,8 @@
'role': 'none' 'role': 'none'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(6); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(5);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent) expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
.toBe("nomorenicks has entered and left the groupchat"); .toBe("nomorenicks has entered and left the groupchat");
presence = $pres({ presence = $pres({
@ -699,8 +695,8 @@
'role': 'participant' 'role': 'participant'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(6); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(5);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent) expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
.toBe("nomorenicks has entered the groupchat"); .toBe("nomorenicks has entered the groupchat");
// Test a member joining and leaving // Test a member joining and leaving
@ -714,7 +710,7 @@
'role': 'participant' 'role': 'participant'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(7); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(6);
/* <presence /* <presence
* from='coven@chat.shakespeare.lit/thirdwitch' * from='coven@chat.shakespeare.lit/thirdwitch'
@ -741,8 +737,8 @@
'role': 'none' 'role': 'none'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(7); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(6);
expect(sizzle('.chat-info:first', chat_content).pop().textContent).toBe( expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(
'insider has entered and left the groupchat. '+ 'insider has entered and left the groupchat. '+
'"Disconnected: Replaced by new connection"'); '"Disconnected: Replaced by new connection"');
@ -763,8 +759,8 @@
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('.chat-info').length).toBe(7); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(6);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("newgirl has entered and left the groupchat"); expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("newgirl has entered and left the groupchat");
expect(view.model.occupants.length).toBe(4); expect(view.model.occupants.length).toBe(4);
done(); done();
})); }));
@ -790,7 +786,7 @@
</presence>`); </presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(2); expect(sizzle('div.chat-info', chat_content).length).toBe(2);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("fabio has entered the groupchat"); expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("fabio has entered the groupchat");
presence = u.toStanza( presence = u.toStanza(
`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/Dele Olajide"> `<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/Dele Olajide">
@ -800,7 +796,7 @@
</presence>`); </presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(3); expect(sizzle('div.chat-info', chat_content).length).toBe(3);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("Dele Olajide has entered the groupchat"); expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("Dele Olajide has entered the groupchat");
presence = u.toStanza( presence = u.toStanza(
`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/jcbrand"> `<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/jcbrand">
@ -811,7 +807,7 @@
</presence>`); </presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(4); expect(sizzle('div.chat-info', chat_content).length).toBe(4);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("jcbrand has entered the groupchat"); expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("jcbrand has entered the groupchat");
presence = u.toStanza( presence = u.toStanza(
`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" type="unavailable" from="coven@chat.shakespeare.lit/Dele Olajide"> `<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" type="unavailable" from="coven@chat.shakespeare.lit/Dele Olajide">
@ -821,7 +817,7 @@
</presence>`); </presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(4); expect(sizzle('div.chat-info', chat_content).length).toBe(4);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("Dele Olajide has entered and left the groupchat"); expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("Dele Olajide has entered and left the groupchat");
presence = u.toStanza( presence = u.toStanza(
`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/Dele Olajide"> `<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/Dele Olajide">
@ -831,7 +827,7 @@
</presence>`); </presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(4); expect(sizzle('div.chat-info', chat_content).length).toBe(4);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("Dele Olajide has entered the groupchat"); expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("Dele Olajide has entered the groupchat");
presence = u.toStanza( presence = u.toStanza(
`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/fuvuv" xml:lang="en"> `<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/fuvuv" xml:lang="en">
@ -843,7 +839,7 @@
</presence>`); </presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(5); expect(sizzle('div.chat-info', chat_content).length).toBe(5);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("fuvuv has entered the groupchat"); expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("fuvuv has entered the groupchat");
presence = u.toStanza( presence = u.toStanza(
`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" type="unavailable" from="coven@chat.shakespeare.lit/fuvuv"> `<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" type="unavailable" from="coven@chat.shakespeare.lit/fuvuv">
@ -853,7 +849,7 @@
</presence>`); </presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(5); expect(sizzle('div.chat-info', chat_content).length).toBe(5);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("fuvuv has entered and left the groupchat"); expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("fuvuv has entered and left the groupchat");
presence = u.toStanza( presence = u.toStanza(
`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" type="unavailable" from="coven@chat.shakespeare.lit/fabio"> `<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" type="unavailable" from="coven@chat.shakespeare.lit/fabio">
@ -864,7 +860,7 @@
</presence>`); </presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(5); expect(sizzle('div.chat-info', chat_content).length).toBe(5);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe( expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(
`fabio has entered and left the groupchat. "Disconnected: Replaced by new connection"`); `fabio has entered and left the groupchat. "Disconnected: Replaced by new connection"`);
presence = u.toStanza( presence = u.toStanza(
@ -877,7 +873,7 @@
</presence>`); </presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(5); expect(sizzle('div.chat-info', chat_content).length).toBe(5);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe( expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(
`fabio has entered the groupchat. "Ready for a new day"`); `fabio has entered the groupchat. "Ready for a new day"`);
// XXX: hack so that we can test leave/enter of occupants // XXX: hack so that we can test leave/enter of occupants
@ -904,7 +900,7 @@
</presence>`); </presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(2); expect(sizzle('div.chat-info', chat_content).length).toBe(2);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe( expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(
`Dele Olajide has left the groupchat`); `Dele Olajide has left the groupchat`);
presence = u.toStanza( presence = u.toStanza(
@ -916,7 +912,7 @@
</presence>`); </presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(2); expect(sizzle('div.chat-info', chat_content).length).toBe(2);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe( expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(
`fabio has left and re-entered the groupchat`); `fabio has left and re-entered the groupchat`);
done(); done();
})); }));
@ -942,7 +938,7 @@
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2);
expect(sizzle('.chat-info:first', chat_content).pop().textContent).toBe('newguy has entered the groupchat'); expect(sizzle('div.chat-info', chat_content).pop().textContent).toBe('newguy has entered the groupchat');
presence = $pres({ presence = $pres({
to: 'romeo@montague.lit/orchard', to: 'romeo@montague.lit/orchard',
@ -958,7 +954,7 @@
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2);
expect(sizzle('.chat-info:first', chat_content).pop().textContent).toBe('newguy has entered and left the groupchat'); expect(sizzle('div.chat-info', chat_content).pop().textContent).toBe('newguy has entered and left the groupchat');
presence = u.toStanza( presence = u.toStanza(
`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/fabio"> `<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/fabio">
@ -970,7 +966,7 @@
</presence>`); </presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('.chat-info:first', chat_content).pop().textContent).toBe(`fabio has entered the groupchat`); expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(`fabio has entered the groupchat`);
presence = u.toStanza( presence = u.toStanza(
`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/Dele Olajide"> `<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/Dele Olajide">
@ -979,8 +975,8 @@
</x> </x>
</presence>`); </presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('.chat-info', chat_content).length).toBe(4); expect(sizzle('div.chat-info', chat_content).length).toBe(4);
expect(sizzle('.chat-info:first', chat_content).pop().textContent).toBe("Dele Olajide has entered the groupchat"); expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("Dele Olajide has entered the groupchat");
await test_utils.sendMessage(view, 'hello world'); await test_utils.sendMessage(view, 'hello world');
presence = u.toStanza( presence = u.toStanza(
@ -991,8 +987,8 @@
</x> </x>
</presence>`); </presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('.chat-info', chat_content).length).toBe(5); expect(sizzle('div.chat-info', chat_content).length).toBe(5);
expect(sizzle('.chat-info:first', chat_content).pop().textContent).toBe(`Dele Olajide has left the groupchat`); expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(`Dele Olajide has left the groupchat`);
done(); done();
})); }));
@ -1049,11 +1045,11 @@
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
const chat_content = view.el.querySelector('.chat-content'); const chat_content = view.el.querySelector('.chat-content');
const messages = chat_content.querySelectorAll('.chat-info'); const messages = chat_content.querySelectorAll('div.chat-info');
expect(messages.length).toBe(3); expect(messages.length).toBe(3);
expect(messages[2].textContent).toBe('romeo has entered the groupchat'); expect(messages[0].textContent).toBe('romeo has entered the groupchat');
expect(messages[1].textContent).toBe('Guus has entered the groupchat'); expect(messages[1].textContent).toBe('Guus has entered the groupchat');
expect(messages[0].textContent).toBe('Guus has left and re-entered the groupchat'); expect(messages[2].textContent).toBe('Guus has left and re-entered the groupchat');
done(); done();
})); }));
@ -1106,8 +1102,8 @@
expect(indicator.getAttribute('data-isodate')).toEqual(dayjs().startOf('day').toISOString()); expect(indicator.getAttribute('data-isodate')).toEqual(dayjs().startOf('day').toISOString());
expect(indicator.querySelector('time').getAttribute('class')).toEqual('separator-text'); expect(indicator.querySelector('time').getAttribute('class')).toEqual('separator-text');
expect(indicator.querySelector('time').textContent).toEqual(dayjs().startOf('day').format("dddd MMM Do YYYY")); expect(indicator.querySelector('time').textContent).toEqual(dayjs().startOf('day').format("dddd MMM Do YYYY"));
expect(chat_content.querySelectorAll('.chat-info').length).toBe(2); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2);
expect(chat_content.querySelector('.chat-info:first-child').textContent).toBe( expect(chat_content.querySelector('div.chat-info:last-child').textContent).toBe(
"some1 has entered the groupchat" "some1 has entered the groupchat"
); );
@ -1135,8 +1131,8 @@
expect(indicator.getAttribute('data-isodate')).toEqual(dayjs().startOf('day').toISOString()); expect(indicator.getAttribute('data-isodate')).toEqual(dayjs().startOf('day').toISOString());
expect(indicator.querySelector('time').textContent).toEqual(dayjs().startOf('day').format("dddd MMM Do YYYY")); expect(indicator.querySelector('time').textContent).toEqual(dayjs().startOf('day').format("dddd MMM Do YYYY"));
expect(chat_content.querySelectorAll('.chat-info').length).toBe(3); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(3);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe( expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(
'some1 has left the groupchat. '+ 'some1 has left the groupchat. '+
'"Disconnected: Replaced by new connection"'); '"Disconnected: Replaced by new connection"');
@ -1167,12 +1163,12 @@
let time = chat_content.querySelectorAll('time.separator-text'); let time = chat_content.querySelectorAll('time.separator-text');
expect(time.length).toEqual(4); expect(time.length).toEqual(4);
indicator = sizzle('.date-separator:eq(0)', chat_content).pop(); indicator = sizzle('.date-separator:eq(3)', chat_content).pop();
expect(indicator.getAttribute('class')).toEqual('message date-separator'); expect(indicator.getAttribute('class')).toEqual('message date-separator');
expect(indicator.getAttribute('data-isodate')).toEqual(dayjs().startOf('day').toISOString()); expect(indicator.getAttribute('data-isodate')).toEqual(dayjs().startOf('day').toISOString());
expect(indicator.querySelector('time').textContent).toEqual(dayjs().startOf('day').format("dddd MMM Do YYYY")); expect(indicator.querySelector('time').textContent).toEqual(dayjs().startOf('day').format("dddd MMM Do YYYY"));
expect(chat_content.querySelectorAll('.chat-info').length).toBe(4); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(4);
expect(sizzle('.chat-info:first', chat_content).pop().textContent) expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
.toBe("newguy has entered the groupchat"); .toBe("newguy has entered the groupchat");
jasmine.clock().tick(ONE_DAY_LATER); jasmine.clock().tick(ONE_DAY_LATER);
@ -1207,12 +1203,12 @@
time = chat_content.querySelectorAll('time.separator-text'); time = chat_content.querySelectorAll('time.separator-text');
expect(time.length).toEqual(6); expect(time.length).toEqual(6);
indicator = sizzle('.date-separator:eq(0)', chat_content).pop(); indicator = sizzle('.date-separator:eq(5)', chat_content).pop();
expect(indicator.getAttribute('class')).toEqual('message date-separator'); expect(indicator.getAttribute('class')).toEqual('message date-separator');
expect(indicator.getAttribute('data-isodate')).toEqual(dayjs().startOf('day').toISOString()); expect(indicator.getAttribute('data-isodate')).toEqual(dayjs().startOf('day').toISOString());
expect(indicator.querySelector('time').textContent).toEqual(dayjs().startOf('day').format("dddd MMM Do YYYY")); expect(indicator.querySelector('time').textContent).toEqual(dayjs().startOf('day').format("dddd MMM Do YYYY"));
expect(chat_content.querySelectorAll('.chat-info').length).toBe(5); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(5);
expect(sizzle('.chat-info:first', chat_content).pop().textContent).toBe( expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(
'newguy has left the groupchat. '+ 'newguy has left the groupchat. '+
'"Disconnected: Replaced by new connection"'); '"Disconnected: Replaced by new connection"');
jasmine.clock().uninstall(); jasmine.clock().uninstall();
@ -1255,8 +1251,8 @@
}).c('body').t(message).tree(); }).c('body').t(message).tree();
await view.model.onMessage(msg); await view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve)); await new Promise((resolve, reject) => view.once('messageInserted', resolve));
expect(_.includes(sizzle('.chat-msg__author:first', view.el).pop().textContent, '**Romeo Montague')).toBeTruthy(); expect(_.includes(sizzle('.chat-msg__author:last', view.el).pop().textContent, '**Romeo Montague')).toBeTruthy();
expect(sizzle('.chat-msg__text:first', view.el).pop().textContent).toBe('is as well'); expect(sizzle('.chat-msg__text:last', view.el).pop().textContent).toBe('is as well');
done(); done();
})); }));
@ -1803,7 +1799,7 @@
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2); await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2);
const info_text = sizzle('.chat-content .chat-info:last', view.el).pop().textContent; const info_text = sizzle('.chat-content .chat-info:first', view.el).pop().textContent;
expect(info_text).toBe('Your nickname has been automatically set to thirdwitch'); expect(info_text).toBe('Your nickname has been automatically set to thirdwitch');
done(); done();
})); }));
@ -2021,7 +2017,7 @@
// Now check that the message appears inside the chatbox in the DOM // Now check that the message appears inside the chatbox in the DOM
const chat_content = view.el.querySelector('.chat-content'); const chat_content = view.el.querySelector('.chat-content');
const msg_txt = sizzle('.chat-msg:first .chat-msg__text', chat_content).pop().textContent; const msg_txt = sizzle('.chat-msg:last .chat-msg__text', chat_content).pop().textContent;
expect(msg_txt).toEqual(message); expect(msg_txt).toEqual(message);
expect(view.content.scrollTop).toBe(0); expect(view.content.scrollTop).toBe(0);
done(); done();
@ -2159,8 +2155,8 @@
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2); await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2);
const info_messages = view.el.querySelectorAll('.chat-content .chat-info'); const info_messages = view.el.querySelectorAll('.chat-content .chat-info');
expect(info_messages[1].textContent).toBe('romeo has entered the groupchat'); expect(info_messages[0].textContent).toBe('romeo has entered the groupchat');
expect(info_messages[0].textContent).toBe('groupchat logging is now enabled'); expect(info_messages[1].textContent).toBe('groupchat logging is now enabled');
done(); done();
})); }));
@ -2238,7 +2234,7 @@
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 2); await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 2);
expect(sizzle('.chat-info:first').pop().textContent).toBe( expect(sizzle('div.chat-info:last').pop().textContent).toBe(
__(_converse.muc.new_nickname_messages["303"], "newnick") __(_converse.muc.new_nickname_messages["303"], "newnick")
); );
expect(view.model.get('connection_status')).toBe(converse.ROOMSTATUS.ENTERED); expect(view.model.get('connection_status')).toBe(converse.ROOMSTATUS.ENTERED);
@ -2581,7 +2577,7 @@
_converse.connection._dataRecv(test_utils.createRequest(message)); _converse.connection._dataRecv(test_utils.createRequest(message));
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 3); await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 3);
const chat_body = view.el.querySelector('.chatroom-body'); const chat_body = view.el.querySelector('.chatroom-body');
expect(sizzle('.message:first', chat_body).pop().textContent) expect(sizzle('.message:last', chat_body).pop().textContent)
.toBe('This groupchat is now no longer anonymous'); .toBe('This groupchat is now no longer anonymous');
done(); done();
})); }));
@ -3181,7 +3177,7 @@
}); });
expect(view.validateRoleOrAffiliationChangeArgs).toHaveBeenCalled(); expect(view.validateRoleOrAffiliationChangeArgs).toHaveBeenCalled();
expect(view.showErrorMessage).toHaveBeenCalled(); expect(view.showErrorMessage).toHaveBeenCalled();
expect(view.el.querySelector('.message:first-child').textContent).toBe( expect(view.el.querySelector('.message:last-child').textContent).toBe(
"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();
@ -3235,7 +3231,7 @@
textarea.value = '/ban joe22'; textarea.value = '/ban joe22';
view.onFormSubmitted(new Event('submit')); view.onFormSubmitted(new Event('submit'));
expect(view.el.querySelector('.message:first-child').textContent).toBe( expect(view.el.querySelector('.message:last-child').textContent).toBe(
"Error: couldn't find a groupchat participant based on your arguments"); "Error: couldn't find a groupchat participant based on your arguments");
done(); done();
})); }));
@ -3326,7 +3322,7 @@
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 4); await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 4);
expect(view.el.querySelectorAll('.chat-info')[0].textContent).toBe("annoying guy has been kicked out"); expect(view.el.querySelectorAll('.chat-info')[3].textContent).toBe("annoying guy has been kicked out");
expect(view.el.querySelectorAll('.chat-info').length).toBe(4); expect(view.el.querySelectorAll('.chat-info').length).toBe(4);
done(); done();
})); }));
@ -3372,10 +3368,10 @@
'role': 'participant' 'role': 'participant'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
const info_msg = view.el.querySelector('.chat-info:first-child'); var info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0);
expect(info_msg.textContent).toBe("trustworthyguy has entered the groupchat"); expect(info_msgs.pop().textContent).toBe("trustworthyguy has entered the groupchat");
const textarea = view.el.querySelector('.chat-textarea') var textarea = view.el.querySelector('.chat-textarea')
textarea.value = '/op'; textarea.value = '/op';
view.onKeyDown({ view.onKeyDown({
target: textarea, target: textarea,
@ -3428,7 +3424,7 @@
'role': 'moderator' 'role': 'moderator'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
let info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0); info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0);
expect(info_msgs.pop().textContent).toBe("trustworthyguy is now a moderator"); expect(info_msgs.pop().textContent).toBe("trustworthyguy is now a moderator");
// Call now with the correct amount of arguments. // Call now with the correct amount of arguments.
// XXX: Calling onFormSubmitted directly, trying // XXX: Calling onFormSubmitted directly, trying
@ -3502,7 +3498,7 @@
* </x> * </x>
* </presence> * </presence>
*/ */
let presence = $pres({ var 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'
@ -3514,8 +3510,8 @@
'role': 'participant' 'role': 'participant'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
const info_msg = sizzle('.chat-info:first', view.el).pop(); var info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0);
expect(info_msg.textContent).toBe("annoyingGuy has entered the groupchat"); expect(info_msgs.pop().textContent).toBe("annoyingGuy has entered the groupchat");
const textarea = view.el.querySelector('.chat-textarea') const textarea = view.el.querySelector('.chat-textarea')
textarea.value = '/mute'; textarea.value = '/mute';
@ -3569,7 +3565,7 @@
'role': 'visitor' 'role': 'visitor'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
let info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0); info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0);
expect(info_msgs.pop().textContent).toBe("annoyingGuy has been muted"); expect(info_msgs.pop().textContent).toBe("annoyingGuy has been muted");
// Call now with the correct of arguments. // Call now with the correct of arguments.
@ -4707,7 +4703,7 @@
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2);
expect(sizzle('.chat-info:first', chat_content).pop().textContent) expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
.toBe("newguy has entered the groupchat"); .toBe("newguy has entered the groupchat");
presence = $pres({ presence = $pres({
@ -4722,7 +4718,7 @@
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(3); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(3);
expect(sizzle('.chat-info:first', chat_content).pop().textContent) expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
.toBe("nomorenicks has entered the groupchat"); .toBe("nomorenicks has entered the groupchat");
// See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions // See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
@ -4741,9 +4737,9 @@
// Check that the notification appears inside the chatbox in the DOM // Check that the notification appears inside the chatbox in the DOM
let events = view.el.querySelectorAll('.chat-event'); let events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3); expect(events.length).toBe(3);
expect(events[2].textContent).toEqual('some1 has entered the groupchat'); expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat'); expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
let notifications = view.el.querySelectorAll('.chat-state-notification'); let notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(1); expect(notifications.length).toBe(1);
@ -4763,9 +4759,9 @@
events = view.el.querySelectorAll('.chat-event'); events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3); expect(events.length).toBe(3);
expect(events[2].textContent).toEqual('some1 has entered the groupchat'); expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat'); expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
notifications = view.el.querySelectorAll('.chat-state-notification'); notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(1); expect(notifications.length).toBe(1);
@ -4782,15 +4778,15 @@
await view.model.onMessage(msg); await view.model.onMessage(msg);
events = view.el.querySelectorAll('.chat-event'); events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3); expect(events.length).toBe(3);
expect(events[2].textContent).toEqual('some1 has entered the groupchat'); expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat'); expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
await test_utils.waitUntil(() => (view.el.querySelectorAll('.chat-state-notification').length === 2)); await test_utils.waitUntil(() => (view.el.querySelectorAll('.chat-state-notification').length === 2));
notifications = view.el.querySelectorAll('.chat-state-notification'); notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(2); expect(notifications.length).toBe(2);
expect(notifications[1].textContent).toEqual('nomorenicks is typing'); expect(notifications[0].textContent).toEqual('nomorenicks is typing');
expect(notifications[0].textContent).toEqual('newguy is typing'); expect(notifications[1].textContent).toEqual('newguy is typing');
// Check that new messages appear under the chat state notifications // Check that new messages appear under the chat state notifications
msg = $msg({ msg = $msg({
@ -4811,9 +4807,9 @@
timeout_functions[0](); timeout_functions[0]();
events = view.el.querySelectorAll('.chat-event'); events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3); expect(events.length).toBe(3);
expect(events[2].textContent).toEqual('some1 has entered the groupchat'); expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat'); expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
notifications = view.el.querySelectorAll('.chat-state-notification'); notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(1); expect(notifications.length).toBe(1);
@ -4822,9 +4818,9 @@
timeout_functions[1](); timeout_functions[1]();
events = view.el.querySelectorAll('.chat-event'); events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3); expect(events.length).toBe(3);
expect(events[2].textContent).toEqual('some1 has entered the groupchat'); expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat'); expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
notifications = view.el.querySelectorAll('.chat-state-notification'); notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(0); expect(notifications.length).toBe(0);
@ -4877,7 +4873,7 @@
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2);
expect(sizzle('.chat-info:first', chat_content).pop().textContent) expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
.toBe("newguy has entered the groupchat"); .toBe("newguy has entered the groupchat");
presence = $pres({ presence = $pres({
@ -4892,7 +4888,7 @@
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(3); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(3);
expect(sizzle('.chat-info:first', chat_content).pop().textContent) expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
.toBe("nomorenicks has entered the groupchat"); .toBe("nomorenicks has entered the groupchat");
// See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions // See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
@ -4909,9 +4905,9 @@
// Check that the notification appears inside the chatbox in the DOM // Check that the notification appears inside the chatbox in the DOM
var events = view.el.querySelectorAll('.chat-event'); var events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3); expect(events.length).toBe(3);
expect(events[2].textContent).toEqual('some1 has entered the groupchat'); expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat'); expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-state-notification').length); await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-state-notification').length);
let notifications = view.el.querySelectorAll('.chat-state-notification'); let notifications = view.el.querySelectorAll('.chat-state-notification');
@ -4929,9 +4925,9 @@
events = view.el.querySelectorAll('.chat-event'); events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3); expect(events.length).toBe(3);
expect(events[2].textContent).toEqual('some1 has entered the groupchat'); expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat'); expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
notifications = view.el.querySelectorAll('.chat-state-notification'); notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(1); expect(notifications.length).toBe(1);
@ -4947,9 +4943,9 @@
await view.model.onMessage(msg); await view.model.onMessage(msg);
events = view.el.querySelectorAll('.chat-event'); events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3); expect(events.length).toBe(3);
expect(events[2].textContent).toEqual('some1 has entered the groupchat'); expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat'); expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-state-notification').length === 2); await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-state-notification').length === 2);
notifications = view.el.querySelectorAll('.chat-state-notification'); notifications = view.el.querySelectorAll('.chat-state-notification');
@ -4970,9 +4966,9 @@
await view.model.onMessage(msg); await view.model.onMessage(msg);
events = view.el.querySelectorAll('.chat-event'); events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3); expect(events.length).toBe(3);
expect(events[2].textContent).toEqual('some1 has entered the groupchat'); expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat'); expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
await test_utils.waitUntil(() => { await test_utils.waitUntil(() => {
return _.map( return _.map(

View File

@ -208,7 +208,7 @@
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
await new Promise((resolve, reject) => view.once('messageInserted', resolve)); await new Promise((resolve, reject) => view.once('messageInserted', resolve));
expect(view.model.messages.length).toBe(2); expect(view.model.messages.length).toBe(2);
expect(view.el.querySelectorAll('.chat-msg__body')[0].textContent.trim()) expect(view.el.querySelectorAll('.chat-msg__body')[1].textContent.trim())
.toBe('This is an encrypted message from the contact'); .toBe('This is an encrypted message from the contact');
// #1193 Check for a received message without <body> tag // #1193 Check for a received message without <body> tag
@ -228,7 +228,7 @@
await new Promise((resolve, reject) => view.once('messageInserted', resolve)); await new Promise((resolve, reject) => view.once('messageInserted', resolve));
await test_utils.waitUntil(() => view.model.messages.length > 1); await test_utils.waitUntil(() => view.model.messages.length > 1);
expect(view.model.messages.length).toBe(3); expect(view.model.messages.length).toBe(3);
expect(view.el.querySelectorAll('.chat-msg__body')[0].textContent.trim()) expect(view.el.querySelectorAll('.chat-msg__body')[2].textContent.trim())
.toBe('Another received encrypted message without fallback'); .toBe('Another received encrypted message without fallback');
done(); done();
})); }));

View File

@ -345,6 +345,7 @@ converse.plugins.add('converse-chatview', {
initialize () { initialize () {
this.initDebounced(); this.initDebounced();
this.model.messages.on('add', this.onMessageAdded, this); this.model.messages.on('add', this.onMessageAdded, this);
this.model.messages.on('rendered', this.scrollDown, this);
this.model.messages.on('reset', () => { this.model.messages.on('reset', () => {
this.content.innerHTML = ''; this.content.innerHTML = '';
this.removeAll(); this.removeAll();
@ -366,6 +367,7 @@ converse.plugins.add('converse-chatview', {
}, },
initDebounced () { initDebounced () {
this.scrollDown = _.debounce(this._scrollDown, 100);
this.markScrolled = _.debounce(this._markScrolled, 100); this.markScrolled = _.debounce(this._markScrolled, 100);
this.show = _.debounce(this._show, 500, {'leading': true}); this.show = _.debounce(this._show, 500, {'leading': true});
}, },
@ -544,7 +546,8 @@ converse.plugins.add('converse-chatview', {
await this.model.messages.fetched; await this.model.messages.fetched;
await Promise.all(this.model.messages.map(m => this.onMessageAdded(m))); await Promise.all(this.model.messages.map(m => this.onMessageAdded(m)));
this.insertIntoDOM(); this.insertIntoDOM();
this.content.addEventListener('scroll', () => this.markScrolled()); this.scrollDown();
this.content.addEventListener('scroll', this.markScrolled.bind(this));
}, },
insertIntoDOM () { insertIntoDOM () {
@ -565,23 +568,26 @@ converse.plugins.add('converse-chatview', {
'message': message, 'message': message,
'isodate': isodate, 'isodate': isodate,
})); }));
this.insertDayIndicator(this.content.firstElementChild); this.insertDayIndicator(this.content.lastElementChild);
this.scrollDown();
return isodate; return isodate;
}, },
showErrorMessage (message) { showErrorMessage (message) {
this.content.insertAdjacentHTML( this.content.insertAdjacentHTML(
'afterBegin', 'beforeend',
tpl_error_message({'message': message, 'isodate': (new Date()).toISOString() }) tpl_error_message({'message': message, 'isodate': (new Date()).toISOString() })
); );
this.scrollDown();
}, },
addSpinner (append=false) { addSpinner (append=false) {
if (_.isNull(this.el.querySelector('.spinner'))) { if (_.isNull(this.el.querySelector('.spinner'))) {
if (append) { if (append) {
this.content.insertAdjacentHTML('afterBegin', tpl_spinner()); this.content.insertAdjacentHTML('beforeend', tpl_spinner());
this.scrollDown();
} else { } else {
this.content.insertAdjacentHTML('beforeEnd', tpl_spinner()); this.content.insertAdjacentHTML('afterbegin', tpl_spinner());
} }
} }
}, },
@ -602,16 +608,16 @@ converse.plugins.add('converse-chatview', {
* which specifies its creation date. * which specifies its creation date.
*/ */
insertDayIndicator (next_msg_el) { insertDayIndicator (next_msg_el) {
const earlier_msg_el = u.getNextElement(next_msg_el, ".message:not(.chat-state-notification)"), const prev_msg_el = u.getPreviousElement(next_msg_el, ".message:not(.chat-state-notification)"),
earlier_msg_date = _.isNull(earlier_msg_el) ? null : earlier_msg_el.getAttribute('data-isodate'), prev_msg_date = _.isNull(prev_msg_el) ? null : prev_msg_el.getAttribute('data-isodate'),
next_msg_date = next_msg_el.getAttribute('data-isodate'); next_msg_date = next_msg_el.getAttribute('data-isodate');
if (_.isNull(earlier_msg_date) && _.isNull(next_msg_date)) { if (_.isNull(prev_msg_date) && _.isNull(next_msg_date)) {
return; return;
} }
if (_.isNull(earlier_msg_date) || dayjs(next_msg_date).isAfter(earlier_msg_date, 'day')) { if (_.isNull(prev_msg_date) || dayjs(next_msg_date).isAfter(prev_msg_date, 'day')) {
const day_date = dayjs(next_msg_date).startOf('day'); const day_date = dayjs(next_msg_date).startOf('day');
next_msg_el.insertAdjacentHTML('afterEnd', next_msg_el.insertAdjacentHTML('beforeBegin',
tpl_new_day({ tpl_new_day({
'isodate': day_date.toISOString(), 'isodate': day_date.toISOString(),
'datestring': day_date.format("dddd MMM Do YYYY") 'datestring': day_date.format("dddd MMM Do YYYY")
@ -629,15 +635,14 @@ converse.plugins.add('converse-chatview', {
* @returns { Date } * @returns { Date }
*/ */
getLastMessageDate (cutoff) { getLastMessageDate (cutoff) {
const most_recent_msg = u.getFirstChildElement(this.content, '.message:not(.chat-state-notification)'); const first_msg = u.getFirstChildElement(this.content, '.message:not(.chat-state-notification)');
const most_recent_date = most_recent_msg ? most_recent_msg.getAttribute('data-isodate') : null; const oldest_date = first_msg ? first_msg.getAttribute('data-isodate') : null;
if (_.isNull(most_recent_date)) { if (!_.isNull(oldest_date) && dayjs(oldest_date).isAfter(cutoff)) {
return null; return null;
} }
const last_msg = u.getLastChildElement(this.content, '.message:not(.chat-state-notification)');
const oldest_message = u.getLastChildElement(this.content, '.message:not(.chat-state-notification)'); const most_recent_date = last_msg ? last_msg.getAttribute('data-isodate') : null;
const oldest_date = oldest_message ? oldest_message.getAttribute('data-isodate') : null; if (_.isNull(most_recent_date)) {
if (!_.isNull(oldest_date) && dayjs(oldest_date).isAfter(cutoff)) {
return null; return null;
} }
if (dayjs(most_recent_date).isBefore(cutoff)) { if (dayjs(most_recent_date).isBefore(cutoff)) {
@ -664,6 +669,29 @@ converse.plugins.add('converse-chatview', {
} }
}, },
setScrollPosition (message_el) {
/* Given a newly inserted message, determine whether we
* should keep the scrollbar in place (so as to not scroll
* up when using infinite scroll).
*/
if (this.model.get('scrolled')) {
const next_msg_el = u.getNextElement(message_el, ".chat-msg");
if (next_msg_el) {
// The currently received message is not new, there
// are newer messages after it. So let's see if we
// should maintain our current scroll position.
if (this.content.scrollTop === 0 || this.model.get('top_visible_message')) {
const top_visible_message = this.model.get('top_visible_message') || next_msg_el;
this.model.set('top_visible_message', top_visible_message);
this.content.scrollTop = top_visible_message.offsetTop - 30;
}
}
} else {
this.scrollDown();
}
},
showHelpMessages (msgs, type, spinner) { showHelpMessages (msgs, type, spinner) {
msgs.forEach(msg => { msgs.forEach(msg => {
this.content.insertAdjacentHTML( this.content.insertAdjacentHTML(
@ -680,6 +708,7 @@ converse.plugins.add('converse-chatview', {
} else if (spinner === false) { } else if (spinner === false) {
this.clearSpinner(); this.clearSpinner();
} }
return this.scrollDown();
}, },
shouldShowOnTextMessage () { shouldShowOnTextMessage () {
@ -697,24 +726,24 @@ converse.plugins.add('converse-chatview', {
if (view.model.get('type') === 'error') { if (view.model.get('type') === 'error') {
const previous_msg_el = this.content.querySelector(`[data-msgid="${view.model.get('msgid')}"]`); const previous_msg_el = this.content.querySelector(`[data-msgid="${view.model.get('msgid')}"]`);
if (previous_msg_el) { if (previous_msg_el) {
previous_msg_el.insertAdjacentElement('beforeBegin', view.el); previous_msg_el.insertAdjacentElement('afterend', view.el);
return this.trigger('messageInserted', view.el); return this.trigger('messageInserted', view.el);
} }
} }
const current_msg_date = dayjs(view.model.get('time')).toDate() || new Date(); const current_msg_date = dayjs(view.model.get('time')).toDate() || new Date(),
const previous_msg_date = this.getLastMessageDate(current_msg_date); previous_msg_date = this.getLastMessageDate(current_msg_date);
if (_.isNull(previous_msg_date)) { if (_.isNull(previous_msg_date)) {
this.content.insertAdjacentElement('beforeEnd', view.el); this.content.insertAdjacentElement('afterbegin', view.el);
} else { } else {
const previous_msg_el = sizzle(`[data-isodate="${previous_msg_date.toISOString()}"]:first`, this.content).pop(); const previous_msg_el = sizzle(`[data-isodate="${previous_msg_date.toISOString()}"]:last`, this.content).pop();
if (view.model.get('type') === 'error' && if (view.model.get('type') === 'error' &&
u.hasClass('chat-error', previous_msg_el) && u.hasClass('chat-error', previous_msg_el) &&
previous_msg_el.textContent === view.model.get('message')) { previous_msg_el.textContent === view.model.get('message')) {
// We don't show a duplicate error message // We don't show a duplicate error message
return; return;
} }
previous_msg_el.insertAdjacentElement('beforeBegin', view.el); previous_msg_el.insertAdjacentElement('afterend', view.el);
this.markFollowups(view.el); this.markFollowups(view.el);
} }
return this.trigger('messageInserted', view.el); return this.trigger('messageInserted', view.el);
@ -736,27 +765,26 @@ converse.plugins.add('converse-chatview', {
* @param { HTMLElement } el - The message element * @param { HTMLElement } el - The message element
*/ */
markFollowups (el) { markFollowups (el) {
const from = el.getAttribute('data-from'); const from = el.getAttribute('data-from'),
const earlier_msg_el = el.nextElementSibling; previous_el = el.previousElementSibling,
const date = dayjs(el.getAttribute('data-isodate')); date = dayjs(el.getAttribute('data-isodate')),
next_el = el.nextElementSibling;
if (earlier_msg_el && if (!u.hasClass('chat-msg--action', el) && !u.hasClass('chat-msg--action', previous_el) &&
!u.hasClass('chat-msg--action', el) && !u.hasClass('chat-msg--action', earlier_msg_el) && previous_el.getAttribute('data-from') === from &&
earlier_msg_el.getAttribute('data-from') === from && date.isBefore(dayjs(previous_el.getAttribute('data-isodate')).add(10, 'minutes')) &&
date.isBefore(dayjs(earlier_msg_el.getAttribute('data-isodate')).add(10, 'minutes')) && el.getAttribute('data-encrypted') === previous_el.getAttribute('data-encrypted')) {
el.getAttribute('data-encrypted') === earlier_msg_el.getAttribute('data-encrypted')) {
u.addClass('chat-msg--followup', el); u.addClass('chat-msg--followup', el);
} }
if (!next_el) { return; }
const later_msg_el = el.previousElementSibling;
if (!later_msg_el) { return; }
if (!u.hasClass('chat-msg--action', el) && if (!u.hasClass('chat-msg--action', el) &&
later_msg_el.getAttribute('data-from') === from && next_el.getAttribute('data-from') === from &&
dayjs(later_msg_el.getAttribute('data-isodate')).isBefore(date.add(10, 'minutes')) && dayjs(next_el.getAttribute('data-isodate')).isBefore(date.add(10, 'minutes')) &&
el.getAttribute('data-encrypted') === later_msg_el.getAttribute('data-encrypted')) { el.getAttribute('data-encrypted') === next_el.getAttribute('data-encrypted')) {
u.addClass('chat-msg--followup', later_msg_el); u.addClass('chat-msg--followup', next_el);
} else { } else {
u.removeClass('chat-msg--followup', later_msg_el); u.removeClass('chat-msg--followup', next_el);
} }
}, },
@ -781,6 +809,7 @@ converse.plugins.add('converse-chatview', {
this.insertMessage(view); this.insertMessage(view);
this.insertDayIndicator(view.el); this.insertDayIndicator(view.el);
this.setScrollPosition(view.el);
if (u.isNewMessage(message)) { if (u.isNewMessage(message)) {
if (message.get('sender') === 'me') { if (message.get('sender') === 'me') {
@ -788,8 +817,8 @@ converse.plugins.add('converse-chatview', {
// gets scrolled down. We always want to scroll down // gets scrolled down. We always want to scroll down
// when the user writes a message as opposed to when a // when the user writes a message as opposed to when a
// message is received. // message is received.
this.scrolled = false; this.model.set('scrolled', false);
} else if (this.scrolled && !u.isOnlyChatStateNotification(message)) { } else if (this.model.get('scrolled', true) && !u.isOnlyChatStateNotification(message)) {
this.showNewMessagesIndicator(); this.showNewMessagesIndicator();
} }
} }
@ -1230,6 +1259,7 @@ converse.plugins.add('converse-chatview', {
'message': text, 'message': text,
'isodate': (new Date()).toISOString(), 'isodate': (new Date()).toISOString(),
})); }));
this.scrollDown();
} }
} }
}, },
@ -1300,6 +1330,7 @@ converse.plugins.add('converse-chatview', {
if (_converse.auto_focus) { if (_converse.auto_focus) {
this.focus(); this.focus();
} }
this.focus();
}, },
_show () { _show () {
@ -1341,16 +1372,26 @@ converse.plugins.add('converse-chatview', {
scrolled = false; scrolled = false;
this.onScrolledDown(); this.onScrolledDown();
} }
this.scrolled = scrolled; u.safeSave(this.model, {
'scrolled': scrolled,
'top_visible_message': null
});
}, },
viewUnreadMessages () { viewUnreadMessages () {
this.scrolled = false; this.model.save({
'scrolled': false,
'top_visible_message': null
});
this.scrollDown(); this.scrollDown();
}, },
scrollDown () { _scrollDown () {
if (this.content && u.isVisible(this.content) && !this.scrolled) { /* Inner method that gets debounced */
if (_.isUndefined(this.content)) {
return;
}
if (u.isVisible(this.content) && !this.model.get('scrolled')) {
this.content.scrollTop = this.content.scrollHeight; this.content.scrollTop = this.content.scrollHeight;
} }
}, },

View File

@ -470,6 +470,7 @@ converse.plugins.add('converse-muc-views', {
this.initDebounced(); this.initDebounced();
this.model.messages.on('add', this.onMessageAdded, this); this.model.messages.on('add', this.onMessageAdded, this);
this.model.messages.on('rendered', this.scrollDown, this);
this.model.messages.on('reset', () => { this.model.messages.on('reset', () => {
this.content.innerHTML = ''; this.content.innerHTML = '';
this.removeAll(); this.removeAll();
@ -679,6 +680,7 @@ converse.plugins.add('converse-muc-views', {
this.model.clearUnreadMsgCounter(); this.model.clearUnreadMsgCounter();
this.model.save(); this.model.save();
} }
this.scrollDown();
this.renderEmojiPicker(); this.renderEmojiPicker();
}, },
@ -706,6 +708,7 @@ converse.plugins.add('converse-muc-views', {
} else if (conn_status === converse.ROOMSTATUS.ENTERED) { } else if (conn_status === converse.ROOMSTATUS.ENTERED) {
this.hideSpinner(); this.hideSpinner();
this.setChatState(_converse.ACTIVE); this.setChatState(_converse.ACTIVE);
this.scrollDown();
if (_converse.auto_focus) { if (_converse.auto_focus) {
this.focus(); this.focus();
} }
@ -763,6 +766,7 @@ converse.plugins.add('converse-muc-views', {
ev.stopPropagation(); ev.stopPropagation();
} }
this.model.save({'hidden_occupants': true}); this.model.save({'hidden_occupants': true});
this.scrollDown();
}, },
toggleOccupants (ev) { toggleOccupants (ev) {
@ -774,6 +778,7 @@ converse.plugins.add('converse-muc-views', {
ev.stopPropagation(); ev.stopPropagation();
} }
this.model.save({'hidden_occupants': !this.model.get('hidden_occupants')}); this.model.save({'hidden_occupants': !this.model.get('hidden_occupants')});
this.scrollDown();
}, },
onOccupantClicked (ev) { onOccupantClicked (ev) {
@ -1321,7 +1326,7 @@ converse.plugins.add('converse-muc-views', {
return; return;
} }
if (!dayjs(el.getAttribute('data-isodate')).isSame(new Date(), "day")) { if (!dayjs(el.getAttribute('data-isodate')).isSame(new Date(), "day")) {
el = el.nextElementSibling; el = el.previousElementSibling;
continue; continue;
} }
if (data.join === nick || if (data.join === nick ||
@ -1330,7 +1335,7 @@ converse.plugins.add('converse-muc-views', {
data.joinleave === nick) { data.joinleave === nick) {
return el; return el;
} }
el = el.nextElementSibling; el = el.previousElementSibling;
} }
}, },
@ -1341,7 +1346,7 @@ converse.plugins.add('converse-muc-views', {
} }
const nick = occupant.get('nick'), const nick = occupant.get('nick'),
stat = _converse.muc_show_join_leave_status ? occupant.get('status') : null, stat = _converse.muc_show_join_leave_status ? occupant.get('status') : null,
prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.firstElementChild, nick), prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick),
data = _.get(prev_info_el, 'dataset', {}); data = _.get(prev_info_el, 'dataset', {});
if (data.leave === nick) { if (data.leave === nick) {
@ -1359,8 +1364,8 @@ converse.plugins.add('converse-muc-views', {
'message': message 'message': message
}; };
this.content.removeChild(prev_info_el); this.content.removeChild(prev_info_el);
this.content.insertAdjacentHTML('afterBegin', tpl_info(data)); this.content.insertAdjacentHTML('beforeend', tpl_info(data));
const el = this.content.firstElementChild; const el = this.content.lastElementChild;
setTimeout(() => u.addClass('fade-out', el), 5000); setTimeout(() => u.addClass('fade-out', el), 5000);
setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5500); setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5500);
} else { } else {
@ -1379,12 +1384,13 @@ converse.plugins.add('converse-muc-views', {
}; };
if (prev_info_el) { if (prev_info_el) {
this.content.removeChild(prev_info_el); this.content.removeChild(prev_info_el);
this.content.insertAdjacentHTML('afterBegin', tpl_info(data)); this.content.insertAdjacentHTML('beforeend', tpl_info(data));
} else { } else {
this.content.insertAdjacentHTML('afterBegin', tpl_info(data)); this.content.insertAdjacentHTML('beforeend', tpl_info(data));
this.insertDayIndicator(this.content.firstElementChild); this.insertDayIndicator(this.content.lastElementChild);
} }
} }
this.scrollDown();
}, },
showLeaveNotification (occupant) { showLeaveNotification (occupant) {
@ -1395,7 +1401,7 @@ converse.plugins.add('converse-muc-views', {
} }
const nick = occupant.get('nick'), const nick = occupant.get('nick'),
stat = _converse.muc_show_join_leave_status ? occupant.get('status') : null, stat = _converse.muc_show_join_leave_status ? occupant.get('status') : null,
prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.firstElementChild, nick), prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick),
dataset = _.get(prev_info_el, 'dataset', {}); dataset = _.get(prev_info_el, 'dataset', {});
if (dataset.join === nick) { if (dataset.join === nick) {
@ -1413,8 +1419,8 @@ converse.plugins.add('converse-muc-views', {
'message': message 'message': message
}; };
this.content.removeChild(prev_info_el); this.content.removeChild(prev_info_el);
this.content.insertAdjacentHTML('afterBegin', tpl_info(data)); this.content.insertAdjacentHTML('beforeend', tpl_info(data));
const el = this.content.firstElementChild; const el = this.content.lastElementChild;
setTimeout(() => u.addClass('fade-out', el), 5000); setTimeout(() => u.addClass('fade-out', el), 5000);
setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5500); setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5500);
} else { } else {
@ -1433,12 +1439,13 @@ converse.plugins.add('converse-muc-views', {
} }
if (prev_info_el) { if (prev_info_el) {
this.content.removeChild(prev_info_el); this.content.removeChild(prev_info_el);
this.content.insertAdjacentHTML('afterBegin', tpl_info(data)); this.content.insertAdjacentHTML('beforeend', tpl_info(data));
} else { } else {
this.content.insertAdjacentHTML('afterBegin', tpl_info(data)); this.content.insertAdjacentHTML('beforeend', tpl_info(data));
this.insertDayIndicator(this.content.firstElementChild); this.insertDayIndicator(this.content.lastElementChild);
} }
} }
this.scrollDown();
}, },
renderAfterTransition () { renderAfterTransition () {
@ -1453,6 +1460,7 @@ converse.plugins.add('converse-muc-views', {
} else { } else {
u.showElement(this.el.querySelector('.chat-area')); u.showElement(this.el.querySelector('.chat-area'));
u.showElement(this.el.querySelector('.occupants')); u.showElement(this.el.querySelector('.occupants'));
this.scrollDown();
} }
}, },
@ -1502,6 +1510,7 @@ converse.plugins.add('converse-muc-views', {
'render_message': true 'render_message': true
})); }));
} }
this.scrollDown();
} }
}); });