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

This commit is contained in:
JC Brand 2021-07-15 16:36:42 +02:00
parent 8de4671603
commit d2a35d4ce1
4 changed files with 56 additions and 56 deletions

View File

@ -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) {

View File

@ -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();
}
}

View File

@ -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');

View File

@ -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",