Use flexbox to keep the chat scrolled down

By using `display: flex` and `flex-direction: column-reverse`, the chat
now automatically scrolls down when loaded, without requiring any
extra JavaScript.

We still need to scroll down with JavaScript when sending a message.

By using `column-reverse`, the messages container now works in reverse.
So the newest message is the first element in the container and the
oldest message is the last. This is the reverse of before.

Due to this, this change will likely break some plugins.
This commit is contained in:
JC Brand 2019-06-17 21:42:55 +02:00
parent 21b0f2464d
commit dd91d3cc55
12 changed files with 311 additions and 322 deletions

View File

@ -19,6 +19,7 @@
"window": true
},
"rules": {
"lodash/prefer-lodash-chain": "off",
"lodash/prefer-lodash-method": [2, {
"ignoreMethods": [
"assign", "every", "keys", "find", "endsWith", "startsWith", "filter",

View File

@ -56,6 +56,8 @@
- Removed events `statusChanged` and `statusMessageChanged`. Instead, you can
listen on the `change:status` or `change:status\_message` events on
`_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

33
package-lock.json generated
View File

@ -2408,11 +2408,19 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/backbone/-/backbone-1.4.0.tgz",
"integrity": "sha512-RLmDrRXkVdouTg38jcgHhyQ/2zjg7a8E6sz2zxfz21Hh17xDJYUHBZimVIt5fUyS8vbfpeSmTL3gUjTEvUV3qQ==",
"dev": true,
"requires": {
"underscore": ">=1.8.3"
}
},
"backbone.browserStorage": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/backbone.browserStorage/-/backbone.browserStorage-0.0.5.tgz",
"integrity": "sha512-Cf8B90EIWyHMm/ReS5yFmFMOXPVNda6QcTFcdyp1RW/1zM3LZF2Nf4U601/seIaEu/X8cRVEKqTINpPKql3sxA==",
"requires": {
"backbone": "~1.x.x",
"underscore": ">=1.4.0"
}
},
"backbone.nativeview": {
"version": "github:conversejs/Backbone.NativeView#5997c8197ca594e6b8469447f28310c78bd1d95e",
"from": "github:conversejs/Backbone.NativeView#5997c8197ca594e6b8469447f28310c78bd1d95e",
@ -7096,8 +7104,7 @@
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
"dev": true
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
},
"lodash-template-webpack-loader": {
"version": "github:jcbrand/lodash-template-webpack-loader#258c095ab22130dfde454fa59ee0986f302bb733",
@ -12004,6 +12011,14 @@
"find-up": "^2.1.0"
}
},
"pluggable.js": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/pluggable.js/-/pluggable.js-2.0.1.tgz",
"integrity": "sha512-SBt6v6Tbp20Jf8hU0cpcc/+HBHGMY8/Q+yA6Ih0tBQE8tfdZ6U4PRG0iNvUUjLx/hVyOP53n0UfGBymlfaaXCg==",
"requires": {
"lodash": "^4.17.11"
}
},
"po2json": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/po2json/-/po2json-0.4.5.tgz",
@ -13733,6 +13748,10 @@
"through": "^2.3.4"
}
},
"strophe.js": {
"version": "github:strophe/strophejs#31f31b52fd37a92eebee7b47d668a7d7dc40df3b",
"from": "github:strophe/strophejs#31f31b52fd37a92eebee7b47d668a7d7dc40df3b"
},
"style-loader": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz",
@ -14163,6 +14182,11 @@
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"dev": true
},
"twemoji": {
"version": "11.3.0",
"resolved": "https://registry.npmjs.org/twemoji/-/twemoji-11.3.0.tgz",
"integrity": "sha512-xN/vlR6+gDmfjt6LInAqwGAv3Agwrmzx5TD1jEFwKS19IOGDrX0/3OB8GP1wUYPVIdkaer5hw6qd+52jzvz0Lg=="
},
"type-check": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
@ -14250,8 +14274,7 @@
"underscore": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
"integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=",
"dev": true
"integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI="
},
"underscore-contrib": {
"version": "0.3.0",

View File

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

View File

@ -33,7 +33,6 @@
color: var(--separator-text-color);
display: inline-block;
line-height: 2em;
padding: 0 1em;
position: relative;
z-index: 5;
}
@ -44,7 +43,7 @@
font-size: var(--message-font-size);
line-height: var(--line-height-small);
font-size: 90%;
padding: 0.17rem 1rem;
padding: 0.17rem 0;
&.badge {
color: var(--chat-head-text-color);
@ -77,11 +76,10 @@
}
&.chat-msg {
display: inline-flex;
display: flex;
width: 100%;
flex-direction: row;
overflow: auto; // Ensures that content stays inside
padding: 0.125rem 1rem;
padding: 0.125rem 0;
&.onload {
animation: colorchange-chatmessage 1s;
@ -152,7 +150,6 @@
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
}
.chat-msg__message {
@ -269,7 +266,7 @@
}
&.chat-msg--action {
.chat-msg__content {
flex-wrap: nowrap;
flex-wrap: wrap;
flex-direction: row;
justify-content: flex-start;
}

View File

@ -44,7 +44,7 @@
}).c('body').t('hello world').tree();
await _converse.chatboxes.onMessage(msg);
await test_utils.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
expect(view.content.lastElementChild.textContent.trim().indexOf('hello world')).not.toBe(-1);
expect(view.content.firstElementChild.textContent.trim().indexOf('hello world')).not.toBe(-1);
done();
}));
@ -78,22 +78,22 @@
message = '/me is as well';
await test_utils.sendMessage(view, message);
expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(2);
await test_utils.waitUntil(() => sizzle('.chat-msg__author:last', view.el).pop().textContent.trim() === '**Romeo Montague');
const last_el = sizzle('.chat-msg__text:last', view.el).pop();
await test_utils.waitUntil(() => sizzle('.chat-msg__author:first', view.el).pop().textContent.trim() === '**Romeo Montague');
const last_el = sizzle('.chat-msg__text:first', view.el).pop();
expect(last_el.textContent).toBe('is as well');
expect(u.hasClass('chat-msg--followup', last_el)).toBe(false);
// Check that /me messages after a normal message don't
// get the 'chat-msg--followup' class.
message = 'This a normal message';
await test_utils.sendMessage(view, message);
let message_el = view.el.querySelector('.message:last-child');
let message_el = view.el.querySelector('.message:first-child');
expect(u.hasClass('chat-msg--followup', message_el)).toBeFalsy();
message = '/me wrote a 3rd person message';
await test_utils.sendMessage(view, message);
message_el = view.el.querySelector('.message:last-child');
message_el = view.el.querySelector('.message:first-child');
expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(3);
expect(sizzle('.chat-msg__text:last', view.el).pop().textContent).toBe('wrote a 3rd person message');
expect(u.isVisible(sizzle('.chat-msg__author:last', view.el).pop())).toBeTruthy();
expect(sizzle('.chat-msg__text:first', view.el).pop().textContent).toBe('wrote a 3rd person message');
expect(u.isVisible(sizzle('.chat-msg__author:first', view.el).pop())).toBeTruthy();
expect(u.hasClass('chat-msg--followup', message_el)).toBeFalsy();
done();
}));
@ -267,7 +267,7 @@
const jid = el.textContent.replace(/ /g,'.').toLowerCase() + '@montague.lit';
spyOn(_converse.api, "trigger");
el.click();
await test_utils.waitUntil(() => _converse.api.trigger.calls.count(), 500);
await test_utils.waitUntil(() => _converse.api.trigger.calls.count(), 1000);
expect(_converse.chatboxes.length).toEqual(2);
expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxFocused', jasmine.any(Object));
done();
@ -361,7 +361,6 @@
spyOn(_converse.api, "trigger");
// We need to rebind all events otherwise our spy won't be called
chatview.delegateEvents();
chatview.el.querySelector('.toggle-chatbox-button').click();
expect(chatview.minimize).toHaveBeenCalled();
@ -377,7 +376,6 @@
expect(trimmedview.restore).toHaveBeenCalled();
expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxMaximized', jasmine.any(Object));
await test_utils.waitUntil(() => u.isVisible(chatview.el.querySelector('.chat-body')), 500);
const toggle_el = sizzle('.toggle-chatbox-button', chatview.el).pop();
expect(u.hasClass('fa-minus', toggle_el)).toBeTruthy();
expect(u.hasClass('fa-plus', toggle_el)).toBeFalsy();

View File

@ -239,7 +239,7 @@
expect(view.model.messages.at(0).get('correcting')).toBeFalsy();
expect(view.model.messages.at(1).get('correcting')).toBeFalsy();
expect(view.model.messages.at(2).get('correcting')).toBe(true);
await test_utils.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg:last', view.el).pop()), 500);
await test_utils.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg:first', view.el).pop()), 500);
textarea.selectionEnd = 0; // Happens by pressing up,
// but for some reason not in tests, so we set it manually.
@ -264,11 +264,11 @@
expect(textarea.value).toBe('');
const messages = view.el.querySelectorAll('.chat-msg');
expect(messages.length).toBe(3);
expect(messages[0].querySelector('.chat-msg__text').textContent)
expect(messages[2].querySelector('.chat-msg__text').textContent)
.toBe('But soft, what light through yonder window breaks?');
expect(messages[1].querySelector('.chat-msg__text').textContent)
.toBe('It is the east, and Juliet is the sun.');
expect(messages[2].querySelector('.chat-msg__text').textContent)
expect(messages[0].querySelector('.chat-msg__text').textContent)
.toBe('Arise, fair sun, and kill the envious moon');
expect(view.model.messages.at(0).get('correcting')).toBeFalsy();
@ -413,53 +413,53 @@
view.clearSpinner(); //cleanup
expect(chat_content.querySelectorAll('.date-separator').length).toEqual(4);
let day = sizzle('.date-separator:first', chat_content).pop();
let day = sizzle('.date-separator:last', chat_content).pop();
expect(day.getAttribute('data-isodate')).toEqual(dayjs('2017-12-31T00:00:00').toISOString());
let time = sizzle('time:first', chat_content).pop();
let time = sizzle('time:last', chat_content).pop();
expect(time.textContent).toEqual('Sunday Dec 31st 2017')
day = sizzle('.date-separator:first', chat_content).pop();
expect(day.nextElementSibling.querySelector('.chat-msg__text').textContent).toBe('Older message');
day = sizzle('.date-separator:last', chat_content).pop();
expect(day.previousElementSibling.querySelector('.chat-msg__text').textContent).toBe('Older message');
let el = sizzle('.chat-msg:first', chat_content).pop().querySelector('.chat-msg__text')
let el = sizzle('.chat-msg:last', chat_content).pop().querySelector('.chat-msg__text')
expect(u.hasClass('chat-msg--followup', el)).toBe(false);
expect(el.textContent).toEqual('Older message');
time = sizzle('time.separator-text:eq(1)', chat_content).pop();
time = sizzle('time.separator-text:eq(2)', chat_content).pop();
expect(time.textContent).toEqual("Monday Jan 1st 2018");
day = sizzle('.date-separator:eq(1)', chat_content).pop();
day = sizzle('.date-separator:eq(2)', chat_content).pop();
expect(day.getAttribute('data-isodate')).toEqual(dayjs('2018-01-01T00:00:00').toISOString());
expect(day.nextElementSibling.querySelector('.chat-msg__text').textContent).toBe('Inbetween message');
expect(day.previousElementSibling.querySelector('.chat-msg__text').textContent).toBe('Inbetween message');
el = sizzle('.chat-msg:eq(1)', chat_content).pop();
el = sizzle('.chat-msg:eq(5)', chat_content).pop();
expect(el.querySelector('.chat-msg__text').textContent).toEqual('Inbetween message');
expect(el.nextElementSibling.querySelector('.chat-msg__text').textContent).toEqual('another inbetween message');
el = sizzle('.chat-msg:eq(2)', chat_content).pop();
expect(el.previousElementSibling.querySelector('.chat-msg__text').textContent).toEqual('another inbetween message');
el = sizzle('.chat-msg:eq(4)', chat_content).pop();
expect(el.querySelector('.chat-msg__text').textContent)
.toEqual('another inbetween message');
expect(u.hasClass('chat-msg--followup', el)).toBe(true);
time = sizzle('time.separator-text:nth(2)', chat_content).pop();
time = sizzle('time.separator-text:nth(1)', chat_content).pop();
expect(time.textContent).toEqual("Tuesday Jan 2nd 2018");
day = sizzle('.date-separator:nth(2)', chat_content).pop();
day = sizzle('.date-separator:nth(1)', chat_content).pop();
expect(day.getAttribute('data-isodate')).toEqual(dayjs('2018-01-02T00:00:00').toISOString());
expect(day.nextElementSibling.querySelector('.chat-msg__text').textContent).toBe('An earlier message on the next day');
expect(day.previousElementSibling.querySelector('.chat-msg__text').textContent).toBe('An earlier message on the next day');
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(u.hasClass('chat-msg--followup', el)).toBe(false);
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).toEqual('message');
expect(el.nextElementSibling.querySelector('.chat-msg__text').textContent).toEqual('newer message from the next day');
expect(el.previousElementSibling.querySelector('.chat-msg__text').textContent).toEqual('newer message from the next day');
expect(u.hasClass('chat-msg--followup', el)).toBe(false);
day = sizzle('.date-separator:last', chat_content).pop();
day = sizzle('.date-separator:first', chat_content).pop();
expect(day.getAttribute('data-isodate')).toEqual(dayjs().startOf('day').toISOString());
expect(day.nextElementSibling.querySelector('.chat-msg__text').textContent).toBe('latest message');
expect(day.previousElementSibling.querySelector('.chat-msg__text').textContent).toBe('latest message');
expect(u.hasClass('chat-msg--followup', el)).toBe(false);
done();
}));
@ -779,12 +779,12 @@
expect(chat_content.querySelectorAll('time.separator-text').length).toEqual(2); // There are now two time elements
const message_date = new Date();
day = sizzle('.date-separator:last', chat_content);
day = sizzle('.date-separator:first', chat_content);
expect(day.length).toEqual(1);
expect(day[0].getAttribute('class')).toEqual('message date-separator');
expect(day[0].getAttribute('data-isodate')).toEqual(dayjs(message_date).startOf('day').toISOString());
time = sizzle('time.separator-text:last', chat_content).pop();
time = sizzle('time.separator-text:first', chat_content).pop();
expect(time.textContent).toEqual(dayjs(message_date).startOf('day').format("dddd MMM Do YYYY"));
// Normal checks for the 2nd message
@ -794,12 +794,12 @@
expect(msg_obj.get('fullname')).toBeUndefined();
expect(msg_obj.get('sender')).toEqual('them');
expect(msg_obj.get('is_delayed')).toEqual(false);
const msg_txt = sizzle('.chat-msg:last .chat-msg__text', chat_content).pop().textContent;
const msg_txt = sizzle('.chat-msg:first .chat-msg__text', chat_content).pop().textContent;
expect(msg_txt).toEqual(message);
expect(chat_content.querySelector('.chat-msg:last-child .chat-msg__text').textContent).toEqual(message);
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:last-child .chat-msg__author').textContent.trim()).toBe('Juliet Capulet');
expect(chat_content.querySelector('.chat-msg:first-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:first-child .chat-msg__author').textContent.trim()).toBe('Juliet Capulet');
done();
}));
@ -890,21 +890,21 @@
message = 'http://www.opkode.com/"onmouseover="alert(1)"whatever';
await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
msg = sizzle('.chat-content .chat-msg:first .chat-msg__text', view.el).pop();
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>');
message = "https://en.wikipedia.org/wiki/Ender's_Game";
await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
msg = sizzle('.chat-content .chat-msg:first .chat-msg__text', view.el).pop();
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>');
message = "<https://bugs.documentfoundation.org/show_bug.cgi?id=123737>";
await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
msg = sizzle('.chat-content .chat-msg:first .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
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;`);
@ -912,7 +912,7 @@
message = '<http://www.opkode.com/"onmouseover="alert(1)"whatever>';
await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
msg = sizzle('.chat-content .chat-msg:first .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
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;');
@ -920,7 +920,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`
await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
msg = sizzle('.chat-content .chat-msg:first .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
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>`);
@ -977,7 +977,7 @@
</message>`);
_converse.connection._dataRecv(test_utils.createRequest(stanza));
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
expect(chat_content.querySelector('.message:last-child .chat-msg__text').innerHTML).toBe('Hey<br><br>Have you heard the news?');
expect(chat_content.querySelector('.message:first-child .chat-msg__text').innerHTML).toBe('Hey<br><br>Have you heard the news?');
stanza = u.toStanza(`
<message from="${contact_jid}"
type="chat"
@ -986,7 +986,7 @@
</message>`);
_converse.connection._dataRecv(test_utils.createRequest(stanza));
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
expect(chat_content.querySelector('.message:last-child .chat-msg__text').innerHTML).toBe('Hey<br>Have you heard<br>the news?');
expect(chat_content.querySelector('.message:first-child .chat-msg__text').innerHTML).toBe('Hey<br>Have you heard<br>the news?');
done();
}));
@ -1005,7 +1005,7 @@
test_utils.sendMessage(view, message);
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length, 1000)
expect(view.model.sendMessage).toHaveBeenCalled();
let msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop();
let msg = sizzle('.chat-content .chat-msg:first .chat-msg__text').pop();
expect(msg.innerHTML.trim()).toEqual(
'<!-- src/templates/image.html -->\n'+
'<a href="'+base_url+'/logo/conversejs-filled.svg" target="_blank" rel="noopener"><img class="chat-image img-thumbnail"'+
@ -1014,7 +1014,7 @@
test_utils.sendMessage(view, message);
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length === 2, 1000);
expect(view.model.sendMessage).toHaveBeenCalled();
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop();
msg = sizzle('.chat-content .chat-msg:first .chat-msg__text').pop();
expect(msg.innerHTML.trim()).toEqual(
'<!-- src/templates/image.html -->\n'+
'<a href="'+base_url+'/logo/conversejs-filled.svg?param1=val1&amp;param2=val2" target="_blank" rel="noopener"><img'+
@ -1025,7 +1025,7 @@
test_utils.sendMessage(view, message);
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length === 4, 1000);
expect(view.model.sendMessage).toHaveBeenCalled();
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop();
msg = sizzle('.chat-content .chat-msg:first .chat-msg__text').pop();
expect(msg.textContent.trim()).toEqual('hello world');
expect(msg.querySelectorAll('img').length).toEqual(2);
@ -1055,10 +1055,10 @@
expect(chatbox.messages.models.length, 1);
const msg_object = chatbox.messages.models[0];
const msg_author = view.el.querySelector('.chat-content .chat-msg:last-child .chat-msg__author');
const msg_author = view.el.querySelector('.chat-content .chat-msg:first-child .chat-msg__author');
expect(msg_author.textContent.trim()).toBe('Romeo Montague');
const msg_time = view.el.querySelector('.chat-content .chat-msg:last-child .chat-msg__time');
const msg_time = view.el.querySelector('.chat-content .chat-msg:first-child .chat-msg__time');
const time = dayjs(msg_object.get('time')).format(_converse.time_format);
expect(msg_time.textContent).toBe(time);
done();
@ -1144,19 +1144,19 @@
expect(chat_content.querySelectorAll('.message').length).toBe(6);
expect(chat_content.querySelectorAll('.chat-msg').length).toBe(5);
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(false);
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(3)'))).toBe(true);
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(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 message");
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(
"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(
"Another message 14 minutes since we started");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(5) .chat-msg__text').textContent).toBe(
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(2) .chat-msg__text').textContent).toBe(
"Another message 1 minute and 1 second since the previous one");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(6) .chat-msg__text').textContent).toBe(
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(1)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(1) .chat-msg__text').textContent).toBe(
"Another message within 10 minutes, but from a different person");
// Let's add a delayed, inbetween message
@ -1176,21 +1176,21 @@
expect(chat_content.querySelectorAll('.message').length).toBe(7);
expect(chat_content.querySelectorAll('.chat-msg').length).toBe(6);
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(false);
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(3)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(3) .chat-msg__text').textContent).toBe(
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(6) .chat-msg__text').textContent).toBe("A message");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(5) .chat-msg__text').textContent).toBe(
"Another message 3 minutes later");
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(
"A delayed message, sent 5 minutes since we started");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(5) .chat-msg__text').textContent).toBe(
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(
"Another message 14 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(
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(2) .chat-msg__text').textContent).toBe(
"Another message 1 minute and 1 second since the previous one");
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(1)'))).toBe(false);
_converse.chatboxes.onMessage($msg({'id': 'aeb213', 'to': _converse.bare_jid})
.c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
@ -1206,25 +1206,25 @@
expect(chat_content.querySelectorAll('.message').length).toBe(8);
expect(chat_content.querySelectorAll('.chat-msg').length).toBe(7);
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(false);
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(3)'))).toBe(true);
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(7)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(7) .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(chat_content.querySelector('.message:nth-child(3) .chat-msg__text').textContent).toBe(
"Another message 14 minutes since we started");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(7)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(7) .chat-msg__text').textContent).toBe(
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(2) .chat-msg__text').textContent).toBe(
"Another message 1 minute and 1 second since the previous one");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(8)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(8) .chat-msg__text').textContent).toBe(
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(1)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(1) .chat-msg__text').textContent).toBe(
"Another message within 10 minutes, but from a different person");
jasmine.clock().uninstall();
@ -1662,7 +1662,7 @@
});
view.model.sendMessage(msg_text);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
msg_txt = sizzle('.chat-msg:last .chat-msg__text', chat_content).pop().textContent;
msg_txt = sizzle('.chat-msg:first .chat-msg__text', chat_content).pop().textContent;
expect(msg_txt).toEqual(msg_text);
/* <message xmlns="jabber:client"
@ -1730,7 +1730,7 @@
});
view.model.sendMessage(msg_text);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
msg_txt = sizzle('.chat-msg:last .chat-msg__text', chat_content).pop().textContent;
msg_txt = sizzle('.chat-msg:first .chat-msg__text', chat_content).pop().textContent;
expect(msg_txt).toEqual(msg_text);
// A different error message will however render
@ -1810,14 +1810,20 @@
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
promises.push(new Promise((resolve, reject) => view.once('messageInserted', resolve)));
}
await Promise.all(promises);
// XXX Fails on Travis
// await test_utils.waitUntil(() => view.content.scrollTop, 1000)
await test_utils.waitUntil(() => !view.model.get('auto_scrolled'), 500);
view.content.scrollTop = 0;
// XXX Fails on Travis
// await test_utils.waitUntil(() => view.model.get('scrolled'), 900);
view.model.set('scrolled', true);
// await test_utils.waitUntil(() => view.scrolled, 900);
view.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';
_converse.chatboxes.onMessage($msg({
@ -1831,10 +1837,10 @@
await test_utils.waitUntil(() => view.model.messages.length > 20, 1000);
// Now check that the message appears inside the chatbox in the DOM
const chat_content = view.el.querySelector('.chat-content');
const msg_txt = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop().textContent;
const msg_txt = sizzle('.chat-content .chat-msg:first .chat-msg__text', view.el).pop().textContent;
expect(msg_txt).toEqual(message);
await test_utils.waitUntil(() => u.isVisible(view.el.querySelector('.new-msgs-indicator')), 900);
expect(view.model.get('scrolled')).toBe(true);
expect(view.scrolled).toBe(true);
expect(view.content.scrollTop).toBe(0);
expect(u.isVisible(view.el.querySelector('.new-msgs-indicator'))).toBeTruthy();
// Scroll down again
@ -1935,9 +1941,9 @@
</message>`);
_converse.connection._dataRecv(test_utils.createRequest(stanza));
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
msg = view.el.querySelector('.chat-msg:last-child .chat-msg__text');
msg = view.el.querySelector('.chat-msg:first-child .chat-msg__text');
expect(msg.innerHTML).toEqual('<!-- message gets added here via renderMessage -->'); // Emtpy
media = view.el.querySelector('.chat-msg:last-child .chat-msg__media');
media = view.el.querySelector('.chat-msg:first-child .chat-msg__media');
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
'<!-- src/templates/audio.html -->'+
'<audio controls="" src="https://montague.lit/audio.mp3"></audio>'+
@ -1985,9 +1991,9 @@
</message>`);
_converse.connection._dataRecv(test_utils.createRequest(stanza));
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
msg = view.el.querySelector('.chat-msg:last-child .chat-msg__text');
msg = view.el.querySelector('.chat-msg:first-child .chat-msg__text');
expect(msg.innerHTML).toEqual('<!-- message gets added here via renderMessage -->'); // Emtpy
media = view.el.querySelector('.chat-msg:last-child .chat-msg__media');
media = view.el.querySelector('.chat-msg:first-child .chat-msg__media');
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual(
'<!-- src/templates/video.html -->'+
'<video controls="" src="https://montague.lit/video.mp4" style="max-height: 50vh"></video>'+
@ -2328,7 +2334,7 @@
expect(view.model.messages.last().get('affiliation')).toBe('member');
expect(view.model.messages.last().get('role')).toBe('participant');
expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
expect(sizzle('.chat-msg__author', view.el).pop().classList.value.trim()).toBe('chat-msg__author participant');
expect(sizzle('.chat-msg__author:first', view.el).pop().classList.value.trim()).toBe('chat-msg__author participant');
presence = $pres({
to:'romeo@montague.lit/orchard',
@ -2534,7 +2540,7 @@
expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
expect(view.model.messages.at(0).get('correcting')).toBe(true);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
await test_utils.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
await test_utils.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg.groupchat:last', view.el).pop()), 500);
expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
view.onKeyDown({
target: textarea,

View File

@ -375,8 +375,8 @@
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);
expect(info_texts[0]).toBe('A new groupchat has been created');
expect(info_texts[1]).toBe('nicky has entered the groupchat');
expect(info_texts[1]).toBe('A new groupchat has been created');
expect(info_texts[0]).toBe('nicky has entered the groupchat');
// An instant room is created by saving the default configuratoin.
//
@ -482,9 +482,9 @@
_converse.connection._dataRecv(test_utils.createRequest(presence));
await test_utils.waitUntil(() => chat_content.querySelectorAll('.chat-info').length === 2);
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("This groupchat is not anonymous");
expect(sizzle('div.chat-info:first', chat_content).pop().textContent)
.toBe("some1 has entered the groupchat");
done();
}));
@ -495,7 +495,7 @@
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
await test_utils.openChatRoom(_converse, "coven", 'chat.shakespeare.lit', 'some1');
await test_utils.openAndEnterChatRoom(_converse, "coven", 'chat.shakespeare.lit', 'some1');
const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
const chat_content = view.el.querySelector('.chat-content');
/* We don't show join/leave messages for existing occupants. We
@ -512,7 +512,10 @@
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(0);
const info_msgs = sizzle('.chat-info', chat_content);
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"
* from="coven@chat.shakespeare.lit/some1">
@ -533,7 +536,7 @@
}).up()
.c('status', {code: '110'});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info:first', chat_content).pop().textContent)
expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
.toBe("some1 has entered the groupchat");
presence = $pres({
@ -547,8 +550,8 @@
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(3);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent)
.toBe("newguy has entered the groupchat");
const msg = $msg({
@ -573,8 +576,8 @@
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(3);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(4);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent)
.toBe("newgirl has entered the groupchat");
// Don't show duplicate join messages
@ -588,7 +591,7 @@
'role': 'participant'
});
_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(4);
/* <presence
* from='coven@chat.shakespeare.lit/thirdwitch'
@ -615,8 +618,8 @@
'role': 'none'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(4);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(5);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe(
'newguy has left the groupchat. '+
'"Disconnected: Replaced by new connection"');
@ -632,8 +635,9 @@
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(4);
let msg_el = sizzle('div.chat-info:last', chat_content).pop();
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(5);
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.getAttribute('data-leavejoin')).toBe('newguy');
@ -649,8 +653,8 @@
'role': 'none'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(4);
msg_el = sizzle('div.chat-info', chat_content).pop();
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(5);
msg_el = sizzle('.chat-info:first', chat_content).pop();
expect(msg_el.textContent).toBe('newguy has left the groupchat');
expect(msg_el.getAttribute('data-leave')).toBe('newguy');
@ -665,8 +669,8 @@
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(5);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
expect(chat_content.querySelectorAll('.chat-info').length).toBe(6);
expect(sizzle('.chat-info:first', chat_content).pop().textContent)
.toBe("nomorenicks has entered the groupchat");
presence = $pres({
@ -680,8 +684,8 @@
'role': 'none'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(5);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(6);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent)
.toBe("nomorenicks has entered and left the groupchat");
presence = $pres({
@ -695,8 +699,8 @@
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(5);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(6);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent)
.toBe("nomorenicks has entered the groupchat");
// Test a member joining and leaving
@ -710,7 +714,7 @@
'role': 'participant'
});
_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(7);
/* <presence
* from='coven@chat.shakespeare.lit/thirdwitch'
@ -737,8 +741,8 @@
'role': 'none'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(6);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(7);
expect(sizzle('.chat-info:first', chat_content).pop().textContent).toBe(
'insider has entered and left the groupchat. '+
'"Disconnected: Replaced by new connection"');
@ -759,8 +763,8 @@
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(6);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("newgirl has entered and left the groupchat");
expect(chat_content.querySelectorAll('.chat-info').length).toBe(7);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("newgirl has entered and left the groupchat");
expect(view.model.occupants.length).toBe(4);
done();
}));
@ -786,7 +790,7 @@
</presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(2);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("fabio has entered the groupchat");
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("fabio has entered the groupchat");
presence = u.toStanza(
`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/Dele Olajide">
@ -796,7 +800,7 @@
</presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(3);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("Dele Olajide has entered the groupchat");
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("Dele Olajide has entered the groupchat");
presence = u.toStanza(
`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/jcbrand">
@ -807,7 +811,7 @@
</presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(4);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("jcbrand has entered the groupchat");
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("jcbrand has entered the groupchat");
presence = u.toStanza(
`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" type="unavailable" from="coven@chat.shakespeare.lit/Dele Olajide">
@ -817,7 +821,7 @@
</presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(4);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("Dele Olajide has entered and left the groupchat");
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("Dele Olajide has entered and left the groupchat");
presence = u.toStanza(
`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/Dele Olajide">
@ -827,7 +831,7 @@
</presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(4);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("Dele Olajide has entered the groupchat");
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("Dele Olajide has entered the groupchat");
presence = u.toStanza(
`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/fuvuv" xml:lang="en">
@ -839,7 +843,7 @@
</presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(5);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("fuvuv has entered the groupchat");
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("fuvuv has entered the groupchat");
presence = u.toStanza(
`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" type="unavailable" from="coven@chat.shakespeare.lit/fuvuv">
@ -849,7 +853,7 @@
</presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(5);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("fuvuv has entered and left the groupchat");
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("fuvuv has entered and left the groupchat");
presence = u.toStanza(
`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" type="unavailable" from="coven@chat.shakespeare.lit/fabio">
@ -860,7 +864,7 @@
</presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(5);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe(
`fabio has entered and left the groupchat. "Disconnected: Replaced by new connection"`);
presence = u.toStanza(
@ -873,7 +877,7 @@
</presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(5);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe(
`fabio has entered the groupchat. "Ready for a new day"`);
// XXX: hack so that we can test leave/enter of occupants
@ -900,7 +904,7 @@
</presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(2);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe(
`Dele Olajide has left the groupchat`);
presence = u.toStanza(
@ -912,7 +916,7 @@
</presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(2);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe(
`fabio has left and re-entered the groupchat`);
done();
}));
@ -938,7 +942,7 @@
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2);
expect(sizzle('div.chat-info', chat_content).pop().textContent).toBe('newguy has entered the groupchat');
expect(sizzle('.chat-info:first', chat_content).pop().textContent).toBe('newguy has entered the groupchat');
presence = $pres({
to: 'romeo@montague.lit/orchard',
@ -954,7 +958,7 @@
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2);
expect(sizzle('div.chat-info', chat_content).pop().textContent).toBe('newguy has entered and left the groupchat');
expect(sizzle('.chat-info:first', chat_content).pop().textContent).toBe('newguy has entered and left the groupchat');
presence = u.toStanza(
`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/fabio">
@ -966,7 +970,7 @@
</presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(`fabio has entered the groupchat`);
expect(sizzle('.chat-info:first', chat_content).pop().textContent).toBe(`fabio has entered the groupchat`);
presence = u.toStanza(
`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/Dele Olajide">
@ -975,8 +979,8 @@
</x>
</presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(4);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("Dele Olajide has entered the groupchat");
expect(sizzle('.chat-info', chat_content).length).toBe(4);
expect(sizzle('.chat-info:first', chat_content).pop().textContent).toBe("Dele Olajide has entered the groupchat");
await test_utils.sendMessage(view, 'hello world');
presence = u.toStanza(
@ -987,8 +991,8 @@
</x>
</presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(5);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(`Dele Olajide has left the groupchat`);
expect(sizzle('.chat-info', chat_content).length).toBe(5);
expect(sizzle('.chat-info:first', chat_content).pop().textContent).toBe(`Dele Olajide has left the groupchat`);
done();
}));
@ -1045,11 +1049,11 @@
_converse.connection._dataRecv(test_utils.createRequest(stanza));
const chat_content = view.el.querySelector('.chat-content');
const messages = chat_content.querySelectorAll('div.chat-info');
const messages = chat_content.querySelectorAll('.chat-info');
expect(messages.length).toBe(3);
expect(messages[0].textContent).toBe('romeo has entered the groupchat');
expect(messages[2].textContent).toBe('romeo has entered the groupchat');
expect(messages[1].textContent).toBe('Guus has entered the groupchat');
expect(messages[2].textContent).toBe('Guus has left and re-entered the groupchat');
expect(messages[0].textContent).toBe('Guus has left and re-entered the groupchat');
done();
}));
@ -1102,8 +1106,8 @@
expect(indicator.getAttribute('data-isodate')).toEqual(dayjs().startOf('day').toISOString());
expect(indicator.querySelector('time').getAttribute('class')).toEqual('separator-text');
expect(indicator.querySelector('time').textContent).toEqual(dayjs().startOf('day').format("dddd MMM Do YYYY"));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2);
expect(chat_content.querySelector('div.chat-info:last-child').textContent).toBe(
expect(chat_content.querySelectorAll('.chat-info').length).toBe(2);
expect(chat_content.querySelector('.chat-info:first-child').textContent).toBe(
"some1 has entered the groupchat"
);
@ -1131,8 +1135,8 @@
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(chat_content.querySelectorAll('div.chat-info').length).toBe(3);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(
expect(chat_content.querySelectorAll('.chat-info').length).toBe(3);
expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe(
'some1 has left the groupchat. '+
'"Disconnected: Replaced by new connection"');
@ -1163,12 +1167,12 @@
let time = chat_content.querySelectorAll('time.separator-text');
expect(time.length).toEqual(4);
indicator = sizzle('.date-separator:eq(3)', chat_content).pop();
indicator = sizzle('.date-separator:eq(0)', chat_content).pop();
expect(indicator.getAttribute('class')).toEqual('message date-separator');
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(chat_content.querySelectorAll('div.chat-info').length).toBe(4);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
expect(chat_content.querySelectorAll('.chat-info').length).toBe(4);
expect(sizzle('.chat-info:first', chat_content).pop().textContent)
.toBe("newguy has entered the groupchat");
jasmine.clock().tick(ONE_DAY_LATER);
@ -1203,12 +1207,12 @@
time = chat_content.querySelectorAll('time.separator-text');
expect(time.length).toEqual(6);
indicator = sizzle('.date-separator:eq(5)', chat_content).pop();
indicator = sizzle('.date-separator:eq(0)', chat_content).pop();
expect(indicator.getAttribute('class')).toEqual('message date-separator');
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(chat_content.querySelectorAll('div.chat-info').length).toBe(5);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(
expect(chat_content.querySelectorAll('.chat-info').length).toBe(5);
expect(sizzle('.chat-info:first', chat_content).pop().textContent).toBe(
'newguy has left the groupchat. '+
'"Disconnected: Replaced by new connection"');
jasmine.clock().uninstall();
@ -1251,8 +1255,8 @@
}).c('body').t(message).tree();
await view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
expect(_.includes(sizzle('.chat-msg__author:last', view.el).pop().textContent, '**Romeo Montague')).toBeTruthy();
expect(sizzle('.chat-msg__text:last', view.el).pop().textContent).toBe('is as well');
expect(_.includes(sizzle('.chat-msg__author:first', view.el).pop().textContent, '**Romeo Montague')).toBeTruthy();
expect(sizzle('.chat-msg__text:first', view.el).pop().textContent).toBe('is as well');
done();
}));
@ -1799,7 +1803,7 @@
_converse.connection._dataRecv(test_utils.createRequest(presence));
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2);
const info_text = sizzle('.chat-content .chat-info:first', view.el).pop().textContent;
const info_text = sizzle('.chat-content .chat-info:last', view.el).pop().textContent;
expect(info_text).toBe('Your nickname has been automatically set to thirdwitch');
done();
}));
@ -2017,7 +2021,7 @@
// Now check that the message appears inside the chatbox in the DOM
const chat_content = view.el.querySelector('.chat-content');
const msg_txt = sizzle('.chat-msg:last .chat-msg__text', chat_content).pop().textContent;
const msg_txt = sizzle('.chat-msg:first .chat-msg__text', chat_content).pop().textContent;
expect(msg_txt).toEqual(message);
expect(view.content.scrollTop).toBe(0);
done();
@ -2155,8 +2159,8 @@
_converse.connection._dataRecv(test_utils.createRequest(stanza));
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2);
const info_messages = view.el.querySelectorAll('.chat-content .chat-info');
expect(info_messages[0].textContent).toBe('romeo has entered the groupchat');
expect(info_messages[1].textContent).toBe('groupchat logging is now enabled');
expect(info_messages[1].textContent).toBe('romeo has entered the groupchat');
expect(info_messages[0].textContent).toBe('groupchat logging is now enabled');
done();
}));
@ -2234,7 +2238,7 @@
_converse.connection._dataRecv(test_utils.createRequest(presence));
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 2);
expect(sizzle('div.chat-info:last').pop().textContent).toBe(
expect(sizzle('.chat-info:first').pop().textContent).toBe(
__(_converse.muc.new_nickname_messages["303"], "newnick")
);
expect(view.model.get('connection_status')).toBe(converse.ROOMSTATUS.ENTERED);
@ -2577,7 +2581,7 @@
_converse.connection._dataRecv(test_utils.createRequest(message));
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 3);
const chat_body = view.el.querySelector('.chatroom-body');
expect(sizzle('.message:last', chat_body).pop().textContent)
expect(sizzle('.message:first', chat_body).pop().textContent)
.toBe('This groupchat is now no longer anonymous');
done();
}));
@ -3302,7 +3306,7 @@
_converse.connection._dataRecv(test_utils.createRequest(presence));
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 4);
expect(view.el.querySelectorAll('.chat-info')[3].textContent).toBe("annoying guy has been kicked out");
expect(view.el.querySelectorAll('.chat-info')[0].textContent).toBe("annoying guy has been kicked out");
expect(view.el.querySelectorAll('.chat-info').length).toBe(4);
done();
}));
@ -3348,10 +3352,10 @@
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
var info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0);
expect(info_msgs.pop().textContent).toBe("trustworthyguy has entered the groupchat");
const info_msg = view.el.querySelector('.chat-info:first-child');
expect(info_msg.textContent).toBe("trustworthyguy has entered the groupchat");
var textarea = view.el.querySelector('.chat-textarea')
const textarea = view.el.querySelector('.chat-textarea')
textarea.value = '/op';
view.onKeyDown({
target: textarea,
@ -3404,7 +3408,7 @@
'role': 'moderator'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0);
let info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0);
expect(info_msgs.pop().textContent).toBe("trustworthyguy is now a moderator");
// Call now with the correct amount of arguments.
// XXX: Calling onFormSubmitted directly, trying
@ -3478,7 +3482,7 @@
* </x>
* </presence>
*/
var presence = $pres({
let presence = $pres({
'from': 'lounge@montague.lit/annoyingGuy',
'id':'27C55F89-1C6A-459A-9EB5-77690145D624',
'to': 'romeo@montague.lit/desktop'
@ -3490,8 +3494,8 @@
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
var info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0);
expect(info_msgs.pop().textContent).toBe("annoyingGuy has entered the groupchat");
const info_msg = sizzle('.chat-info:first', view.el).pop();
expect(info_msg.textContent).toBe("annoyingGuy has entered the groupchat");
const textarea = view.el.querySelector('.chat-textarea')
textarea.value = '/mute';
@ -3545,7 +3549,7 @@
'role': 'visitor'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0);
let info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0);
expect(info_msgs.pop().textContent).toBe("annoyingGuy has been muted");
// Call now with the correct of arguments.
@ -4683,7 +4687,7 @@
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
expect(sizzle('.chat-info:first', chat_content).pop().textContent)
.toBe("newguy has entered the groupchat");
presence = $pres({
@ -4698,7 +4702,7 @@
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(3);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
expect(sizzle('.chat-info:first', chat_content).pop().textContent)
.toBe("nomorenicks has entered the groupchat");
// See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
@ -4717,9 +4721,9 @@
// Check that the notification appears inside the chatbox in the DOM
let events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[2].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat');
let notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(1);
@ -4739,9 +4743,9 @@
events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[2].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat');
notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(1);
@ -4758,15 +4762,15 @@
await view.model.onMessage(msg);
events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[2].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat');
await test_utils.waitUntil(() => (view.el.querySelectorAll('.chat-state-notification').length === 2));
notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(2);
expect(notifications[0].textContent).toEqual('nomorenicks is typing');
expect(notifications[1].textContent).toEqual('newguy is typing');
expect(notifications[1].textContent).toEqual('nomorenicks is typing');
expect(notifications[0].textContent).toEqual('newguy is typing');
// Check that new messages appear under the chat state notifications
msg = $msg({
@ -4787,9 +4791,9 @@
timeout_functions[0]();
events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[2].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat');
notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(1);
@ -4798,9 +4802,9 @@
timeout_functions[1]();
events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[2].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat');
notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(0);
@ -4853,7 +4857,7 @@
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
expect(sizzle('.chat-info:first', chat_content).pop().textContent)
.toBe("newguy has entered the groupchat");
presence = $pres({
@ -4868,7 +4872,7 @@
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(3);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
expect(sizzle('.chat-info:first', chat_content).pop().textContent)
.toBe("nomorenicks has entered the groupchat");
// See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
@ -4885,9 +4889,9 @@
// Check that the notification appears inside the chatbox in the DOM
var events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[2].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat');
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-state-notification').length);
let notifications = view.el.querySelectorAll('.chat-state-notification');
@ -4905,9 +4909,9 @@
events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[2].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat');
notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(1);
@ -4923,9 +4927,9 @@
await view.model.onMessage(msg);
events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[2].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat');
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-state-notification').length === 2);
notifications = view.el.querySelectorAll('.chat-state-notification');
@ -4946,9 +4950,9 @@
await view.model.onMessage(msg);
events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[2].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat');
await test_utils.waitUntil(() => {
return _.map(

View File

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

View File

@ -344,7 +344,6 @@ converse.plugins.add('converse-chatview', {
initialize () {
this.initDebounced();
this.model.messages.on('add', this.onMessageAdded, this);
this.model.messages.on('rendered', this.scrollDown, this);
this.model.messages.on('reset', () => {
this.content.innerHTML = '';
this.removeAll();
@ -366,9 +365,8 @@ converse.plugins.add('converse-chatview', {
},
initDebounced () {
this.scrollDown = _.debounce(this._scrollDown, 100);
this.markScrolled = _.debounce(this._markScrolled, 100);
this.show = _.debounce(this._show, 250, {'leading': true});
this.show = _.debounce(this._show, 500, {'leading': true});
},
render () {
@ -545,8 +543,7 @@ converse.plugins.add('converse-chatview', {
await this.model.messages.fetched;
await Promise.all(this.model.messages.map(m => this.onMessageAdded(m)));
this.insertIntoDOM();
this.scrollDown();
this.content.addEventListener('scroll', this.markScrolled.bind(this));
this.content.addEventListener('scroll', () => this.markScrolled());
},
insertIntoDOM () {
@ -567,8 +564,7 @@ converse.plugins.add('converse-chatview', {
'message': message,
'isodate': isodate,
}));
this.insertDayIndicator(this.content.lastElementChild);
this.scrollDown();
this.insertDayIndicator(this.content.firstElementChild);
return isodate;
},
@ -577,14 +573,12 @@ converse.plugins.add('converse-chatview', {
'beforeend',
tpl_error_message({'message': message, 'isodate': (new Date()).toISOString() })
);
this.scrollDown();
},
addSpinner (append=false) {
if (_.isNull(this.el.querySelector('.spinner'))) {
if (append) {
this.content.insertAdjacentHTML('beforeend', tpl_spinner());
this.scrollDown();
} else {
this.content.insertAdjacentHTML('afterbegin', tpl_spinner());
}
@ -607,16 +601,16 @@ converse.plugins.add('converse-chatview', {
* which specifies its creation date.
*/
insertDayIndicator (next_msg_el) {
const prev_msg_el = u.getPreviousElement(next_msg_el, ".message:not(.chat-state-notification)"),
prev_msg_date = _.isNull(prev_msg_el) ? null : prev_msg_el.getAttribute('data-isodate'),
const earlier_msg_el = u.getNextElement(next_msg_el, ".message:not(.chat-state-notification)"),
earlier_msg_date = _.isNull(earlier_msg_el) ? null : earlier_msg_el.getAttribute('data-isodate'),
next_msg_date = next_msg_el.getAttribute('data-isodate');
if (_.isNull(prev_msg_date) && _.isNull(next_msg_date)) {
if (_.isNull(earlier_msg_date) && _.isNull(next_msg_date)) {
return;
}
if (_.isNull(prev_msg_date) || dayjs(next_msg_date).isAfter(prev_msg_date, 'day')) {
if (_.isNull(earlier_msg_date) || dayjs(next_msg_date).isAfter(earlier_msg_date, 'day')) {
const day_date = dayjs(next_msg_date).startOf('day');
next_msg_el.insertAdjacentHTML('beforeBegin',
next_msg_el.insertAdjacentHTML('afterEnd',
tpl_new_day({
'isodate': day_date.toISOString(),
'datestring': day_date.format("dddd MMM Do YYYY")
@ -634,14 +628,15 @@ converse.plugins.add('converse-chatview', {
* @returns { Date }
*/
getLastMessageDate (cutoff) {
const first_msg = u.getFirstChildElement(this.content, '.message:not(.chat-state-notification)');
const oldest_date = first_msg ? first_msg.getAttribute('data-isodate') : null;
if (!_.isNull(oldest_date) && dayjs(oldest_date).isAfter(cutoff)) {
const most_recent_msg = u.getFirstChildElement(this.content, '.message:not(.chat-state-notification)');
const most_recent_date = most_recent_msg ? most_recent_msg.getAttribute('data-isodate') : null;
if (_.isNull(most_recent_date)) {
return null;
}
const last_msg = u.getLastChildElement(this.content, '.message:not(.chat-state-notification)');
const most_recent_date = last_msg ? last_msg.getAttribute('data-isodate') : null;
if (_.isNull(most_recent_date)) {
const oldest_message = u.getLastChildElement(this.content, '.message:not(.chat-state-notification)');
const oldest_date = oldest_message ? oldest_message.getAttribute('data-isodate') : null;
if (!_.isNull(oldest_date) && dayjs(oldest_date).isAfter(cutoff)) {
return null;
}
if (dayjs(most_recent_date).isBefore(cutoff)) {
@ -668,29 +663,6 @@ 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) {
msgs.forEach(msg => {
this.content.insertAdjacentHTML(
@ -707,7 +679,6 @@ converse.plugins.add('converse-chatview', {
} else if (spinner === false) {
this.clearSpinner();
}
return this.scrollDown();
},
shouldShowOnTextMessage () {
@ -725,24 +696,24 @@ converse.plugins.add('converse-chatview', {
if (view.model.get('type') === 'error') {
const previous_msg_el = this.content.querySelector(`[data-msgid="${view.model.get('msgid')}"]`);
if (previous_msg_el) {
previous_msg_el.insertAdjacentElement('afterend', view.el);
previous_msg_el.insertAdjacentElement('beforeBegin', view.el);
return this.trigger('messageInserted', view.el);
}
}
const current_msg_date = dayjs(view.model.get('time')).toDate() || new Date(),
previous_msg_date = this.getLastMessageDate(current_msg_date);
const current_msg_date = dayjs(view.model.get('time')).toDate() || new Date();
const previous_msg_date = this.getLastMessageDate(current_msg_date);
if (_.isNull(previous_msg_date)) {
this.content.insertAdjacentElement('afterbegin', view.el);
this.content.insertAdjacentElement('beforeEnd', view.el);
} else {
const previous_msg_el = sizzle(`[data-isodate="${previous_msg_date.toISOString()}"]:last`, this.content).pop();
const previous_msg_el = sizzle(`[data-isodate="${previous_msg_date.toISOString()}"]:first`, this.content).pop();
if (view.model.get('type') === 'error' &&
u.hasClass('chat-error', previous_msg_el) &&
previous_msg_el.textContent === view.model.get('message')) {
// We don't show a duplicate error message
return;
}
previous_msg_el.insertAdjacentElement('afterend', view.el);
previous_msg_el.insertAdjacentElement('beforeBegin', view.el);
this.markFollowups(view.el);
}
return this.trigger('messageInserted', view.el);
@ -764,26 +735,27 @@ converse.plugins.add('converse-chatview', {
* @param { HTMLElement } el - The message element
*/
markFollowups (el) {
const from = el.getAttribute('data-from'),
previous_el = el.previousElementSibling,
date = dayjs(el.getAttribute('data-isodate')),
next_el = el.nextElementSibling;
const from = el.getAttribute('data-from');
const earlier_msg_el = el.nextElementSibling;
const date = dayjs(el.getAttribute('data-isodate'));
if (!u.hasClass('chat-msg--action', el) && !u.hasClass('chat-msg--action', previous_el) &&
previous_el.getAttribute('data-from') === from &&
date.isBefore(dayjs(previous_el.getAttribute('data-isodate')).add(10, 'minutes')) &&
el.getAttribute('data-encrypted') === previous_el.getAttribute('data-encrypted')) {
if (earlier_msg_el &&
!u.hasClass('chat-msg--action', el) && !u.hasClass('chat-msg--action', earlier_msg_el) &&
earlier_msg_el.getAttribute('data-from') === from &&
date.isBefore(dayjs(earlier_msg_el.getAttribute('data-isodate')).add(10, 'minutes')) &&
el.getAttribute('data-encrypted') === earlier_msg_el.getAttribute('data-encrypted')) {
u.addClass('chat-msg--followup', el);
}
if (!next_el) { return; }
if (!u.hasClass('chat-msg--action', 'el') &&
next_el.getAttribute('data-from') === from &&
dayjs(next_el.getAttribute('data-isodate')).isBefore(date.add(10, 'minutes')) &&
el.getAttribute('data-encrypted') === next_el.getAttribute('data-encrypted')) {
u.addClass('chat-msg--followup', next_el);
const later_msg_el = el.previousElementSibling;
if (!later_msg_el) { return; }
if (!u.hasClass('chat-msg--action', el) &&
later_msg_el.getAttribute('data-from') === from &&
dayjs(later_msg_el.getAttribute('data-isodate')).isBefore(date.add(10, 'minutes')) &&
el.getAttribute('data-encrypted') === later_msg_el.getAttribute('data-encrypted')) {
u.addClass('chat-msg--followup', later_msg_el);
} else {
u.removeClass('chat-msg--followup', next_el);
u.removeClass('chat-msg--followup', later_msg_el);
}
},
@ -808,7 +780,6 @@ converse.plugins.add('converse-chatview', {
this.insertMessage(view);
this.insertDayIndicator(view.el);
this.setScrollPosition(view.el);
if (u.isNewMessage(message)) {
if (message.get('sender') === 'me') {
@ -816,8 +787,8 @@ converse.plugins.add('converse-chatview', {
// gets scrolled down. We always want to scroll down
// when the user writes a message as opposed to when a
// message is received.
this.model.set('scrolled', false);
} else if (this.model.get('scrolled', true) && !u.isOnlyChatStateNotification(message)) {
this.scrolled = false;
} else if (this.scrolled && !u.isOnlyChatStateNotification(message)) {
this.showNewMessagesIndicator();
}
}
@ -1252,7 +1223,6 @@ converse.plugins.add('converse-chatview', {
'message': text,
'isodate': (new Date()).toISOString(),
}));
this.scrollDown();
}
}
},
@ -1319,7 +1289,6 @@ converse.plugins.add('converse-chatview', {
afterShown () {
this.model.clearUnreadMsgCounter();
this.setChatState(_converse.ACTIVE);
this.scrollDown();
this.focus();
},
@ -1360,26 +1329,16 @@ converse.plugins.add('converse-chatview', {
scrolled = false;
this.onScrolledDown();
}
u.safeSave(this.model, {
'scrolled': scrolled,
'top_visible_message': null
});
this.scrolled = scrolled;
},
viewUnreadMessages () {
this.model.save({
'scrolled': false,
'top_visible_message': null
});
this.scrolled = false;
this.scrollDown();
},
_scrollDown () {
/* Inner method that gets debounced */
if (_.isUndefined(this.content)) {
return;
}
if (u.isVisible(this.content) && !this.model.get('scrolled')) {
scrollDown () {
if (this.content && u.isVisible(this.content) && !this.scrolled) {
this.content.scrollTop = this.content.scrollHeight;
}
},

View File

@ -470,7 +470,6 @@ converse.plugins.add('converse-muc-views', {
this.initDebounced();
this.model.messages.on('add', this.onMessageAdded, this);
this.model.messages.on('rendered', this.scrollDown, this);
this.model.messages.on('reset', () => {
this.content.innerHTML = '';
this.removeAll();
@ -680,7 +679,6 @@ converse.plugins.add('converse-muc-views', {
this.model.clearUnreadMsgCounter();
this.model.save();
}
this.scrollDown();
this.renderEmojiPicker();
},
@ -706,7 +704,6 @@ converse.plugins.add('converse-muc-views', {
} else if (conn_status === converse.ROOMSTATUS.ENTERED) {
this.hideSpinner();
this.setChatState(_converse.ACTIVE);
this.scrollDown();
this.focus();
} else if (conn_status === converse.ROOMSTATUS.DISCONNECTED) {
this.showDisconnectMessage();
@ -762,7 +759,6 @@ converse.plugins.add('converse-muc-views', {
ev.stopPropagation();
}
this.model.save({'hidden_occupants': true});
this.scrollDown();
},
toggleOccupants (ev) {
@ -774,7 +770,6 @@ converse.plugins.add('converse-muc-views', {
ev.stopPropagation();
}
this.model.save({'hidden_occupants': !this.model.get('hidden_occupants')});
this.scrollDown();
},
onOccupantClicked (ev) {
@ -1297,18 +1292,23 @@ converse.plugins.add('converse-muc-views', {
}
},
/**
* Working backwards, get the first join/leave notification
* from the same user, on the same day and BEFORE any chat
* messages were received.
* @private
* @method _converse.ChatRoomView#getPreviousJoinOrLeaveNotification
* @param {HTMLElement} el
* @param {string} nick
*/
getPreviousJoinOrLeaveNotification (el, nick) {
/* Working backwards, get the first join/leave notification
* from the same user, on the same day and BEFORE any chat
* messages were received.
*/
while (!_.isNil(el)) {
const data = _.get(el, 'dataset', {});
if (!_.includes(_.get(el, 'classList', []), 'chat-info')) {
return;
}
if (!dayjs(el.getAttribute('data-isodate')).isSame(new Date(), "day")) {
el = el.previousElementSibling;
el = el.nextElementSibling;
continue;
}
if (data.join === nick ||
@ -1317,7 +1317,7 @@ converse.plugins.add('converse-muc-views', {
data.joinleave === nick) {
return el;
}
el = el.previousElementSibling;
el = el.nextElementSibling;
}
},
@ -1328,7 +1328,7 @@ converse.plugins.add('converse-muc-views', {
}
const nick = occupant.get('nick'),
stat = _converse.muc_show_join_leave_status ? occupant.get('status') : null,
prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick),
prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.firstElementChild, nick),
data = _.get(prev_info_el, 'dataset', {});
if (data.leave === nick) {
@ -1346,8 +1346,8 @@ converse.plugins.add('converse-muc-views', {
'message': message
};
this.content.removeChild(prev_info_el);
this.content.insertAdjacentHTML('beforeend', tpl_info(data));
const el = this.content.lastElementChild;
this.content.insertAdjacentHTML('afterBegin', tpl_info(data));
const el = this.content.firstElementChild;
setTimeout(() => u.addClass('fade-out', el), 5000);
setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5500);
} else {
@ -1366,13 +1366,12 @@ converse.plugins.add('converse-muc-views', {
};
if (prev_info_el) {
this.content.removeChild(prev_info_el);
this.content.insertAdjacentHTML('beforeend', tpl_info(data));
this.content.insertAdjacentHTML('afterBegin', tpl_info(data));
} else {
this.content.insertAdjacentHTML('beforeend', tpl_info(data));
this.insertDayIndicator(this.content.lastElementChild);
this.content.insertAdjacentHTML('afterBegin', tpl_info(data));
this.insertDayIndicator(this.content.firstElementChild);
}
}
this.scrollDown();
},
showLeaveNotification (occupant) {
@ -1383,7 +1382,7 @@ converse.plugins.add('converse-muc-views', {
}
const nick = occupant.get('nick'),
stat = _converse.muc_show_join_leave_status ? occupant.get('status') : null,
prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick),
prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.firstElementChild, nick),
dataset = _.get(prev_info_el, 'dataset', {});
if (dataset.join === nick) {
@ -1401,8 +1400,8 @@ converse.plugins.add('converse-muc-views', {
'message': message
};
this.content.removeChild(prev_info_el);
this.content.insertAdjacentHTML('beforeend', tpl_info(data));
const el = this.content.lastElementChild;
this.content.insertAdjacentHTML('afterBegin', tpl_info(data));
const el = this.content.firstElementChild;
setTimeout(() => u.addClass('fade-out', el), 5000);
setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5500);
} else {
@ -1421,13 +1420,12 @@ converse.plugins.add('converse-muc-views', {
}
if (prev_info_el) {
this.content.removeChild(prev_info_el);
this.content.insertAdjacentHTML('beforeend', tpl_info(data));
this.content.insertAdjacentHTML('afterBegin', tpl_info(data));
} else {
this.content.insertAdjacentHTML('beforeend', tpl_info(data));
this.insertDayIndicator(this.content.lastElementChild);
this.content.insertAdjacentHTML('afterBegin', tpl_info(data));
this.insertDayIndicator(this.content.firstElementChild);
}
}
this.scrollDown();
},
renderAfterTransition () {
@ -1442,7 +1440,6 @@ converse.plugins.add('converse-muc-views', {
} else {
u.showElement(this.el.querySelector('.chat-area'));
u.showElement(this.el.querySelector('.occupants'));
this.scrollDown();
}
},
@ -1492,7 +1489,6 @@ converse.plugins.add('converse-muc-views', {
'render_message': true
}));
}
this.scrollDown();
}
});

View File

@ -258,7 +258,7 @@ u.getLastChildElement = function (el, selector='*') {
}
u.hasClass = function (className, el) {
return _.includes(el.classList, className);
return Array.from(el.classList).includes(className);
};
u.addClass = function (className, el) {