Jelajahi Sumber

Fix and test message ordering

JC Brand 7 tahun lalu
induk
melakukan
ee15e9e331
3 mengubah file dengan 212 tambahan dan 57 penghapusan
  1. 138 0
      spec/chatbox.js
  2. 73 57
      src/converse-chatview.js
  3. 1 0
      src/converse-headline.js

+ 138 - 0
spec/chatbox.js

@@ -879,6 +879,144 @@
                         }).then(done);
                     }));
 
+                    it("can be received out of order, and will still be displayed in the right order",
+                        mock.initConverseWithPromises(
+                            null, ['rosterGroupsFetched'], {},
+                            function (done, _converse) {
+
+                        test_utils.createContacts(_converse, 'current');
+                        test_utils.openControlBox();
+                        test_utils.openContactsPanel(_converse);
+
+                        test_utils.waitUntil(function () {
+                                return _converse.rosterview.$el.find('.roster-group').length;
+                            }, 300)
+                        .then(function () {
+                            var message, msg;
+                            spyOn(_converse, 'log');
+                            spyOn(_converse.chatboxes, 'getChatBox').and.callThrough();
+                            _converse.filter_by_resource = true;
+                            var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
+
+                            /*  <message id='aeb213' to='juliet@capulet.lit/chamber'>
+                             *    <forwarded xmlns='urn:xmpp:forward:0'>
+                             *      <delay xmlns='urn:xmpp:delay' stamp='2010-07-10T23:08:25Z'/>
+                             *      <message xmlns='jabber:client'
+                             *          to='juliet@capulet.lit/balcony'
+                             *          from='romeo@montague.lit/orchard'
+                             *          type='chat'>
+                             *          <body>Call me but love, and I'll be new baptized; Henceforth I never will be Romeo.</body>
+                             *      </message>
+                             *    </forwarded>
+                             *  </message>
+                             */
+                            msg = $msg({'id': 'aeb213', 'to': _converse.bare_jid})
+                                .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
+                                    .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-02T13:08:25Z'}).up()
+                                    .c('message', {
+                                        'xmlns': 'jabber:client',
+                                        'to': _converse.bare_jid,
+                                        'from': sender_jid,
+                                        'type': 'chat'})
+                                    .c('body').t("message from today")
+                                    .tree();
+                            _converse.chatboxes.onMessage(msg);
+
+                            msg = $msg({'id': 'aeb214', 'to': _converse.bare_jid})
+                                .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
+                                    .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2017-12-31T23:08:25Z'}).up()
+                                    .c('message', {
+                                        'xmlns': 'jabber:client',
+                                        'to': _converse.bare_jid,
+                                        'from': sender_jid,
+                                        'type': 'chat'})
+                                    .c('body').t("Older message")
+                                    .tree();
+                            _converse.chatboxes.onMessage(msg);
+
+                            msg = $msg({'id': 'aeb215', 'to': _converse.bare_jid})
+                                .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
+                                    .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-01T13:18:23Z'}).up()
+                                    .c('message', {
+                                        'xmlns': 'jabber:client',
+                                        'to': _converse.bare_jid,
+                                        'from': sender_jid,
+                                        'type': 'chat'})
+                                    .c('body').t("Inbetween message")
+                                    .tree();
+                            _converse.chatboxes.onMessage(msg);
+
+                            msg = $msg({'id': 'aeb216', 'to': _converse.bare_jid})
+                                .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
+                                    .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-01T13:18:23Z'}).up()
+                                    .c('message', {
+                                        'xmlns': 'jabber:client',
+                                        'to': _converse.bare_jid,
+                                        'from': sender_jid,
+                                        'type': 'chat'})
+                                    .c('body').t("another inbetween message")
+                                    .tree();
+                            _converse.chatboxes.onMessage(msg);
+
+                            msg = $msg({'id': 'aeb217', 'to': _converse.bare_jid})
+                                .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
+                                    .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-02T12:18:23Z'}).up()
+                                    .c('message', {
+                                        'xmlns': 'jabber:client',
+                                        'to': _converse.bare_jid,
+                                        'from': sender_jid,
+                                        'type': 'chat'})
+                                    .c('body').t("An earlier message today")
+                                    .tree();
+                            _converse.chatboxes.onMessage(msg);
+
+                            msg = $msg({'id': 'aeb218', 'to': _converse.bare_jid})
+                                .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
+                                    .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':'2018-01-02T23:28:23Z'}).up()
+                                    .c('message', {
+                                        'xmlns': 'jabber:client',
+                                        'to': _converse.bare_jid,
+                                        'from': sender_jid,
+                                        'type': 'chat'})
+                                    .c('body').t("newer message from today")
+                                    .tree();
+                            _converse.chatboxes.onMessage(msg);
+
+                            var chatboxview = _converse.chatboxviews.get(sender_jid);
+                            var $chat_content = chatboxview.$el.find('.chat-content');
+                            chatboxview.clearSpinner(); //cleanup
+
+                            var $time = $chat_content.find('time');
+                            expect($time.length).toEqual(3);
+                            $time = $chat_content.find('time:first');
+                            expect($time.data('isodate')).toEqual('2017-12-31T00:00:00+00:00');
+
+                            expect($time[0].nextElementSibling.querySelector('.chat-msg-content').textContent).toBe('Older message');
+                            var $el = $chat_content.find('.chat-message:first').find('.chat-msg-content')
+                            expect($el.text()).toEqual('Older message');
+
+                            $time = $chat_content.find('time:eq(1)');
+                            expect($time.data('isodate')).toEqual('2018-01-01T00:00:00+00:00');
+                            expect($time[0].nextElementSibling.querySelector('.chat-msg-content').textContent).toBe('Inbetween message');
+                            $el = $chat_content.find('.chat-message:eq(1)');
+                            expect($el.find('.chat-msg-content').text()).toEqual('Inbetween message');
+                            expect($el[0].nextElementSibling.querySelector('.chat-msg-content').textContent).toEqual('another inbetween message');
+                            $el = $chat_content.find('.chat-message:eq(2)');
+                            expect($el.find('.chat-msg-content').text()).toEqual('another inbetween message');
+
+                            $time = $chat_content.find('time:last');
+                            expect($time.data('isodate')).toEqual('2018-01-02T00:00:00+00:00');
+                            expect($time[0].nextElementSibling.querySelector('.chat-msg-content').textContent).toBe('An earlier message today');
+                            $el = $chat_content.find('.chat-message:eq(3)');
+                            expect($el.find('.chat-msg-content').text()).toEqual('An earlier message today');
+
+                            $el = $chat_content.find('.chat-message:eq(4)');
+                            expect($el.find('.chat-msg-content').text()).toEqual('message from today');
+                            expect($el[0].nextElementSibling.querySelector('.chat-msg-content').textContent).toEqual('newer message from today');
+                            done();
+                        });
+                    }));
+
                     it("is ignored if it's intended for a different resource and filter_by_resource is set to true",
                         mock.initConverseWithPromises(
                             null, ['rosterGroupsFetched'], {},

+ 73 - 57
src/converse-chatview.js

@@ -42,7 +42,7 @@
             tpl_toolbar
     ) {
     "use strict";
-    const { $msg, Backbone, Strophe, _, b64_sha1, moment } = converse.env;
+    const { $msg, Backbone, Strophe, _, b64_sha1, sizzle, moment } = converse.env;
     const u = converse.env.utils;
     const KEY = {
         ENTER: 13,
@@ -277,7 +277,6 @@
                     this.model.on('sendMessage', this.sendMessage, this);
 
                     this.render().renderToolbar().insertHeading().fetchMessages();
-                    this.createEmojiPicker();
                     u.refreshWebkit();
                     _converse.emit('chatBoxOpened', this);
                     _converse.emit('chatBoxInitialized', this);
@@ -418,6 +417,26 @@
                     )(this.renderMessage(attrs));
                 },
 
+                getLastMessageElement () {
+                    let last_msg_el = this.content.lastElementChild;
+                    while (!_.isNull(last_msg_el) &&
+                            !u.hasClass(last_msg_el, 'message') &&
+                            !u.hasClass(last_msg_el, 'chat-info')) {
+                        last_msg_el = last_msg_el.previousSibling
+                    }
+                    return last_msg_el;
+                },
+
+                getFirstMessageElement () {
+                    let first_msg_el = this.content.firstElementChild;
+                    while (!_.isNull(first_msg_el) &&
+                            !u.hasClass(first_msg_el, 'message') &&
+                            !u.hasClass(first_msg_el, 'chat-info')) {
+                        first_msg_el = first_msg_el.nextSibling
+                    }
+                    return first_msg_el;
+                },
+
                 getLastMessageDate (cutoff) {
                     /* Return the ISO8601 format date of the latest message.
                      *
@@ -425,22 +444,36 @@
                      *  (Object) cutoff: Moment Date cutoff date. The last
                      *      message received cutoff this date will be returned.
                      */
-                    if (!cutoff) {
-                        const last_msg = this.content.lastElementChild;
-                        return last_msg ? last_msg.getAttribute('data-isodate') : null
+                    const first_msg = this.getFirstMessageElement(),
+                          oldest_date = first_msg ? first_msg.getAttribute('data-isodate') : null;
+                    if (!_.isNull(oldest_date) && moment(oldest_date).isAfter(cutoff)) {
+                        return null;
+                    }
+                    const last_msg = this.getLastMessageElement(),
+                          most_recent_date = last_msg ? last_msg.getAttribute('data-isodate') : null;
+                    if (_.isNull(most_recent_date) || moment(most_recent_date).isBefore(cutoff)) {
+                        return most_recent_date;
                     }
                     const msg_dates = _.invokeMap(
-                        this.content.querySelectorAll('.message'),
-                        Element.prototype.getAttribute,
-                        'data-isodate'
+                        sizzle('.message, .chat-info', this.content),
+                        Element.prototype.getAttribute, 'data-isodate'
                     )
                     if (_.isObject(cutoff)) {
                         cutoff = cutoff.format();
                     }
                     msg_dates.push(cutoff);
                     msg_dates.sort();
-                    const idx = msg_dates.indexOf(cutoff);
-                    return msg_dates[idx === 0 ? idx : idx-1];
+                    const idx = msg_dates.lastIndexOf(cutoff);
+                    if (idx === 0) {
+                        return null;
+                    } else {
+                        return msg_dates[idx-1];
+                    }
+                },
+
+                getDayIndicatorElement (date) {
+                    return this.content.querySelector(
+                        `.chat-date[data-isodate="${date.startOf('day').format()}"]`);
                 },
 
                 showMessage (attrs) {
@@ -456,54 +489,37 @@
                      *      attributes.
                      */
                     const current_msg_date = moment(attrs.time) || moment,
-                          first_msg_el = this.content.firstElementChild,
-                          first_msg_date = first_msg_el ? first_msg_el.getAttribute('data-isodate') : null,
-                          append_element = _.bind(this.content.insertAdjacentElement, this.content, 'beforeend'),
-                          append_html = _.bind(this.content.insertAdjacentHTML, this.content, 'beforeend'),
-                          prepend_element = _.bind(this.content.insertAdjacentElement, this.content, 'afterbegin');
-
-                    if (!first_msg_date) {
-                        // This is the first received message, so we insert a
-                        // date indicator before it.
-                        this.insertDayIndicator(current_msg_date, append_html);
-                        this.insertMessage(attrs, append_element);
-                        return;
-                    }
-
-                    const last_msg_date = this.getLastMessageDate();
-                    if (current_msg_date.isAfter(last_msg_date) ||
-                            current_msg_date.isSame(last_msg_date)) {
-                        // The new message is after the last message
-                        if (current_msg_date.isAfter(last_msg_date, 'day')) {
-                            // Append a new day indicator
-                            this.insertDayIndicator(current_msg_date, append_html);
-                        }
-                        this.insertMessage(attrs, append_element);
-                        return;
-                    }
+                          prepend_html = _.bind(this.content.insertAdjacentHTML, this.content, 'afterbegin'),
+                          previous_msg_date = this.getLastMessageDate(current_msg_date);
 
-                    if (current_msg_date.isBefore(first_msg_date) ||
-                            current_msg_date.isSame(first_msg_date)) {
-                        // The message is before the first, but on the same day.
-                        // We need to prepend the message immediately before the
-                        // first message (so that it'll still be after the day
-                        // indicator).
-                        this.insertMessage(attrs, prepend_element);
-                        if (current_msg_date.isBefore(first_msg_date, 'day')) {
-                            // This message is also on a different day, so
-                            // we prepend a day indicator.
-                            this.insertDayIndicator(current_msg_date, append_html);
-                        }
-                        return;
-                    }
-                    const previous_msg_date = this.getLastMessageDate(current_msg_date);
-                    const previous_msg_el = this.content.querySelector(
-                        `.message[data-isodate="${previous_msg_date}"]`);
-                    if (_.isNull(previous_msg_el)) {
-                        this.insertMessage(attrs, prepend_element);
+                    if (_.isNull(previous_msg_date)) {
+                        this.insertMessage(attrs, _.bind(this.content.insertAdjacentElement, this.content, 'afterbegin'));
+                        this.insertDayIndicator(current_msg_date, prepend_html);
                     } else {
-                        this.insertMessage(
-                            attrs, _.bind(previous_msg_el.insertAdjacentElement, previous_msg_el, 'afterend'));
+                        const previous_msg_el = sizzle(`[data-isodate="${previous_msg_date}"]:last`, this.content).pop();
+                        const day_el = this.getDayIndicatorElement(current_msg_date)
+                        if (current_msg_date.isAfter(previous_msg_date, 'day')) {
+                            if (_.isNull(day_el)) {
+                                this.insertMessage(
+                                    attrs,
+                                    _.bind(previous_msg_el.insertAdjacentElement, previous_msg_el, 'afterend')
+                                );
+                                this.insertDayIndicator(
+                                    current_msg_date,
+                                    _.bind(this.content.insertAdjacentHTML, previous_msg_el, 'afterend')
+                                );
+                            } else {
+                                this.insertMessage(
+                                    attrs,
+                                    _.bind(previous_msg_el.insertAdjacentElement, day_el, 'afterend')
+                                );
+                            }
+                        } else {
+                            this.insertMessage(
+                                attrs,
+                                _.bind(previous_msg_el.insertAdjacentElement, previous_msg_el, 'afterend')
+                            );
+                        }
                     }
                 },
 
@@ -590,7 +606,7 @@
                     if (spinner === true) {
                         $(this.content).append(tpl_spinner);
                     } else if (spinner === false) {
-                        $(this.content).find('span.spinner').remove();
+                        this.clearSpinner();
                     }
                     return this.scrollDown();
                 },

+ 1 - 0
src/converse-headline.js

@@ -82,6 +82,7 @@
                 },
 
                 initialize () {
+                    this.markScrolled = _.debounce(this._markScrolled, 100);
                     this.disable_mam = true; // Don't do MAM queries for this box
                     this.model.messages.on('add', this.onMessageAdded, this);
                     this.model.on('show', this.show, this);