浏览代码

Turn `converse-roster` and `converse-roster-filter` into Lit elements

JC Brand 4 年之前
父节点
当前提交
d2a35d4ce1

+ 21 - 20
src/plugins/rosterview/filterview.js

@@ -1,10 +1,9 @@
 import debounce from "lodash-es/debounce";
 import tpl_roster_filter from "./templates/roster_filter.js";
-import { ElementView } from '@converse/skeletor/src/element.js';
+import { CustomElement } from 'shared/components/element.js';
 import { Model } from '@converse/skeletor/src/model.js';
 import { _converse, api } from "@converse/headless/core";
 import { initStorage } from '@converse/headless/shared/utils.js';
-import { render } from 'lit';
 
 export const RosterFilter = Model.extend({
     initialize () {
@@ -16,8 +15,12 @@ export const RosterFilter = Model.extend({
     }
 });
 
-export class RosterFilterView extends ElementView {
-    tagName = 'span';
+export class RosterFilterView extends CustomElement {
+
+    constructor () {
+        super();
+        this.initialize();
+    }
 
     initialize () {
         const model = new _converse.RosterFilter();
@@ -30,24 +33,19 @@ export class RosterFilterView extends ElementView {
             this.model.save({'filter_text': this.querySelector('.roster-filter').value});
         }, 250);
 
-        this.listenTo(this.model, 'change', this.render);
-        this.listenTo(
-            this.model,
-            'change',
-            () => this.dispatchEvent(new CustomEvent('update', { 'detail': this.model.changed }))
-        );
-
-        this.listenTo(_converse.roster, "add", this.render);
-        this.listenTo(_converse.roster, "destroy", this.render);
-        this.listenTo(_converse.roster, "remove", this.render);
-        _converse.presences.on('change:show', this.render, this);
+        this.listenTo(_converse, 'rosterContactsFetched', this.requestUpdate);
+        this.listenTo(_converse.presences, 'change:show', this.requestUpdate);
+        this.listenTo(_converse.roster, "add", this.requestUpdate);
+        this.listenTo(_converse.roster, "destroy", this.requestUpdate);
+        this.listenTo(_converse.roster, "remove", this.requestUpdate);
+        this.listenTo(this.model, 'change', this.dispatchUpdateEvent);
+        this.listenTo(this.model, 'change', this.requestUpdate);
 
         this.model.fetch();
-        this.render();
     }
 
     render () {
-        render(tpl_roster_filter(
+        return tpl_roster_filter(
             Object.assign(this.model.toJSON(), {
                 visible: this.shouldBeVisible(),
                 changeChatStateFilter: ev => this.changeChatStateFilter(ev),
@@ -55,8 +53,11 @@ export class RosterFilterView extends ElementView {
                 clearFilter: ev => this.clearFilter(ev),
                 liveFilter: ev => this.liveFilter(ev),
                 submitFilter: ev => this.submitFilter(ev),
-            })), this);
-        return this;
+            }));
+    }
+
+    dispatchUpdateEvent () {
+        this.dispatchEvent(new CustomEvent('update', { 'detail': this.model.changed }));
     }
 
     changeChatStateFilter (ev) {
@@ -96,7 +97,7 @@ export class RosterFilterView extends ElementView {
     }
 
     shouldBeVisible () {
-        return _converse.roster && _converse.roster.length >= 5 || this.isActive();
+        return _converse.roster?.length >= 5 || this.isActive();
     }
 
     clearFilter (ev) {

+ 21 - 26
src/plugins/rosterview/rosterview.js

@@ -1,9 +1,7 @@
-import debounce from 'lodash-es/debounce';
 import tpl_roster from "./templates/roster.js";
-import { ElementView } from "@converse/skeletor/src/element";
+import { CustomElement } from 'shared/components/element.js';
 import { Model } from '@converse/skeletor/src/model.js';
 import { _converse, api } from "@converse/headless/core";
-import { render } from 'lit';
 
 
 /**
@@ -11,21 +9,22 @@ import { render } from 'lit';
  * @namespace _converse.RosterView
  * @memberOf _converse
  */
-export default class RosterView extends ElementView {
+export default class RosterView extends CustomElement {
+
+    constructor () {
+        super();
+        this.initialize();
+    }
 
     async initialize () {
         await api.waitUntil('rosterInitialized')
-        this.debouncedRender = debounce(this.render, 100);
-        this.listenTo(_converse, 'rosterContactsFetched', this.render);
-        this.listenTo(_converse.roster, "add", this.debouncedRender);
-        this.listenTo(_converse.roster, "destroy", this.debouncedRender);
-        this.listenTo(_converse.roster, "remove", this.debouncedRender);
-        this.listenTo(_converse.roster, 'change', this.renderIfRelevantChange);
-        this.listenTo(_converse.roster.state, "change", this.render);
-        _converse.presences.on('change:show', () => this.debouncedRender());
-
-        this.render();
-        this.listenToRosterFilter();
+        this.listenTo(_converse, 'rosterContactsFetched', this.requestUpdate);
+        this.listenTo(_converse.presences, 'change:show', this.requestUpdate);
+        this.listenTo(_converse.roster, 'add', this.requestUpdate);
+        this.listenTo(_converse.roster, 'destroy', this.requestUpdate);
+        this.listenTo(_converse.roster, 'remove', this.requestUpdate);
+        this.listenTo(_converse.roster, 'change', this.requestUpdate);
+        this.listenTo(_converse.roster.state, 'change', this.requestUpdate);
         /**
          * Triggered once the _converse.RosterView instance has been created and initialized.
          * @event _converse#rosterViewInitialized
@@ -34,21 +33,17 @@ export default class RosterView extends ElementView {
         api.trigger('rosterViewInitialized');
     }
 
-    render () {
-        render(tpl_roster(this), this);
+    firstUpdated () {
+        this.listenToRosterFilter();
     }
 
-    renderIfRelevantChange (model) {
-        const attrs = ['ask', 'requesting', 'groups', 'num_unread'];
-        const changed = model.changed || {};
-        if (Object.keys(changed).filter(m => attrs.includes(m)).length) {
-            this.render();
-        }
+    render () {
+        return tpl_roster(this);
     }
 
     listenToRosterFilter () {
         this.filter_view = this.querySelector('converse-roster-filter');
-        this.filter_view.addEventListener('update', () => this.render());
+        this.filter_view.addEventListener('update', () => this.requestUpdate());
     }
 
     showAddContactModal (ev) { // eslint-disable-line class-methods-use-this
@@ -58,14 +53,14 @@ export default class RosterView extends ElementView {
     async syncContacts (ev) { // eslint-disable-line class-methods-use-this
         ev.preventDefault();
         this.syncing_contacts = true;
-        this.render();
+        this.requestUpdate();
 
         _converse.roster.data.save('version', null);
         await _converse.roster.fetchFromServer();
         api.user.presence.send();
 
         this.syncing_contacts = false;
-        this.render();
+        this.requestUpdate();
     }
 }
 

+ 4 - 3
src/plugins/rosterview/tests/protocol.js

@@ -150,7 +150,7 @@ describe("The Protocol", function () {
             stanza = $iq({'type': 'result', 'id': roster_fetch_stanza.getAttribute('id')});
             _converse.connection._dataRecv(mock.createRequest(stanza));
 
-            await u.waitUntil(() => _converse.roster.create.calls.count());
+            await u.waitUntil(() => _converse.roster.create.calls.count(), 1000);
 
             // A contact should now have been created
             expect(_converse.roster.get('contact@example.org') instanceof _converse.RosterContact).toBeTruthy();
@@ -206,7 +206,7 @@ describe("The Protocol", function () {
             // contact in the roster.
             await u.waitUntil(() => {
                 const header = sizzle('a:contains("Pending contacts")', rosterview).pop();
-                const contacts = Array.from(header.parentElement.querySelectorAll('li')).filter(u.isVisible);
+                const contacts = Array.from(header?.parentElement.querySelectorAll('li') ?? []).filter(u.isVisible);
                 return contacts.length;
             }, 600);
 
@@ -286,7 +286,8 @@ describe("The Protocol", function () {
             expect(u.hasClass('to', contacts[0])).toBeTruthy();
             expect(u.hasClass('both', contacts[0])).toBeFalsy();
             expect(u.hasClass('current-xmpp-contact', contacts[0])).toBeTruthy();
-            expect(contacts[0].textContent.trim()).toBe('Nicky');
+
+            await u.waitUntil(() => contacts[0].textContent.trim() === 'Nicky');
 
             expect(contact.presence.get('show')).toBe('offline');
 

+ 10 - 7
src/plugins/rosterview/tests/roster.js

@@ -139,7 +139,6 @@ describe("The Contacts Roster", function () {
         const rosterview = document.querySelector('converse-roster');
         const filter = rosterview.querySelector('.roster-filter');
         const roster = rosterview.querySelector('.roster-contacts');
-        rosterview.filter_view.delegateEvents();
 
         await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 17), 800);
         filter.value = "la";
@@ -251,7 +250,6 @@ describe("The Contacts Roster", function () {
             await mock.openControlBox(_converse);
             await mock.waitForRoster(_converse, 'current');
             const rosterview = document.querySelector('converse-roster');
-            rosterview.filter_view.delegateEvents();
             const roster = rosterview.querySelector('.roster-contacts');
 
             const button = rosterview.querySelector('span[data-type="groups"]');
@@ -299,7 +297,7 @@ describe("The Contacts Roster", function () {
             const isHidden = (el) => u.hasClass('hidden', el);
             await u.waitUntil(() => !isHidden(rosterview.querySelector('.roster-filter-form .clear-input')), 900);
             rosterview.querySelector('.clear-input').click();
-            expect(document.querySelector('.roster-filter').value).toBe("");
+            await u.waitUntil(() => document.querySelector('.roster-filter').value == '');
         }));
 
         // Disabling for now, because since recently this test consistently
@@ -460,6 +458,7 @@ describe("The Contacts Roster", function () {
 
             const contact = _converse.roster.get('groupchanger@montague.lit');
             contact.set({'groups': ['secondgroup']});
+            await u.waitUntil(() => sizzle('.roster-group[data-group="secondgroup"] a.group-toggle', rosterview).length);
             group_titles = await u.waitUntil(() => {
                 const toggles = sizzle('.roster-group[data-group="secondgroup"] a.group-toggle', rosterview);
                 if (toggles.reduce((result, t) => result && u.isVisible(t), true)) {
@@ -688,8 +687,12 @@ describe("The Contacts Roster", function () {
             // Check that they are sorted alphabetically
             const el = await u.waitUntil(() => rosterview.querySelector(`ul[data-group="Pending contacts"]`));
             const spans = el.querySelectorAll('.pending-xmpp-contact span');
-            const t = Array.from(spans).reduce((result, value) => result + value.textContent?.trim(), '');
-            expect(t).toEqual(mock.pend_names.slice(0,i+1).sort().join(''));
+
+            await u.waitUntil(
+                () => Array.from(spans).reduce((result, value) => result + value.textContent?.trim(), '') ===
+                mock.pend_names.slice(0,i+1).sort().join('')
+            );
+            expect(true).toBe(true);
         }));
     });
 
@@ -729,8 +732,8 @@ describe("The Contacts Roster", function () {
                 requesting: false,
                 subscription: 'both'
             });
-            const el = rosterview.querySelector(`ul[data-group="My contacts"]`);
-            expect(u.hasClass('collapsed', el)).toBe(true);
+            await u.waitUntil(() => u.hasClass('collapsed', rosterview.querySelector(`ul[data-group="My contacts"]`)) === true);
+            expect(true).toBe(true);
         }));
 
         it("can be added to the roster and they will be sorted alphabetically",