Commit dd91d3cc authored by JC Brand's avatar JC Brand

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.
parent 21b0f246
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
"window": true "window": true
}, },
"rules": { "rules": {
"lodash/prefer-lodash-chain": "off",
"lodash/prefer-lodash-method": [2, { "lodash/prefer-lodash-method": [2, {
"ignoreMethods": [ "ignoreMethods": [
"assign", "every", "keys", "find", "endsWith", "startsWith", "filter", "assign", "every", "keys", "find", "endsWith", "startsWith", "filter",
......
...@@ -56,6 +56,8 @@ ...@@ -56,6 +56,8 @@
- 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
......
...@@ -2408,11 +2408,19 @@ ...@@ -2408,11 +2408,19 @@
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/backbone/-/backbone-1.4.0.tgz", "resolved": "https://registry.npmjs.org/backbone/-/backbone-1.4.0.tgz",
"integrity": "sha512-RLmDrRXkVdouTg38jcgHhyQ/2zjg7a8E6sz2zxfz21Hh17xDJYUHBZimVIt5fUyS8vbfpeSmTL3gUjTEvUV3qQ==", "integrity": "sha512-RLmDrRXkVdouTg38jcgHhyQ/2zjg7a8E6sz2zxfz21Hh17xDJYUHBZimVIt5fUyS8vbfpeSmTL3gUjTEvUV3qQ==",
"dev": true,
"requires": { "requires": {
"underscore": ">=1.8.3" "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": { "backbone.nativeview": {
"version": "github:conversejs/Backbone.NativeView#5997c8197ca594e6b8469447f28310c78bd1d95e", "version": "github:conversejs/Backbone.NativeView#5997c8197ca594e6b8469447f28310c78bd1d95e",
"from": "github:conversejs/Backbone.NativeView#5997c8197ca594e6b8469447f28310c78bd1d95e", "from": "github:conversejs/Backbone.NativeView#5997c8197ca594e6b8469447f28310c78bd1d95e",
...@@ -7096,8 +7104,7 @@ ...@@ -7096,8 +7104,7 @@
"lodash": { "lodash": {
"version": "4.17.11", "version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
"dev": true
}, },
"lodash-template-webpack-loader": { "lodash-template-webpack-loader": {
"version": "github:jcbrand/lodash-template-webpack-loader#258c095ab22130dfde454fa59ee0986f302bb733", "version": "github:jcbrand/lodash-template-webpack-loader#258c095ab22130dfde454fa59ee0986f302bb733",
...@@ -12004,6 +12011,14 @@ ...@@ -12004,6 +12011,14 @@
"find-up": "^2.1.0" "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": { "po2json": {
"version": "0.4.5", "version": "0.4.5",
"resolved": "https://registry.npmjs.org/po2json/-/po2json-0.4.5.tgz", "resolved": "https://registry.npmjs.org/po2json/-/po2json-0.4.5.tgz",
...@@ -13733,6 +13748,10 @@ ...@@ -13733,6 +13748,10 @@
"through": "^2.3.4" "through": "^2.3.4"
} }
}, },
"strophe.js": {
"version": "github:strophe/strophejs#31f31b52fd37a92eebee7b47d668a7d7dc40df3b",
"from": "github:strophe/strophejs#31f31b52fd37a92eebee7b47d668a7d7dc40df3b"
},
"style-loader": { "style-loader": {
"version": "0.23.1", "version": "0.23.1",
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz",
...@@ -14163,6 +14182,11 @@ ...@@ -14163,6 +14182,11 @@
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"dev": true "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": { "type-check": {
"version": "0.3.2", "version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
...@@ -14250,8 +14274,7 @@ ...@@ -14250,8 +14274,7 @@
"underscore": { "underscore": {
"version": "1.8.3", "version": "1.8.3",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
"integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI="
"dev": true
}, },
"underscore-contrib": { "underscore-contrib": {
"version": "0.3.0", "version": "0.3.0",
......
...@@ -207,6 +207,9 @@ ...@@ -207,6 +207,9 @@
margin-bottom: 0.25em; margin-bottom: 0.25em;
} }
.chat-content { .chat-content {
display: flex;
flex-direction: column-reverse;
padding: 1em;
height: 100%; height: 100%;
font-size: var(--message-font-size); font-size: var(--message-font-size);
color: var(--text-color); color: var(--text-color);
......
...@@ -33,7 +33,6 @@ ...@@ -33,7 +33,6 @@
color: var(--separator-text-color); color: var(--separator-text-color);
display: inline-block; display: inline-block;
line-height: 2em; line-height: 2em;
padding: 0 1em;
position: relative; position: relative;
z-index: 5; z-index: 5;
} }
...@@ -44,7 +43,7 @@ ...@@ -44,7 +43,7 @@
font-size: var(--message-font-size); font-size: var(--message-font-size);
line-height: var(--line-height-small); line-height: var(--line-height-small);
font-size: 90%; font-size: 90%;
padding: 0.17rem 1rem; padding: 0.17rem 0;
&.badge { &.badge {
color: var(--chat-head-text-color); color: var(--chat-head-text-color);
...@@ -77,11 +76,10 @@ ...@@ -77,11 +76,10 @@
} }
&.chat-msg { &.chat-msg {
display: inline-flex; display: flex;
width: 100%; width: 100%;
flex-direction: row; flex-direction: row;
overflow: auto; // Ensures that content stays inside padding: 0.125rem 0;
padding: 0.125rem 1rem;
&.onload { &.onload {
animation: colorchange-chatmessage 1s; animation: colorchange-chatmessage 1s;
...@@ -152,7 +150,6 @@ ...@@ -152,7 +150,6 @@
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 {
...@@ -269,7 +266,7 @@ ...@@ -269,7 +266,7 @@
} }
&.chat-msg--action { &.chat-msg--action {
.chat-msg__content { .chat-msg__content {
flex-wrap: nowrap; flex-wrap: wrap;
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
} }
......
...@@ -44,7 +44,7 @@ ...@@ -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.lastElementChild.textContent.trim().indexOf('hello world')).not.toBe(-1); expect(view.content.firstElementChild.textContent.trim().indexOf('hello world')).not.toBe(-1);
done(); done();
})); }));
...@@ -78,22 +78,22 @@ ...@@ -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:last', view.el).pop().textContent.trim() === '**Romeo Montague'); await test_utils.waitUntil(() => sizzle('.chat-msg__author:first', view.el).pop().textContent.trim() === '**Romeo Montague');
const last_el = sizzle('.chat-msg__text:last', view.el).pop(); const last_el = sizzle('.chat-msg__text:first', 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:last-child'); let message_el = view.el.querySelector('.message:first-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:last-child'); message_el = view.el.querySelector('.message:first-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:last', view.el).pop().textContent).toBe('wrote a 3rd person message'); expect(sizzle('.chat-msg__text:first', view.el).pop().textContent).toBe('wrote a 3rd person message');
expect(u.isVisible(sizzle('.chat-msg__author:last', view.el).pop())).toBeTruthy(); expect(u.isVisible(sizzle('.chat-msg__author:first', 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 @@ ...@@ -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(), 500); await test_utils.waitUntil(() => _converse.api.trigger.calls.count(), 1000);
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();
...@@ -361,7 +361,6 @@ ...@@ -361,7 +361,6 @@
spyOn(_converse.api, "trigger"); spyOn(_converse.api, "trigger");
// We need to rebind all events otherwise our spy won't be called // We need to rebind all events otherwise our spy won't be called
chatview.delegateEvents(); chatview.delegateEvents();
chatview.el.querySelector('.toggle-chatbox-button').click(); chatview.el.querySelector('.toggle-chatbox-button').click();
expect(chatview.minimize).toHaveBeenCalled(); expect(chatview.minimize).toHaveBeenCalled();
...@@ -377,7 +376,6 @@ ...@@ -377,7 +376,6 @@
expect(trimmedview.restore).toHaveBeenCalled(); expect(trimmedview.restore).toHaveBeenCalled();
expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxMaximized', jasmine.any(Object)); 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(); const toggle_el = sizzle('.toggle-chatbox-button', chatview.el).pop();
expect(u.hasClass('fa-minus', toggle_el)).toBeTruthy(); expect(u.hasClass('fa-minus', toggle_el)).toBeTruthy();
expect(u.hasClass('fa-plus', toggle_el)).toBeFalsy(); expect(u.hasClass('fa-plus', toggle_el)).toBeFalsy();
......
...@@ -239,7 +239,7 @@ ...@@ -239,7 +239,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: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, 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.
...@@ -264,11 +264,11 @@ ...@@ -264,11 +264,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[0].querySelector('.chat-msg__text').textContent) expect(messages[2].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[2].querySelector('.chat-msg__text').textContent) expect(messages[0].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();
...@@ -413,53 +413,53 @@ ...@@ -413,53 +413,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: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()); 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') expect(time.textContent).toEqual('Sunday Dec 31st 2017')
day = sizzle('.date-separator:first', chat_content).pop(); day = sizzle('.date-separator:last', chat_content).pop();
expect(day.nextElementSibling.querySelector('.chat-msg__text').textContent).toBe('Older message'); 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(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(1)', chat_content).pop(); time = sizzle('time.separator-text:eq(2)', chat_content).pop();
expect(time.textContent).toEqual("Monday Jan 1st 2018"); 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.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.querySelector('.chat-msg__text').textContent).toEqual('Inbetween message');
expect(el.nextElementSibling.querySelector('.chat-msg__text').textContent).toEqual('another inbetween message'); expect(el.previousElementSibling.querySelector('.chat-msg__text').textContent).toEqual('another inbetween message');
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) 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(2)', chat_content).pop(); time = sizzle('time.separator-text:nth(1)', chat_content).pop();
expect(time.textContent).toEqual("Tuesday Jan 2nd 2018"); 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.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(); 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(4)', chat_content).pop(); el = sizzle('.chat-msg:eq(2)', chat_content).pop();
expect(el.querySelector('.chat-msg__text').textContent).toEqual('message'); 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); 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.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); expect(u.hasClass('chat-msg--followup', el)).toBe(false);
done(); done();
})); }));
...@@ -779,12 +779,12 @@ ...@@ -779,12 +779,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:last', chat_content); day = sizzle('.date-separator:first', 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: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")); 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
...@@ -794,12 +794,12 @@ ...@@ -794,12 +794,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: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(msg_txt).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__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: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__author').textContent.trim()).toBe('Juliet Capulet'); expect(chat_content.querySelector('.chat-msg:first-child .chat-msg__author').textContent.trim()).toBe('Juliet Capulet');
done(); done();
})); }));
...@@ -890,21 +890,21 @@ ...@@ -890,21 +890,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: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.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: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.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: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.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;`);
...@@ -912,7 +912,7 @@ ...@@ -912,7 +912,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: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.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;');
...@@ -920,7 +920,7 @@ ...@@ -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` 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: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.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>`);
...@@ -977,7 +977,7 @@ ...@@ -977,7 +977,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: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(` stanza = u.toStanza(`
<message from="${contact_jid}" <message from="${contact_jid}"
type="chat" type="chat"
...@@ -986,7 +986,7 @@ ...@@ -986,7 +986,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: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(); done();
})); }));
...@@ -1005,7 +1005,7 @@ ...@@ -1005,7 +1005,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:last .chat-msg__text').pop(); let msg = sizzle('.chat-content .chat-msg:first .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"'+
...@@ -1014,7 +1014,7 @@ ...@@ -1014,7 +1014,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:last .chat-msg__text').pop(); msg = sizzle('.chat-content .chat-msg:first .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'+
...@@ -1025,7 +1025,7 @@ ...@@ -1025,7 +1025,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: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.textContent.trim()).toEqual('hello world');
expect(msg.querySelectorAll('img').length).toEqual(2); expect(msg.querySelectorAll('img').length).toEqual(2);
...@@ -1055,10 +1055,10 @@ ...@@ -1055,10 +1055,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: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'); 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); 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();
...@@ -1144,19 +1144,19 @@ ...@@ -1144,19 +1144,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(2)'))).toBe(false); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(2) .chat-msg__text').textContent).toBe("A message"); 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(3)'))).toBe(true); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(4)'))).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( 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"); "Another message 14 minutes since we started");
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(2)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(5) .chat-msg__text').textContent).toBe( expect(chat_content.querySelector('.message:nth-child(2) .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(6)'))).toBe(false); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(1)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(6) .chat-msg__text').textContent).toBe( expect(chat_content.querySelector('.message:nth-child(1) .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
...@@ -1176,21 +1176,21 @@ ...@@ -1176,21 +1176,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(2)'))).toBe(false); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(2) .chat-msg__text').textContent).toBe("A message"); 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(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 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(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 14 minutes since we started"); "Another message 14 minutes since we started");
expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(true); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(6) .chat-msg__text').textContent).toBe( expect(chat_content.querySelector('.message:nth-child(2) .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(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}) _converse.chatboxes.onMessage($msg({'id': 'aeb213', 'to': _converse.bare_jid})
.c('forwarded', {'xmlns': 'urn:xmpp:forward:0'}) .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
...@@ -1206,25 +1206,25 @@ ...@@ -1206,25 +1206,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(2)'))).toBe(false); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(7)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(2) .chat-msg__text').textContent).toBe("A message"); 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(3)'))).toBe(true); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(3) .chat-msg__text').textContent).toBe( expect(chat_content.querySelector('.message:nth-child(6) .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(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(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( 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"); "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(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(6) .chat-msg__text').textContent).toBe( expect(chat_content.querySelector('.message:nth-child(3) .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(7)'))).toBe(true); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(true);
expect(chat_content.querySelector('.message:nth-child(7) .chat-msg__text').textContent).toBe( expect(chat_content.querySelector('.message:nth-child(2) .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(8)'))).toBe(false); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(1)'))).toBe(false);
expect(chat_content.querySelector('.message:nth-child(8) .chat-msg__text').textContent).toBe( expect(chat_content.querySelector('.message:nth-child(1) .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();
...@@ -1662,7 +1662,7 @@ ...@@ -1662,7 +1662,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: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); expect(msg_txt).toEqual(msg_text);
/* <message xmlns="jabber:client" /* <message xmlns="jabber:client"
...@@ -1730,7 +1730,7 @@ ...@@ -1730,7 +1730,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: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); expect(msg_txt).toEqual(msg_text);
// A different error message will however render // A different error message will however render
...@@ -1810,14 +1810,20 @@ ...@@ -1810,14 +1810,20 @@
.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.model.get('scrolled'), 900); // await test_utils.waitUntil(() => view.scrolled, 900);
view.model.set('scrolled', true); 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'; const message = 'This message is received while the chat area is scrolled up';
_converse.chatboxes.onMessage($msg({ _converse.chatboxes.onMessage($msg({
...@@ -1831,10 +1837,10 @@ ...@@ -1831,10 +1837,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: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); 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.model.get('scrolled')).toBe(true); expect(view.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
...@@ -1935,9 +1941,9 @@ ...@@ -1935,9 +1941,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: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 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( 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>'+
...@@ -1985,9 +1991,9 @@ ...@@ -1985,9 +1991,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: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 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( 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>'+
...@@ -2328,7 +2334,7 @@ ...@@ -2328,7 +2334,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', 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({ presence = $pres({
to:'romeo@montague.lit/orchard', to:'romeo@montague.lit/orchard',
...@@ -2534,7 +2540,7 @@ ...@@ -2534,7 +2540,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', 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?'); expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
view.onKeyDown({ view.onKeyDown({
target: textarea, target: textarea,
......
...@@ -375,8 +375,8 @@ ...@@ -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[0]).toBe('A new groupchat has been created'); expect(info_texts[1]).toBe('A new groupchat has been created');
expect(info_texts[1]).toBe('nicky has entered the groupchat'); expect(info_texts[0]).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 @@ ...@@ -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:first', chat_content).pop().textContent)
.toBe("This groupchat is not anonymous");
expect(sizzle('div.chat-info:last', chat_content).pop().textContent) 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"); .toBe("some1 has entered the groupchat");
done(); done();
})); }));
...@@ -495,7 +495,7 @@ ...@@ -495,7 +495,7 @@
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) { 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 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,7 +512,10 @@ ...@@ -512,7 +512,10 @@
'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(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" /* <presence to="romeo@montague.lit/_converse.js-29092160"
* from="coven@chat.shakespeare.lit/some1"> * from="coven@chat.shakespeare.lit/some1">
...@@ -533,7 +536,7 @@ ...@@ -533,7 +536,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:first', chat_content).pop().textContent) expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
.toBe("some1 has entered the groupchat"); .toBe("some1 has entered the groupchat");
presence = $pres({ presence = $pres({
...@@ -547,8 +550,8 @@ ...@@ -547,8 +550,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(2); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(3);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent) expect(sizzle('div.chat-info:first', chat_content).pop().textContent)
.toBe("newguy has entered the groupchat"); .toBe("newguy has entered the groupchat");
const msg = $msg({ const msg = $msg({
...@@ -573,8 +576,8 @@ ...@@ -573,8 +576,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(4);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent) expect(sizzle('div.chat-info:first', 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
...@@ -588,7 +591,7 @@ ...@@ -588,7 +591,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(3); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(4);
/* <presence /* <presence
* from='coven@chat.shakespeare.lit/thirdwitch' * from='coven@chat.shakespeare.lit/thirdwitch'
...@@ -615,8 +618,8 @@ ...@@ -615,8 +618,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(4); expect(chat_content.querySelectorAll('div.chat-info').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(
'newguy has left the groupchat. '+ 'newguy has left the groupchat. '+
'"Disconnected: Replaced by new connection"'); '"Disconnected: Replaced by new connection"');
...@@ -632,8 +635,9 @@ ...@@ -632,8 +635,9 @@
'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);
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.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');
...@@ -649,8 +653,8 @@ ...@@ -649,8 +653,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(4); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(5);
msg_el = sizzle('div.chat-info', chat_content).pop(); msg_el = sizzle('.chat-info:first', 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');
...@@ -665,8 +669,8 @@ ...@@ -665,8 +669,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(5); expect(chat_content.querySelectorAll('.chat-info').length).toBe(6);
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"); .toBe("nomorenicks has entered the groupchat");
presence = $pres({ presence = $pres({
...@@ -680,8 +684,8 @@ ...@@ -680,8 +684,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(6);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent) expect(sizzle('div.chat-info:first', chat_content).pop().textContent)
.toBe("nomorenicks has entered and left the groupchat"); .toBe("nomorenicks has entered and left the groupchat");
presence = $pres({ presence = $pres({
...@@ -695,8 +699,8 @@ ...@@ -695,8 +699,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(5); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(6);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent) expect(sizzle('div.chat-info:first', 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
...@@ -710,7 +714,7 @@ ...@@ -710,7 +714,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(6); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(7);
/* <presence /* <presence
* from='coven@chat.shakespeare.lit/thirdwitch' * from='coven@chat.shakespeare.lit/thirdwitch'
...@@ -737,8 +741,8 @@ ...@@ -737,8 +741,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(7);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe( expect(sizzle('.chat-info:first', 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"');
...@@ -759,8 +763,8 @@ ...@@ -759,8 +763,8 @@
}); });
_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('.chat-info').length).toBe(7);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("newgirl has entered and left the groupchat"); 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); expect(view.model.occupants.length).toBe(4);
done(); done();
})); }));
...@@ -786,7 +790,7 @@ ...@@ -786,7 +790,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: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 = 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">
...@@ -796,7 +800,7 @@ ...@@ -796,7 +800,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: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 = 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">
...@@ -807,7 +811,7 @@ ...@@ -807,7 +811,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: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 = 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">
...@@ -817,7 +821,7 @@ ...@@ -817,7 +821,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: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 = 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">
...@@ -827,7 +831,7 @@ ...@@ -827,7 +831,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: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 = 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">
...@@ -839,7 +843,7 @@ ...@@ -839,7 +843,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: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 = 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">
...@@ -849,7 +853,7 @@ ...@@ -849,7 +853,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: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 = 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">
...@@ -860,7 +864,7 @@ ...@@ -860,7 +864,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: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"`); `fabio has entered and left the groupchat. "Disconnected: Replaced by new connection"`);
presence = u.toStanza( presence = u.toStanza(
...@@ -873,7 +877,7 @@ ...@@ -873,7 +877,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: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"`); `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
...@@ -900,7 +904,7 @@ ...@@ -900,7 +904,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:last', chat_content).pop().textContent).toBe( expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe(
`Dele Olajide has left the groupchat`); `Dele Olajide has left the groupchat`);
presence = u.toStanza( presence = u.toStanza(
...@@ -912,7 +916,7 @@ ...@@ -912,7 +916,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: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`); `fabio has left and re-entered the groupchat`);
done(); done();
})); }));
...@@ -938,7 +942,7 @@ ...@@ -938,7 +942,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('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({ presence = $pres({
to: 'romeo@montague.lit/orchard', to: 'romeo@montague.lit/orchard',
...@@ -954,7 +958,7 @@ ...@@ -954,7 +958,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('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 = 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">
...@@ -966,7 +970,7 @@ ...@@ -966,7 +970,7 @@
</presence>`); </presence>`);
_converse.connection._dataRecv(test_utils.createRequest(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 = 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">
...@@ -975,8 +979,8 @@ ...@@ -975,8 +979,8 @@
</x> </x>
</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('.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:first', 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(
...@@ -987,8 +991,8 @@ ...@@ -987,8 +991,8 @@
</x> </x>
</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('.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:first', chat_content).pop().textContent).toBe(`Dele Olajide has left the groupchat`);
done(); done();
})); }));
...@@ -1045,11 +1049,11 @@ ...@@ -1045,11 +1049,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('div.chat-info'); const messages = chat_content.querySelectorAll('.chat-info');
expect(messages.length).toBe(3); 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[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(); done();
})); }));
...@@ -1102,8 +1106,8 @@ ...@@ -1102,8 +1106,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('div.chat-info').length).toBe(2); expect(chat_content.querySelectorAll('.chat-info').length).toBe(2);
expect(chat_content.querySelector('div.chat-info:last-child').textContent).toBe( expect(chat_content.querySelector('.chat-info:first-child').textContent).toBe(
"some1 has entered the groupchat" "some1 has entered the groupchat"
); );
...@@ -1131,8 +1135,8 @@ ...@@ -1131,8 +1135,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('div.chat-info').length).toBe(3); expect(chat_content.querySelectorAll('.chat-info').length).toBe(3);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe( expect(sizzle('div.chat-info:first', 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"');
...@@ -1163,12 +1167,12 @@ ...@@ -1163,12 +1167,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(3)', chat_content).pop(); indicator = sizzle('.date-separator:eq(0)', 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('div.chat-info').length).toBe(4); expect(chat_content.querySelectorAll('.chat-info').length).toBe(4);
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"); .toBe("newguy has entered the groupchat");
jasmine.clock().tick(ONE_DAY_LATER); jasmine.clock().tick(ONE_DAY_LATER);
...@@ -1203,12 +1207,12 @@ ...@@ -1203,12 +1207,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(5)', chat_content).pop(); indicator = sizzle('.date-separator:eq(0)', 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('div.chat-info').length).toBe(5); expect(chat_content.querySelectorAll('.chat-info').length).toBe(5);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe( expect(sizzle('.chat-info:first', 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();
...@@ -1251,8 +1255,8 @@ ...@@ -1251,8 +1255,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:last', view.el).pop().textContent, '**Romeo Montague')).toBeTruthy(); expect(_.includes(sizzle('.chat-msg__author:first', view.el).pop().textContent, '**Romeo Montague')).toBeTruthy();
expect(sizzle('.chat-msg__text:last', view.el).pop().textContent).toBe('is as well'); expect(sizzle('.chat-msg__text:first', view.el).pop().textContent).toBe('is as well');
done(); done();
})); }));
...@@ -1799,7 +1803,7 @@ ...@@ -1799,7 +1803,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: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'); expect(info_text).toBe('Your nickname has been automatically set to thirdwitch');
done(); done();
})); }));
...@@ -2017,7 +2021,7 @@ ...@@ -2017,7 +2021,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: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(msg_txt).toEqual(message);
expect(view.content.scrollTop).toBe(0); expect(view.content.scrollTop).toBe(0);
done(); done();
...@@ -2155,8 +2159,8 @@ ...@@ -2155,8 +2159,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[0].textContent).toBe('romeo has entered the groupchat'); expect(info_messages[1].textContent).toBe('romeo has entered the groupchat');
expect(info_messages[1].textContent).toBe('groupchat logging is now enabled'); expect(info_messages[0].textContent).toBe('groupchat logging is now enabled');
done(); done();
})); }));
...@@ -2234,7 +2238,7 @@ ...@@ -2234,7 +2238,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('div.chat-info:last').pop().textContent).toBe( expect(sizzle('.chat-info:first').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);
...@@ -2577,7 +2581,7 @@ ...@@ -2577,7 +2581,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:last', chat_body).pop().textContent) expect(sizzle('.message:first', chat_body).pop().textContent)
.toBe('This groupchat is now no longer anonymous'); .toBe('This groupchat is now no longer anonymous');
done(); done();
})); }));
...@@ -3302,7 +3306,7 @@ ...@@ -3302,7 +3306,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')[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); expect(view.el.querySelectorAll('.chat-info').length).toBe(4);
done(); done();
})); }));
...@@ -3348,10 +3352,10 @@ ...@@ -3348,10 +3352,10 @@
'role': 'participant' 'role': 'participant'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
var info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0); const info_msg = view.el.querySelector('.chat-info:first-child');
expect(info_msgs.pop().textContent).toBe("trustworthyguy has entered the groupchat"); 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'; textarea.value = '/op';
view.onKeyDown({ view.onKeyDown({
target: textarea, target: textarea,
...@@ -3404,7 +3408,7 @@ ...@@ -3404,7 +3408,7 @@
'role': 'moderator' 'role': 'moderator'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _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"); 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
...@@ -3478,7 +3482,7 @@ ...@@ -3478,7 +3482,7 @@
* </x> * </x>
* </presence> * </presence>
*/ */
var presence = $pres({ let presence = $pres({
'from': 'lounge@montague.lit/annoyingGuy', 'from': 'lounge@montague.lit/annoyingGuy',
'id':'27C55F89-1C6A-459A-9EB5-77690145D624', 'id':'27C55F89-1C6A-459A-9EB5-77690145D624',
'to': 'romeo@montague.lit/desktop' 'to': 'romeo@montague.lit/desktop'
...@@ -3490,8 +3494,8 @@ ...@@ -3490,8 +3494,8 @@
'role': 'participant' 'role': 'participant'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
var info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0); const info_msg = sizzle('.chat-info:first', view.el).pop();
expect(info_msgs.pop().textContent).toBe("annoyingGuy has entered the groupchat"); expect(info_msg.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';
...@@ -3545,7 +3549,7 @@ ...@@ -3545,7 +3549,7 @@
'role': 'visitor' 'role': 'visitor'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _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"); expect(info_msgs.pop().textContent).toBe("annoyingGuy has been muted");
// Call now with the correct of arguments. // Call now with the correct of arguments.
...@@ -4683,7 +4687,7 @@ ...@@ -4683,7 +4687,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('div.chat-info:last', chat_content).pop().textContent) expect(sizzle('.chat-info:first', chat_content).pop().textContent)
.toBe("newguy has entered the groupchat"); .toBe("newguy has entered the groupchat");
presence = $pres({ presence = $pres({
...@@ -4698,7 +4702,7 @@ ...@@ -4698,7 +4702,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('div.chat-info:last', chat_content).pop().textContent) expect(sizzle('.chat-info:first', 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
...@@ -4717,9 +4721,9 @@ ...@@ -4717,9 +4721,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[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[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'); let notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(1); expect(notifications.length).toBe(1);
...@@ -4739,9 +4743,9 @@ ...@@ -4739,9 +4743,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[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[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'); notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(1); expect(notifications.length).toBe(1);
...@@ -4758,15 +4762,15 @@ ...@@ -4758,15 +4762,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[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[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)); 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[0].textContent).toEqual('nomorenicks is typing'); expect(notifications[1].textContent).toEqual('nomorenicks is typing');
expect(notifications[1].textContent).toEqual('newguy is typing'); expect(notifications[0].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({
...@@ -4787,9 +4791,9 @@ ...@@ -4787,9 +4791,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[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[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'); notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(1); expect(notifications.length).toBe(1);
...@@ -4798,9 +4802,9 @@ ...@@ -4798,9 +4802,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[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[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'); notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(0); expect(notifications.length).toBe(0);
...@@ -4853,7 +4857,7 @@ ...@@ -4853,7 +4857,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('div.chat-info:last', chat_content).pop().textContent) expect(sizzle('.chat-info:first', chat_content).pop().textContent)
.toBe("newguy has entered the groupchat"); .toBe("newguy has entered the groupchat");
presence = $pres({ presence = $pres({
...@@ -4868,7 +4872,7 @@ ...@@ -4868,7 +4872,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('div.chat-info:last', chat_content).pop().textContent) expect(sizzle('.chat-info:first', 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
...@@ -4885,9 +4889,9 @@ ...@@ -4885,9 +4889,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[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[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); 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');
...@@ -4905,9 +4909,9 @@ ...@@ -4905,9 +4909,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[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[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'); notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(1); expect(notifications.length).toBe(1);
...@@ -4923,9 +4927,9 @@ ...@@ -4923,9 +4927,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[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[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); 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');
...@@ -4946,9 +4950,9 @@ ...@@ -4946,9 +4950,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[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[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(() => { await test_utils.waitUntil(() => {
return _.map( return _.map(
......
...@@ -208,7 +208,7 @@ ...@@ -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')[1].textContent.trim()) expect(view.el.querySelectorAll('.chat-msg__body')[0].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 @@ ...@@ -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')[2].textContent.trim()) expect(view.el.querySelectorAll('.chat-msg__body')[0].textContent.trim())
.toBe('Another received encrypted message without fallback'); .toBe('Another received encrypted message without fallback');
done(); done();
})); }));
......
...@@ -344,7 +344,6 @@ converse.plugins.add('converse-chatview', { ...@@ -344,7 +344,6 @@ 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,9 +365,8 @@ converse.plugins.add('converse-chatview', { ...@@ -366,9 +365,8 @@ 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, 250, {'leading': true}); this.show = _.debounce(this._show, 500, {'leading': true});
}, },
render () { render () {
...@@ -545,8 +543,7 @@ converse.plugins.add('converse-chatview', { ...@@ -545,8 +543,7 @@ 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.scrollDown(); this.content.addEventListener('scroll', () => this.markScrolled());
this.content.addEventListener('scroll', this.markScrolled.bind(this));
}, },
insertIntoDOM () { insertIntoDOM () {
...@@ -567,8 +564,7 @@ converse.plugins.add('converse-chatview', { ...@@ -567,8 +564,7 @@ converse.plugins.add('converse-chatview', {
'message': message, 'message': message,
'isodate': isodate, 'isodate': isodate,
})); }));
this.insertDayIndicator(this.content.lastElementChild); this.insertDayIndicator(this.content.firstElementChild);
this.scrollDown();
return isodate; return isodate;
}, },
...@@ -577,14 +573,12 @@ converse.plugins.add('converse-chatview', { ...@@ -577,14 +573,12 @@ converse.plugins.add('converse-chatview', {
'beforeend', '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('beforeend', tpl_spinner()); this.content.insertAdjacentHTML('beforeend', tpl_spinner());
this.scrollDown();
} else { } else {
this.content.insertAdjacentHTML('afterbegin', tpl_spinner()); this.content.insertAdjacentHTML('afterbegin', tpl_spinner());
} }
...@@ -607,16 +601,16 @@ converse.plugins.add('converse-chatview', { ...@@ -607,16 +601,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 prev_msg_el = u.getPreviousElement(next_msg_el, ".message:not(.chat-state-notification)"), const earlier_msg_el = u.getNextElement(next_msg_el, ".message:not(.chat-state-notification)"),
prev_msg_date = _.isNull(prev_msg_el) ? null : prev_msg_el.getAttribute('data-isodate'), earlier_msg_date = _.isNull(earlier_msg_el) ? null : earlier_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(prev_msg_date) && _.isNull(next_msg_date)) { if (_.isNull(earlier_msg_date) && _.isNull(next_msg_date)) {
return; 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'); const day_date = dayjs(next_msg_date).startOf('day');
next_msg_el.insertAdjacentHTML('beforeBegin', next_msg_el.insertAdjacentHTML('afterEnd',
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")
...@@ -634,14 +628,15 @@ converse.plugins.add('converse-chatview', { ...@@ -634,14 +628,15 @@ converse.plugins.add('converse-chatview', {
* @returns { Date } * @returns { Date }
*/ */
getLastMessageDate (cutoff) { getLastMessageDate (cutoff) {
const first_msg = u.getFirstChildElement(this.content, '.message:not(.chat-state-notification)'); const most_recent_msg = u.getFirstChildElement(this.content, '.message:not(.chat-state-notification)');
const oldest_date = first_msg ? first_msg.getAttribute('data-isodate') : null; const most_recent_date = most_recent_msg ? most_recent_msg.getAttribute('data-isodate') : null;
if (!_.isNull(oldest_date) && dayjs(oldest_date).isAfter(cutoff)) { if (_.isNull(most_recent_date)) {
return null; 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; const oldest_message = u.getLastChildElement(this.content, '.message:not(.chat-state-notification)');
if (_.isNull(most_recent_date)) { const oldest_date = oldest_message ? oldest_message.getAttribute('data-isodate') : null;
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)) {
...@@ -668,29 +663,6 @@ converse.plugins.add('converse-chatview', { ...@@ -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) { showHelpMessages (msgs, type, spinner) {
msgs.forEach(msg => { msgs.forEach(msg => {
this.content.insertAdjacentHTML( this.content.insertAdjacentHTML(
...@@ -707,7 +679,6 @@ converse.plugins.add('converse-chatview', { ...@@ -707,7 +679,6 @@ converse.plugins.add('converse-chatview', {
} else if (spinner === false) { } else if (spinner === false) {
this.clearSpinner(); this.clearSpinner();
} }
return this.scrollDown();
}, },
shouldShowOnTextMessage () { shouldShowOnTextMessage () {
...@@ -725,24 +696,24 @@ converse.plugins.add('converse-chatview', { ...@@ -725,24 +696,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('afterend', view.el); previous_msg_el.insertAdjacentElement('beforeBegin', 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();
previous_msg_date = this.getLastMessageDate(current_msg_date); const previous_msg_date = this.getLastMessageDate(current_msg_date);
if (_.isNull(previous_msg_date)) { if (_.isNull(previous_msg_date)) {
this.content.insertAdjacentElement('afterbegin', view.el); this.content.insertAdjacentElement('beforeEnd', view.el);
} else { } 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' && 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('afterend', view.el); previous_msg_el.insertAdjacentElement('beforeBegin', view.el);
this.markFollowups(view.el); this.markFollowups(view.el);
} }
return this.trigger('messageInserted', view.el); return this.trigger('messageInserted', view.el);
...@@ -764,26 +735,27 @@ converse.plugins.add('converse-chatview', { ...@@ -764,26 +735,27 @@ 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');
previous_el = el.previousElementSibling, const earlier_msg_el = el.nextElementSibling;
date = dayjs(el.getAttribute('data-isodate')), const 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; }
if (!u.hasClass('chat-msg--action', 'el') && const later_msg_el = el.previousElementSibling;
next_el.getAttribute('data-from') === from && if (!later_msg_el) { return; }
dayjs(next_el.getAttribute('data-isodate')).isBefore(date.add(10, 'minutes')) && if (!u.hasClass('chat-msg--action', el) &&
el.getAttribute('data-encrypted') === next_el.getAttribute('data-encrypted')) { later_msg_el.getAttribute('data-from') === from &&
u.addClass('chat-msg--followup', next_el); 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 { } 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', { ...@@ -808,7 +780,6 @@ 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') {
...@@ -816,8 +787,8 @@ converse.plugins.add('converse-chatview', { ...@@ -816,8 +787,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.model.set('scrolled', false); this.scrolled = false;
} else if (this.model.get('scrolled', true) && !u.isOnlyChatStateNotification(message)) { } else if (this.scrolled && !u.isOnlyChatStateNotification(message)) {
this.showNewMessagesIndicator(); this.showNewMessagesIndicator();
} }
} }
...@@ -1252,7 +1223,6 @@ converse.plugins.add('converse-chatview', { ...@@ -1252,7 +1223,6 @@ converse.plugins.add('converse-chatview', {
'message': text, 'message': text,
'isodate': (new Date()).toISOString(), 'isodate': (new Date()).toISOString(),
})); }));
this.scrollDown();
} }
} }
}, },
...@@ -1319,7 +1289,6 @@ converse.plugins.add('converse-chatview', { ...@@ -1319,7 +1289,6 @@ converse.plugins.add('converse-chatview', {
afterShown () { afterShown () {
this.model.clearUnreadMsgCounter(); this.model.clearUnreadMsgCounter();
this.setChatState(_converse.ACTIVE); this.setChatState(_converse.ACTIVE);
this.scrollDown();
this.focus(); this.focus();
}, },
...@@ -1360,26 +1329,16 @@ converse.plugins.add('converse-chatview', { ...@@ -1360,26 +1329,16 @@ converse.plugins.add('converse-chatview', {
scrolled = false; scrolled = false;
this.onScrolledDown(); this.onScrolledDown();
} }
u.safeSave(this.model, { this.scrolled = scrolled;
'scrolled': scrolled,
'top_visible_message': null
});
}, },
viewUnreadMessages () { viewUnreadMessages () {
this.model.save({ this.scrolled = false;
'scrolled': false,
'top_visible_message': null
});
this.scrollDown(); this.scrollDown();
}, },
_scrollDown () { scrollDown () {
/* Inner method that gets debounced */ if (this.content && u.isVisible(this.content) && !this.scrolled) {
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;
} }
}, },
......
...@@ -470,7 +470,6 @@ converse.plugins.add('converse-muc-views', { ...@@ -470,7 +470,6 @@ 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();
...@@ -680,7 +679,6 @@ converse.plugins.add('converse-muc-views', { ...@@ -680,7 +679,6 @@ 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,7 +704,6 @@ converse.plugins.add('converse-muc-views', { ...@@ -706,7 +704,6 @@ 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();
this.focus(); this.focus();
} else if (conn_status === converse.ROOMSTATUS.DISCONNECTED) { } else if (conn_status === converse.ROOMSTATUS.DISCONNECTED) {
this.showDisconnectMessage(); this.showDisconnectMessage();
...@@ -762,7 +759,6 @@ converse.plugins.add('converse-muc-views', { ...@@ -762,7 +759,6 @@ 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,7 +770,6 @@ converse.plugins.add('converse-muc-views', { ...@@ -774,7 +770,6 @@ 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) {
...@@ -1297,18 +1292,23 @@ converse.plugins.add('converse-muc-views', { ...@@ -1297,18 +1292,23 @@ converse.plugins.add('converse-muc-views', {
} }
}, },
getPreviousJoinOrLeaveNotification (el, nick) { /**
/* Working backwards, get the first join/leave notification * Working backwards, get the first join/leave notification
* from the same user, on the same day and BEFORE any chat * from the same user, on the same day and BEFORE any chat
* messages were received. * messages were received.
* @private
* @method _converse.ChatRoomView#getPreviousJoinOrLeaveNotification
* @param {HTMLElement} el
* @param {string} nick
*/ */
getPreviousJoinOrLeaveNotification (el, nick) {
while (!_.isNil(el)) { while (!_.isNil(el)) {
const data = _.get(el, 'dataset', {}); const data = _.get(el, 'dataset', {});
if (!_.includes(_.get(el, 'classList', []), 'chat-info')) { if (!_.includes(_.get(el, 'classList', []), 'chat-info')) {
return; return;
} }
if (!dayjs(el.getAttribute('data-isodate')).isSame(new Date(), "day")) { if (!dayjs(el.getAttribute('data-isodate')).isSame(new Date(), "day")) {
el = el.previousElementSibling; el = el.nextElementSibling;
continue; continue;
} }
if (data.join === nick || if (data.join === nick ||
...@@ -1317,7 +1317,7 @@ converse.plugins.add('converse-muc-views', { ...@@ -1317,7 +1317,7 @@ converse.plugins.add('converse-muc-views', {
data.joinleave === nick) { data.joinleave === nick) {
return el; return el;
} }
el = el.previousElementSibling; el = el.nextElementSibling;
} }
}, },
...@@ -1328,7 +1328,7 @@ converse.plugins.add('converse-muc-views', { ...@@ -1328,7 +1328,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.lastElementChild, nick), prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.firstElementChild, nick),
data = _.get(prev_info_el, 'dataset', {}); data = _.get(prev_info_el, 'dataset', {});
if (data.leave === nick) { if (data.leave === nick) {
...@@ -1346,8 +1346,8 @@ converse.plugins.add('converse-muc-views', { ...@@ -1346,8 +1346,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('beforeend', tpl_info(data)); this.content.insertAdjacentHTML('afterBegin', tpl_info(data));
const el = this.content.lastElementChild; const el = this.content.firstElementChild;
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 {
...@@ -1366,13 +1366,12 @@ converse.plugins.add('converse-muc-views', { ...@@ -1366,13 +1366,12 @@ 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('beforeend', tpl_info(data)); this.content.insertAdjacentHTML('afterBegin', tpl_info(data));
} else { } else {
this.content.insertAdjacentHTML('beforeend', tpl_info(data)); this.content.insertAdjacentHTML('afterBegin', tpl_info(data));
this.insertDayIndicator(this.content.lastElementChild); this.insertDayIndicator(this.content.firstElementChild);
} }
} }
this.scrollDown();
}, },
showLeaveNotification (occupant) { showLeaveNotification (occupant) {
...@@ -1383,7 +1382,7 @@ converse.plugins.add('converse-muc-views', { ...@@ -1383,7 +1382,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.lastElementChild, nick), prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.firstElementChild, nick),
dataset = _.get(prev_info_el, 'dataset', {}); dataset = _.get(prev_info_el, 'dataset', {});
if (dataset.join === nick) { if (dataset.join === nick) {
...@@ -1401,8 +1400,8 @@ converse.plugins.add('converse-muc-views', { ...@@ -1401,8 +1400,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('beforeend', tpl_info(data)); this.content.insertAdjacentHTML('afterBegin', tpl_info(data));
const el = this.content.lastElementChild; const el = this.content.firstElementChild;
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 {
...@@ -1421,13 +1420,12 @@ converse.plugins.add('converse-muc-views', { ...@@ -1421,13 +1420,12 @@ 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('beforeend', tpl_info(data)); this.content.insertAdjacentHTML('afterBegin', tpl_info(data));
} else { } else {
this.content.insertAdjacentHTML('beforeend', tpl_info(data)); this.content.insertAdjacentHTML('afterBegin', tpl_info(data));
this.insertDayIndicator(this.content.lastElementChild); this.insertDayIndicator(this.content.firstElementChild);
} }
} }
this.scrollDown();
}, },
renderAfterTransition () { renderAfterTransition () {
...@@ -1442,7 +1440,6 @@ converse.plugins.add('converse-muc-views', { ...@@ -1442,7 +1440,6 @@ 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();
} }
}, },
...@@ -1492,7 +1489,6 @@ converse.plugins.add('converse-muc-views', { ...@@ -1492,7 +1489,6 @@ converse.plugins.add('converse-muc-views', {
'render_message': true 'render_message': true
})); }));
} }
this.scrollDown();
} }
}); });
......
...@@ -258,7 +258,7 @@ u.getLastChildElement = function (el, selector='*') { ...@@ -258,7 +258,7 @@ u.getLastChildElement = function (el, selector='*') {
} }
u.hasClass = function (className, el) { u.hasClass = function (className, el) {
return _.includes(el.classList, className); return Array.from(el.classList).includes(className);
}; };
u.addClass = function (className, el) { u.addClass = function (className, el) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment