浏览代码

Bugfix. Don't send empty `group` element when adding a contact

JC Brand 4 月之前
父节点
当前提交
748ad470f9

+ 10 - 4
src/headless/plugins/roster/contacts.js

@@ -1,13 +1,13 @@
+import { Collection, Model } from "@converse/skeletor";
 import RosterContact from './contact.js';
 import _converse from '../../shared/_converse.js';
 import api from '../../shared/api/index.js';
 import converse from '../../shared/api/public.js';
 import log from "../../log.js";
-import { Collection, Model } from "@converse/skeletor";
 import { initStorage } from '../../utils/storage.js';
 import { rejectPresenceSubscription } from './utils.js';
 
-const { Strophe, $iq, sizzle, u } = converse.env;
+const { Strophe, $iq, sizzle, stx, u, Stanza } = converse.env;
 
 class RosterContacts extends Collection {
     constructor () {
@@ -147,8 +147,14 @@ class RosterContacts extends Collection {
     sendContactAddIQ (attributes) {
         const { jid, groups } = attributes;
         const name = attributes.name ? attributes.name : null;
-        const iq = $iq({ 'type': 'set' }).c('query', { 'xmlns': Strophe.NS.ROSTER }).c('item', { jid, name });
-        groups?.forEach((g) => iq.c('group').t(g).up());
+        const iq = stx`
+            <iq type="set" xmlns="jabber:client">
+                <query xmlns="${Strophe.NS.ROSTER}">
+                    <item jid="${jid}" ${name ? Stanza.unsafeXML(`name="${Strophe.xmlescape(name)}"`) : ""}>
+                        ${groups?.map(/** @param {string} g */(g) => stx`<group>${g}</group>`)}
+                    </item>
+                </query>
+            </iq>`;
         return api.sendIQ(iq);
     }
 

+ 2 - 1
src/headless/shared/api/public.js

@@ -5,7 +5,7 @@ import { sprintf } from 'sprintf-js';
 import dayjs from 'dayjs';
 import sizzle from 'sizzle';
 import URI from 'urijs';
-import { Strophe, $build, $iq, $msg, $pres, stx } from 'strophe.js';
+import { Stanza, Strophe, $build, $iq, $msg, $pres, stx } from 'strophe.js';
 import { Collection, Model } from "@converse/skeletor";
 import { filesize } from 'filesize';
 import { html } from 'lit';
@@ -191,6 +191,7 @@ const converse = Object.assign(/** @type {ConversePrivateGlobal} */(window).conv
         Collection,
         Model,
         Promise,
+        Stanza,
         Strophe,
         TimeoutError,
         URI,

+ 5 - 2
src/plugins/rosterview/modals/accept-contact-request.js

@@ -5,7 +5,6 @@ import tplAcceptContactRequest from "./templates/accept-contact-request.js";
 import { __ } from "i18n";
 
 export default class AcceptContactRequest extends BaseModal {
-
     /**
      * @param {Object} options
      */
@@ -42,7 +41,11 @@ export default class AcceptContactRequest extends BaseModal {
         const form = /** @type {HTMLFormElement} */ (ev.target);
         const data = new FormData(form);
         const name = /** @type {string} */ (data.get("name") || "").trim();
-        const groups = /** @type {string} */(data.get('groups'))?.split(',').map((g) => g.trim()) || [];
+        const groups =
+            /** @type {string} */ (data.get("groups"))
+                ?.split(",")
+                .map((g) => g.trim())
+                .filter((g) => g) || [];
         try {
             await _converse.state.roster.sendContactAddIQ({
                 jid: this.contact.get("jid"),

+ 5 - 1
src/plugins/rosterview/modals/add-contact.js

@@ -81,7 +81,11 @@ export default class AddContactModal extends BaseModal {
         }
 
         if (this.validateSubmission(jid)) {
-            const groups = /** @type {string} */(data.get('groups'))?.split(',').map((g) => g.trim()) || [];
+            const groups =
+                /** @type {string} */ (data.get("groups"))
+                    ?.split(",")
+                    .map((g) => g.trim())
+                    .filter((g) => g) || [];
             this.afterSubmission(form, jid, name, groups);
         }
     }

+ 1 - 1
src/plugins/rosterview/modals/templates/add-contact.js

@@ -50,7 +50,7 @@ export default (el) => {
                     <label class="form-label clearfix" for="name">${i18n_nickname}:</label>
                     ${api.settings.get('autocomplete_add_contact') && typeof api.settings.get('xhr_user_search_url') === 'string' ?
                         html`<converse-autocomplete
-                            .getAutoCompleteList=${getNamesAutoCompleteList}
+                            .getAutoCompleteList=${(query) => getNamesAutoCompleteList(query, 'fullname')}
                             filter=${FILTER_STARTSWITH}
                             value="${el.model.get('nickname') || ''}"
                             name="name"></converse-autocomplete>` :

+ 18 - 16
src/plugins/rosterview/tests/add-contact-modal.js

@@ -39,7 +39,8 @@ describe("The 'Add Contact' widget", function () {
         modal.querySelector('button[type="submit"]').click();
 
         const sent_IQs = _converse.api.connection.get().IQ_stanzas;
-        const sent_stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`)).pop());
+        const sent_stanza = await u.waitUntil(() => sent_IQs.filter(
+            iq => sizzle(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`, iq).length).pop());
         expect(sent_stanza).toEqualStanza(stx`
             <iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">
                 <query xmlns="jabber:iq:roster">
@@ -72,11 +73,10 @@ describe("The 'Add Contact' widget", function () {
         const sent_stanza = await u.waitUntil(
             () => IQ_stanzas.filter(s => sizzle(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`, s).length).pop()
         );
-        expect(Strophe.serialize(sent_stanza)).toEqual(
-            `<iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
-                `<query xmlns="jabber:iq:roster"><item jid="someone@montague.lit"><group></group></item></query>`+
-            `</iq>`
-        );
+        expect(sent_stanza).toEqualStanza(stx`
+            <iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">
+                <query xmlns="jabber:iq:roster"><item jid="someone@montague.lit"/></query>
+            </iq>`);
     }));
 
     it("integrates with xhr_user_search_url to search for contacts",
@@ -99,7 +99,6 @@ describe("The 'Add Contact' widget", function () {
         const modal = _converse.api.modal.get('converse-add-contact-modal');
         await u.waitUntil(() => u.isVisible(modal), 1000);
 
-        // TODO: We only have autocomplete for the name input
 
         const input_el = modal.querySelector('input[name="name"]');
         input_el.value = 'marty';
@@ -108,12 +107,14 @@ describe("The 'Add Contact' widget", function () {
         expect(modal.querySelectorAll('.suggestion-box li').length).toBe(1);
         const suggestion = modal.querySelector('.suggestion-box li');
         expect(suggestion.textContent).toBe('Marty McFly');
-                return;
 
-        // Mock selection
-        modal.name_auto_complete.select(suggestion);
+        const el = u.ancestor(suggestion, 'converse-autocomplete');
+        el.auto_complete.select(suggestion);
+
+        expect(input_el.value.trim()).toBe('Marty McFly');
+        // TODO: We only have autocomplete for the name input
+        return;
 
-        expect(input_el.value).toBe('Marty McFly');
         expect(modal.querySelector('input[name="jid"]').value).toBe('marty@mcfly.net');
         modal.querySelector('button[type="submit"]').click();
 
@@ -183,10 +184,11 @@ describe("The 'Add Contact' widget", function () {
         modal.querySelector('button[type="submit"]').click();
 
         const sent_IQs = _converse.api.connection.get().IQ_stanzas;
-        const sent_stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`)).pop());
-        expect(Strophe.serialize(sent_stanza)).toEqual(
-        `<iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
-            `<query xmlns="jabber:iq:roster"><item jid="marty@mcfly.net" name="Marty McFly"><group></group></item></query>`+
-        `</iq>`);
+        const sent_stanza = await u.waitUntil(() => sent_IQs.filter(
+            iq => sizzle(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`, iq).length).pop());
+        expect(sent_stanza).toEqualStanza(stx`
+            <iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">
+                <query xmlns="jabber:iq:roster"><item jid="marty@mcfly.net" name="Marty McFly"></item></query>
+            </iq>`);
     }));
 });

+ 5 - 2
src/plugins/rosterview/utils.js

@@ -300,7 +300,7 @@ export function getJIDsAutoCompleteList() {
 /**
  * @param {string} query
  */
-export async function getNamesAutoCompleteList(query) {
+export async function getNamesAutoCompleteList(query, value_attr='jid') {
     const options = {
         'mode': /** @type {RequestMode} */ ('cors'),
         'headers': {
@@ -322,5 +322,8 @@ export async function getNamesAutoCompleteList(query) {
         log.error(`Invalid JSON returned"`);
         return [];
     }
-    return json.map((i) => ({ 'label': i.fullname || i.jid, 'value': i.jid }));
+    return json.map((i) => ({
+        label: i.fullname || i.jid,
+        value: i[value_attr]
+    }));
 }

+ 1 - 1
src/types/plugins/rosterview/utils.d.ts

@@ -64,7 +64,7 @@ export function getJIDsAutoCompleteList(): any[];
 /**
  * @param {string} query
  */
-export function getNamesAutoCompleteList(query: string): Promise<{
+export function getNamesAutoCompleteList(query: string, value_attr?: string): Promise<{
     label: any;
     value: any;
 }[]>;