converse.js
Version:
Browser based XMPP chat client
277 lines (251 loc) • 13.5 kB
JavaScript
const { sizzle, u } = converse.env;
describe('Requesting Contacts', function () {
beforeAll(() => jasmine.addMatchers({ toEqualStanza: jasmine.toEqualStanza }));
it(
'can be added to the roster and they will be sorted alphabetically',
mock.initConverse([], {}, async function (_converse) {
await mock.waitForRoster(_converse, 'current', 0);
await mock.openControlBox(_converse);
let names = [];
const addName = function (item) {
if (!u.hasClass('request-actions', item)) {
names.push(item.textContent.replace(/^\s+|\s+$/g, ''));
}
};
const rosterview = document.querySelector('converse-roster');
await Promise.all(
mock.req_names.map((name) => {
const contact = _converse.roster.create({
jid: name.replace(/ /g, '.').toLowerCase() + '@montague.lit',
subscription: 'none',
ask: null,
requesting: true,
nickname: name,
});
return u.waitUntil(() => contact.initialized);
})
);
await u.waitUntil(() => rosterview.querySelectorAll(`ul[data-group="Contact requests"] li`).length, 700);
// Check that they are sorted alphabetically
const children = rosterview.querySelectorAll(
`ul[data-group="Contact requests"] .requesting-xmpp-contact .contact-name`
);
names = [];
Array.from(children).forEach(addName);
expect(names.join('')).toEqual(
mock.req_names
.slice(0, mock.req_names.length + 1)
.sort()
.join('')
);
})
);
it(
'can have their requests accepted via a dropdown in the roster',
mock.initConverse([], { lazy_load_vcards: false }, async function (_converse) {
await mock.openControlBox(_converse);
await mock.waitForRoster(_converse, 'current', 0);
await mock.createContacts(_converse, 'requesting', 1);
const roster_el = document.querySelector('converse-roster');
const accept_btn = roster_el.querySelector('.dropdown-item.accept-xmpp-request');
accept_btn.click();
await u.waitUntil(() => document.querySelector('converse-accept-contact-request-modal'));
})
);
it(
'can have their requests declined via a dropdown in the roster',
mock.initConverse([], {}, async function (_converse) {
await mock.waitUntilDiscoConfirmed(
_converse,
_converse.domain,
[{ 'category': 'server', 'type': 'IM' }],
['urn:xmpp:blocking']
);
await mock.waitForRoster(_converse, 'current', 0);
await mock.openControlBox(_converse);
await mock.createContacts(_converse, 'requesting', 1);
const name = mock.req_names.sort()[0];
const jid = name.replace(/ /g, '.').toLowerCase() + '@montague.lit';
const { roster } = _converse;
const contact = roster.get(jid);
spyOn(_converse.api, 'confirm').and.returnValue(Promise.resolve(true));
spyOn(contact, 'unauthorize').and.callFake(function () {
return contact;
});
const roster_el = document.querySelector('converse-roster');
const decline_btn = roster_el.querySelector('.dropdown-item.decline-xmpp-request');
decline_btn.click();
await u.waitUntil(() => _converse.api.confirm.calls.count);
await u.waitUntil(() => contact.unauthorize.calls.count());
})
);
it(
"do not have a header if there aren't any",
mock.initConverse([], { show_self_in_roster: false }, async function (_converse) {
await mock.openControlBox(_converse);
await mock.waitForRoster(_converse, 'current', 0);
await mock.waitUntilDiscoConfirmed(
_converse,
_converse.domain,
[{ 'category': 'server', 'type': 'IM' }],
['urn:xmpp:blocking']
);
const name = mock.req_names[0];
spyOn(_converse.api, 'confirm').and.returnValue(Promise.resolve(true));
_converse.roster.create({
'jid': name.replace(/ /g, '.').toLowerCase() + '@montague.lit',
'subscription': 'none',
'ask': null,
'requesting': true,
'nickname': name,
});
const rosterview = document.querySelector('converse-roster');
await u.waitUntil(() => sizzle('.roster-group', rosterview).filter(u.isVisible).length, 900);
expect(u.isVisible(rosterview.querySelector(`ul[data-group="Contact requests"]`))).toEqual(true);
expect(
sizzle('.roster-group', rosterview)
.filter(u.isVisible)
.map((e) => e.querySelector('li')).length
).toBe(1);
sizzle('.roster-group', rosterview)
.filter(u.isVisible)
.map((e) => e.querySelector('li .decline-xmpp-request'))[0]
.click();
await u.waitUntil(() => _converse.api.confirm.calls.count);
await u.waitUntil(() => rosterview.querySelector(`ul[data-group="Contact requests"]`) === null);
})
);
it(
'can be collapsed under their own header',
mock.initConverse([], {}, async function (_converse) {
await mock.waitForRoster(_converse, 'current', 0);
mock.createContacts(_converse, 'requesting');
await mock.openControlBox(_converse);
const rosterview = document.querySelector('converse-roster');
await u.waitUntil(() => sizzle('.roster-group', rosterview).filter(u.isVisible).length, 700);
const el = await u.waitUntil(() => rosterview.querySelector(`ul[data-group="Contact requests"]`));
await mock.checkHeaderToggling.apply(_converse, [el.parentElement]);
})
);
it(
'can have their requests accepted by the user',
mock.initConverse([], { lazy_load_vcards: false }, async function (_converse) {
await mock.openControlBox(_converse);
await mock.waitForRoster(_converse, 'current', 0);
await mock.createContacts(_converse, 'requesting');
const name = mock.req_names.sort()[0];
const jid = name.replace(/ /g, '.').toLowerCase() + '@montague.lit';
const { api, roster } = _converse;
const contact = roster.get(jid);
spyOn(contact, 'authorize').and.callThrough();
const rosterview = document.querySelector('converse-roster');
await u.waitUntil(() => rosterview.querySelectorAll('.roster-group li').length);
const req_contact = sizzle(`.contact-name:contains("${contact.getDisplayName()}")`, rosterview).pop();
req_contact.parentElement.parentElement.querySelector('.accept-xmpp-request').click();
const modal = _converse.api.modal.get('converse-accept-contact-request-modal');
await u.waitUntil(() => u.isVisible(modal), 1000);
expect(modal.querySelector('input[name="name"]')?.value).toBe('Escalus, prince of Verona');
const groups_input = modal.querySelector('input[name="groups"]');
groups_input.value = 'Princes, Veronese';
const sent_stanzas = _converse.api.connection.get().sent_stanzas;
while (sent_stanzas.length) sent_stanzas.pop();
modal.querySelector('button[type="submit"]').click();
let stanza = await u.waitUntil(() => sent_stanzas.filter((s) => s.matches('iq[type="set"]')).pop());
expect(stanza).toEqualStanza(
stx`<iq type="set" xmlns="jabber:client" id="${stanza.getAttribute('id')}">
<query xmlns="jabber:iq:roster">
<item jid="${contact.get('jid')}" name="Escalus, prince of Verona">
<group>Princes</group>
<group>Veronese</group>
</item>
</query>
</iq>`
);
const result = stx`
<iq to="${api.connection.get().jid}" type="result" id="${stanza.getAttribute('id')}" xmlns="jabber:client"/>`;
api.connection.get()._dataRecv(mock.createRequest(result));
stanza = await u.waitUntil(() =>
sent_stanzas.filter((s) => s.matches('presence[type="subscribed"]')).pop()
);
expect(stanza).toEqualStanza(
stx`<presence to="${contact.get('jid')}" type="subscribed" xmlns="jabber:client"/>`
);
await u.waitUntil(() => contact.authorize.calls.count());
expect(contact.authorize).toHaveBeenCalled();
expect(contact.get('groups')).toEqual(['Princes', 'Veronese']);
})
);
it(
'can have their requests denied by the user',
mock.initConverse([], {}, async function (_converse) {
await mock.waitUntilDiscoConfirmed(
_converse,
_converse.domain,
[{ 'category': 'server', 'type': 'IM' }],
['urn:xmpp:blocking']
);
await mock.waitForRoster(_converse, 'current', 0);
await mock.createContacts(_converse, 'requesting');
await mock.openControlBox(_converse);
const rosterview = document.querySelector('converse-roster');
await u.waitUntil(() => sizzle('.roster-group li', rosterview).length, 700);
const name = mock.req_names.sort()[1];
const jid = name.replace(/ /g, '.').toLowerCase() + '@montague.lit';
const contact = _converse.roster.get(jid);
spyOn(_converse.api, 'confirm').and.returnValue(Promise.resolve(true));
spyOn(contact, 'unauthorize').and.callFake(function () {
return contact;
});
const req_contact = await u.waitUntil(() =>
sizzle(".contact-name:contains('" + name + "')", rosterview).pop()
);
req_contact.parentElement.parentElement.querySelector('.decline-xmpp-request').click();
await u.waitUntil(() => _converse.api.confirm.calls.count);
await u.waitUntil(() => contact.unauthorize.calls.count());
// There should now be one less contact
expect(_converse.roster.length).toEqual(mock.req_names.length - 1);
})
);
it(
"are persisted even if other contacts' change their presence ",
mock.initConverse([], {}, async function (_converse) {
await mock.openControlBox(_converse);
const { IQ_stanzas } = _converse.api.connection.get();
const stanza = await u.waitUntil(() =>
IQ_stanzas.filter((iq) => sizzle('iq query[xmlns="jabber:iq:roster"]', iq).length).pop()
);
// Taken from the spec
// https://xmpp.org/rfcs/rfc3921.html#rfc.section.7.3
const result = stx`
<iq to="${_converse.api.connection.get().jid}" type="result" id="${stanza.getAttribute('id')}" xmlns="jabber:client">
<query xmlns="jabber:iq:roster">
<item jid="juliet@example.net" name="Juliet" subscription="both">
<group>Friends</group>
</item>
<item jid="mercutio@example.org" name="Mercutio" subscription="from">
<group>Friends</group>
</item>
</query>
</iq>`;
_converse.api.connection.get()._dataRecv(mock.createRequest(result));
const pres = $pres({ from: 'data@enterprise/resource', type: 'subscribe' });
_converse.api.connection.get()._dataRecv(mock.createRequest(pres));
expect(_converse.roster.pluck('jid').length).toBe(1);
const rosterview = document.querySelector('converse-roster');
await u.waitUntil(() => sizzle('a:contains("Contact requests")', rosterview).length, 700);
expect(_converse.roster.pluck('jid').includes('data@enterprise')).toBeTruthy();
const roster_push = stx`
<iq type="set" to="${_converse.api.connection.get().jid}" xmlns="jabber:client">
<query xmlns="jabber:iq:roster" ver="ver34">
<item jid="benvolio@example.org" name="Benvolio" subscription="both">
<group>Friends</group>
</item>
</query>
</iq>`;
_converse.api.connection.get()._dataRecv(mock.createRequest(roster_push));
expect(_converse.roster.data.get('version')).toBe('ver34');
expect(_converse.roster.models.length).toBe(4);
expect(_converse.roster.pluck('jid').includes('data@enterprise')).toBeTruthy();
})
);
});