浏览代码

Fixes #2939

Data forms with a field named "username" are not displayed #2939
Also adds a test case for ad-hoc commands
Update to Lit 2.4.0
JC Brand 2 年之前
父节点
当前提交
21c41f9265

+ 1 - 0
CHANGES.md

@@ -19,6 +19,7 @@
 - #2879: Quotes, lines not aligned to the first line
 - #2879: Quotes, lines not aligned to the first line
 - #2925: Fix missing disco-items in browser storage.
 - #2925: Fix missing disco-items in browser storage.
 - #2936: Fix documentation about enable_smacks option, which is true by default.
 - #2936: Fix documentation about enable_smacks option, which is true by default.
+- #2939: Data forms with a field named "username" are not displayed
 - #3005: Fix MUC messages with a fallback body not rendering.
 - #3005: Fix MUC messages with a fallback body not rendering.
 - #3007: Fix links becoming text when a message is edited
 - #3007: Fix links becoming text when a message is edited
 - #3018: Fix MUC icons not functioning.
 - #3018: Fix MUC icons not functioning.

+ 23 - 23
package-lock.json

@@ -19,7 +19,7 @@
         "favico.js-slevomat": "^0.3.11",
         "favico.js-slevomat": "^0.3.11",
         "filesize": "^7.0.0",
         "filesize": "^7.0.0",
         "jed": "1.1.1",
         "jed": "1.1.1",
-        "lit": "^2.2.6",
+        "lit": "^2.4.0",
         "localforage-webextensionstorage-driver": "^3.0.0",
         "localforage-webextensionstorage-driver": "^3.0.0",
         "lodash-es": "^4.17.21",
         "lodash-es": "^4.17.21",
         "pluggable.js": "3.0.1",
         "pluggable.js": "3.0.1",
@@ -3712,9 +3712,9 @@
       }
       }
     },
     },
     "node_modules/@lit/reactive-element": {
     "node_modules/@lit/reactive-element": {
-      "version": "1.3.4",
-      "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.3.4.tgz",
-      "integrity": "sha512-I1wz4uxOA52zSBhKmv4KQWLJpCyvfpnDg+eQR6mjpRgV+Ldi14HLPpSUpJklZRldz0fFmGCC/kVmuc/3cPFqCg=="
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.4.1.tgz",
+      "integrity": "sha512-qDv4851VFSaBWzpS02cXHclo40jsbAjRXnebNXpm0uVg32kCneZPo9RYVQtrTNICtZ+1wAYHu1ZtxWSWMbKrBw=="
     },
     },
     "node_modules/@nicolo-ribaudo/chokidar-2": {
     "node_modules/@nicolo-ribaudo/chokidar-2": {
       "version": "2.1.8-no-fsevents.3",
       "version": "2.1.8-no-fsevents.3",
@@ -10909,13 +10909,13 @@
       }
       }
     },
     },
     "node_modules/lit": {
     "node_modules/lit": {
-      "version": "2.2.8",
-      "resolved": "https://registry.npmjs.org/lit/-/lit-2.2.8.tgz",
-      "integrity": "sha512-QjeNbi/H9LVIHR+u0OqsL+hs62a16m02JlJHYN48HcBuXyiPYR8JvzsTp5dYYS81l+b9Emp3UaGo82EheV0pog==",
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/lit/-/lit-2.4.0.tgz",
+      "integrity": "sha512-fdgzxEtLrZFQU/BqTtxFQCLwlZd9bdat+ltzSFjvWkZrs7eBmeX0L5MHUMb3kYIkuS8Xlfnii/iI5klirF8/Xg==",
       "dependencies": {
       "dependencies": {
-        "@lit/reactive-element": "^1.3.0",
+        "@lit/reactive-element": "^1.4.0",
         "lit-element": "^3.2.0",
         "lit-element": "^3.2.0",
-        "lit-html": "^2.2.0"
+        "lit-html": "^2.4.0"
       }
       }
     },
     },
     "node_modules/lit-element": {
     "node_modules/lit-element": {
@@ -10928,9 +10928,9 @@
       }
       }
     },
     },
     "node_modules/lit-html": {
     "node_modules/lit-html": {
-      "version": "2.2.7",
-      "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.2.7.tgz",
-      "integrity": "sha512-JhqiAwO1l03kRe68uBZ0i2x4ef2S5szY9vvP411nlrFZIpKK4/hwnhA/15bqbvxe1lV3ipBdhaOzHmyOk7QIRg==",
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.4.0.tgz",
+      "integrity": "sha512-G6qXu4JNUpY6aaF2VMfaszhO9hlWw0hOTRFDmuMheg/nDYGB+2RztUSOyrzALAbr8Nh0Y7qjhYkReh3rPnplVg==",
       "dependencies": {
       "dependencies": {
         "@types/trusted-types": "^2.0.2"
         "@types/trusted-types": "^2.0.2"
       }
       }
@@ -20548,9 +20548,9 @@
       }
       }
     },
     },
     "@lit/reactive-element": {
     "@lit/reactive-element": {
-      "version": "1.3.4",
-      "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.3.4.tgz",
-      "integrity": "sha512-I1wz4uxOA52zSBhKmv4KQWLJpCyvfpnDg+eQR6mjpRgV+Ldi14HLPpSUpJklZRldz0fFmGCC/kVmuc/3cPFqCg=="
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.4.1.tgz",
+      "integrity": "sha512-qDv4851VFSaBWzpS02cXHclo40jsbAjRXnebNXpm0uVg32kCneZPo9RYVQtrTNICtZ+1wAYHu1ZtxWSWMbKrBw=="
     },
     },
     "@nicolo-ribaudo/chokidar-2": {
     "@nicolo-ribaudo/chokidar-2": {
       "version": "2.1.8-no-fsevents.3",
       "version": "2.1.8-no-fsevents.3",
@@ -26097,13 +26097,13 @@
       }
       }
     },
     },
     "lit": {
     "lit": {
-      "version": "2.2.8",
-      "resolved": "https://registry.npmjs.org/lit/-/lit-2.2.8.tgz",
-      "integrity": "sha512-QjeNbi/H9LVIHR+u0OqsL+hs62a16m02JlJHYN48HcBuXyiPYR8JvzsTp5dYYS81l+b9Emp3UaGo82EheV0pog==",
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/lit/-/lit-2.4.0.tgz",
+      "integrity": "sha512-fdgzxEtLrZFQU/BqTtxFQCLwlZd9bdat+ltzSFjvWkZrs7eBmeX0L5MHUMb3kYIkuS8Xlfnii/iI5klirF8/Xg==",
       "requires": {
       "requires": {
-        "@lit/reactive-element": "^1.3.0",
+        "@lit/reactive-element": "^1.4.0",
         "lit-element": "^3.2.0",
         "lit-element": "^3.2.0",
-        "lit-html": "^2.2.0"
+        "lit-html": "^2.4.0"
       }
       }
     },
     },
     "lit-element": {
     "lit-element": {
@@ -26116,9 +26116,9 @@
       }
       }
     },
     },
     "lit-html": {
     "lit-html": {
-      "version": "2.2.7",
-      "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.2.7.tgz",
-      "integrity": "sha512-JhqiAwO1l03kRe68uBZ0i2x4ef2S5szY9vvP411nlrFZIpKK4/hwnhA/15bqbvxe1lV3ipBdhaOzHmyOk7QIRg==",
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.4.0.tgz",
+      "integrity": "sha512-G6qXu4JNUpY6aaF2VMfaszhO9hlWw0hOTRFDmuMheg/nDYGB+2RztUSOyrzALAbr8Nh0Y7qjhYkReh3rPnplVg==",
       "requires": {
       "requires": {
         "@types/trusted-types": "^2.0.2"
         "@types/trusted-types": "^2.0.2"
       }
       }

+ 1 - 1
package.json

@@ -111,7 +111,7 @@
     "dompurify": "^2.3.1",
     "dompurify": "^2.3.1",
     "favico.js-slevomat": "^0.3.11",
     "favico.js-slevomat": "^0.3.11",
     "jed": "1.1.1",
     "jed": "1.1.1",
-    "lit": "^2.2.6"
+    "lit": "^2.4.0"
   },
   },
   "resolutions": {
   "resolutions": {
     "autoprefixer": "10.4.5"
     "autoprefixer": "10.4.5"

+ 4 - 1
src/plugins/adhoc-views/adhoc-commands.js

@@ -85,6 +85,7 @@ export default class AdHocCommands extends CustomElement {
 
 
     hideCommandForm (ev) {
     hideCommandForm (ev) {
         ev.preventDefault();
         ev.preventDefault();
+        this.nonce = u.getUniqueId();
         this.showform = ''
         this.showform = ''
     }
     }
 
 
@@ -117,7 +118,9 @@ export default class AdHocCommands extends CustomElement {
             result = await api.sendIQ(iq);
             result = await api.sendIQ(iq);
         } catch (e) {
         } catch (e) {
             cmd.alert_type = 'danger';
             cmd.alert_type = 'danger';
-            cmd.alert = __('Sorry, an error occurred while trying to execute the command. See the developer console for details');
+            cmd.alert = __(
+                'Sorry, an error occurred while trying to execute the command. See the developer console for details'
+            );
             log.error('Error while trying to execute an ad-hoc command');
             log.error('Error while trying to execute an ad-hoc command');
             log.error(e);
             log.error(e);
         }
         }

+ 4 - 0
src/plugins/adhoc-views/templates/ad-hoc-command-form.js

@@ -5,6 +5,9 @@ export default (o, command) => {
     const i18n_hide = __('Hide');
     const i18n_hide = __('Hide');
     const i18n_run = __('Execute');
     const i18n_run = __('Execute');
     return html`
     return html`
+        <span> <!-- Don't remove this <span>,
+                    this is a workaround for a lit bug where a <form> cannot be removed
+                    if it contains an <input> with name "remove" -->
         <form @submit=${o.runCommand}>
         <form @submit=${o.runCommand}>
             ${ command.alert ? html`<div class="alert alert-${command.alert_type}" role="alert">${command.alert}</div>` : '' }
             ${ command.alert ? html`<div class="alert alert-${command.alert_type}" role="alert">${command.alert}</div>` : '' }
             <fieldset class="form-group">
             <fieldset class="form-group">
@@ -19,5 +22,6 @@ export default (o, command) => {
                 <input type="button" class="btn btn-secondary button-cancel" value="${i18n_hide}" @click=${o.hideCommandForm}>
                 <input type="button" class="btn btn-secondary button-cancel" value="${i18n_hide}" @click=${o.hideCommandForm}>
             </fieldset>
             </fieldset>
         </form>
         </form>
+        </span>
     `;
     `;
 }
 }

+ 72 - 5
src/plugins/adhoc-views/tests/adhoc.js

@@ -1,12 +1,13 @@
 /*global mock, converse */
 /*global mock, converse */
 
 
-const { sizzle, u, stx } = converse.env;
+const { Strophe, sizzle, u, stx } = converse.env;
 
 
 describe("Ad-hoc commands", function () {
 describe("Ad-hoc commands", function () {
 
 
-    fit("can be queried for via a modal", mock.initConverse([], {}, async (_converse) => {
+    it("can be queried for via a modal", mock.initConverse([], {}, async (_converse) => {
         const { api } = _converse;
         const { api } = _converse;
         const entity_jid = 'muc.montague.lit';
         const entity_jid = 'muc.montague.lit';
+        const { IQ_stanzas } = _converse.connection;
 
 
         const modal = await api.modal.show('converse-user-settings-modal');
         const modal = await api.modal.show('converse-user-settings-modal');
         await u.waitUntil(() => u.isVisible(modal));
         await u.waitUntil(() => u.isVisible(modal));
@@ -23,8 +24,8 @@ describe("Ad-hoc commands", function () {
 
 
         await mock.waitUntilDiscoConfirmed(_converse, entity_jid, [], ['http://jabber.org/protocol/commands'], [], 'info');
         await mock.waitUntilDiscoConfirmed(_converse, entity_jid, [], ['http://jabber.org/protocol/commands'], [], 'info');
 
 
-        const sel = `iq[to="${entity_jid}"] query[xmlns="http://jabber.org/protocol/disco#items"]`;
-        const iq = await u.waitUntil(() => _converse.connection.IQ_stanzas.filter(iq => sizzle(sel, iq).length).pop());
+        let sel = `iq[to="${entity_jid}"] query[xmlns="http://jabber.org/protocol/disco#items"]`;
+        let iq = await u.waitUntil(() => IQ_stanzas.filter(iq => sizzle(sel, iq).length).pop());
 
 
         _converse.connection._dataRecv(mock.createRequest(stx`
         _converse.connection._dataRecv(mock.createRequest(stx`
             <iq type="result"
             <iq type="result"
@@ -51,6 +52,9 @@ describe("Ad-hoc commands", function () {
                 <item jid="${entity_jid}"
                 <item jid="${entity_jid}"
                     node="restart"
                     node="restart"
                     name="Restart Service"/>
                     name="Restart Service"/>
+                <item jid="${entity_jid}"
+                    node="adduser"
+                    name="Add User"/>
             </query>
             </query>
         </iq>`));
         </iq>`));
 
 
@@ -58,12 +62,75 @@ describe("Ad-hoc commands", function () {
         expect(heading.textContent).toBe('Commands found:');
         expect(heading.textContent).toBe('Commands found:');
 
 
         const items = adhoc_form.querySelectorAll('.list-group-item:not(.active)');
         const items = adhoc_form.querySelectorAll('.list-group-item:not(.active)');
-        expect(items.length).toBe(6);
+        expect(items.length).toBe(7);
         expect(items[0].textContent.trim()).toBe('List Service Configurations');
         expect(items[0].textContent.trim()).toBe('List Service Configurations');
         expect(items[1].textContent.trim()).toBe('Configure Service');
         expect(items[1].textContent.trim()).toBe('Configure Service');
         expect(items[2].textContent.trim()).toBe('Reset Service Configuration');
         expect(items[2].textContent.trim()).toBe('Reset Service Configuration');
         expect(items[3].textContent.trim()).toBe('Start Service');
         expect(items[3].textContent.trim()).toBe('Start Service');
         expect(items[4].textContent.trim()).toBe('Stop Service');
         expect(items[4].textContent.trim()).toBe('Stop Service');
         expect(items[5].textContent.trim()).toBe('Restart Service');
         expect(items[5].textContent.trim()).toBe('Restart Service');
+        expect(items[6].textContent.trim()).toBe('Add User');
+
+        items[6].querySelector('a').click();
+
+        sel = `iq[to="${entity_jid}"][type="set"] command`;
+        iq = await u.waitUntil(() => IQ_stanzas.filter(iq => sizzle(sel, iq).length).pop());
+
+        expect(Strophe.serialize(iq)).toBe(
+            `<iq id="${iq.getAttribute("id")}" to="${entity_jid}" type="set" xmlns="jabber:client">`+
+                `<command action="execute" node="adduser" xmlns="http://jabber.org/protocol/commands"/>`+
+            `</iq>`
+        );
+
+        _converse.connection._dataRecv(mock.createRequest(stx`
+            <iq to="${_converse.jid}" xmlns="jabber:client" type="result" xml:lang="en" id="${iq.getAttribute('id')}" from="${entity_jid}">
+            <command status="executing" node="adduser" sessionid="1653988890.6236324-886f3dc54ce443c6b4a1805877bf7faa" xmlns="http://jabber.org/protocol/commands">
+                <actions>
+                    <complete />
+                </actions>
+                <x type="form" xmlns="jabber:x:data">
+                    <title>Title</title>
+                    <instructions>Instructions</instructions>
+                    <field type="boolean" label="Remove my registration" var="remove">
+                        <value>0</value>
+                        <required />
+                    </field>
+                    <field type="text-single" label="User name" var="username">
+                        <value>romeo</value>
+                        <required />
+                    </field>
+                    <field type="text-single" label="Password" var="password">
+                        <value>secret</value>
+                        <required />
+                    </field>
+                </x>
+            </command>
+        </iq>`));
+
+        const form = await u.waitUntil(() => adhoc_form.querySelector('form form'));
+        expect(u.isVisible(form)).toBe(true);
+        const inputs = form.querySelectorAll('input');
+        expect(inputs.length).toBe(7);
+        expect(inputs[0].getAttribute('name')).toBe('command_node');
+        expect(inputs[0].getAttribute('type')).toBe('hidden');
+        expect(inputs[0].getAttribute('value')).toBe('adduser');
+        expect(inputs[1].getAttribute('name')).toBe('command_jid');
+        expect(inputs[0].getAttribute('type')).toBe('hidden');
+        expect(inputs[1].getAttribute('value')).toBe('muc.montague.lit');
+        expect(inputs[2].getAttribute('name')).toBe('remove');
+        expect(inputs[2].getAttribute('type')).toBe('checkbox');
+        expect(inputs[3].getAttribute('name')).toBe('username');
+        expect(inputs[3].getAttribute('type')).toBe('text');
+        expect(inputs[3].getAttribute('value')).toBe('romeo');
+        expect(inputs[4].getAttribute('name')).toBe('password');
+        expect(inputs[4].getAttribute('type')).toBe('password');
+        expect(inputs[4].getAttribute('value')).toBe('secret');
+        expect(inputs[5].getAttribute('type')).toBe('submit');
+        expect(inputs[5].getAttribute('value')).toBe('Execute');
+        expect(inputs[6].getAttribute('type')).toBe('button');
+        expect(inputs[6].getAttribute('value')).toBe('Hide');
+
+        inputs[6].click();
+        await u.waitUntil(() => !u.isVisible(form));
     }));
     }));
 });
 });

+ 1 - 1
src/plugins/adhoc-views/utils.js

@@ -20,7 +20,7 @@ export async function fetchCommandForm (command) {
         command.sessionid = cmd_el.getAttribute('sessionid');
         command.sessionid = cmd_el.getAttribute('sessionid');
         command.instructions = sizzle('x[type="form"][xmlns="jabber:x:data"] instructions', cmd_el).pop()?.textContent;
         command.instructions = sizzle('x[type="form"][xmlns="jabber:x:data"] instructions', cmd_el).pop()?.textContent;
         command.fields = sizzle('x[type="form"][xmlns="jabber:x:data"] field', cmd_el)
         command.fields = sizzle('x[type="form"][xmlns="jabber:x:data"] field', cmd_el)
-            .map(f => u.xForm2TemplateResult(f, cmd_el));
+            .map(f => u.xForm2TemplateResult(f, cmd_el, { domain: jid }));
 
 
     } catch (e) {
     } catch (e) {
         if (e === null) {
         if (e === null) {

+ 10 - 3
src/utils/html.js

@@ -438,7 +438,7 @@ u.fadeIn = function (el, callback) {
  * @param { Object } options
  * @param { Object } options
  * @returns { TemplateResult }
  * @returns { TemplateResult }
  */
  */
-u.xForm2TemplateResult = function (field, stanza, options) {
+u.xForm2TemplateResult = function (field, stanza, options={}) {
     if (field.getAttribute('type') === 'list-single' || field.getAttribute('type') === 'list-multi') {
     if (field.getAttribute('type') === 'list-single' || field.getAttribute('type') === 'list-multi') {
         const values = u.queryChildren(field, 'value').map(el => el?.textContent);
         const values = u.queryChildren(field, 'value').map(el => el?.textContent);
         const options = u.queryChildren(field, 'option').map(option => {
         const options = u.queryChildren(field, 'option').map(option => {
@@ -474,8 +474,7 @@ u.xForm2TemplateResult = function (field, stanza, options) {
             'id': u.getUniqueId(),
             'id': u.getUniqueId(),
             'name': field.getAttribute('var'),
             'name': field.getAttribute('var'),
             'label': field.getAttribute('label') || '',
             'label': field.getAttribute('label') || '',
-            'checked': ((value === '1' || value === 'true') && 'checked="1"') || '',
-            'required': !!field.querySelector('required')
+            'checked': ((value === '1' || value === 'true') && 'checked="1"') || ''
         });
         });
     } else if (field.getAttribute('var') === 'url') {
     } else if (field.getAttribute('var') === 'url') {
         return tpl_form_url({
         return tpl_form_url({
@@ -491,6 +490,14 @@ u.xForm2TemplateResult = function (field, stanza, options) {
             'value': field.querySelector('value')?.textContent,
             'value': field.querySelector('value')?.textContent,
             'required': !!field.querySelector('required')
             'required': !!field.querySelector('required')
         });
         });
+    } else if (field.getAttribute('var') === 'password') {
+        return tpl_form_input({
+            'name': field.getAttribute('var'),
+            'type': 'password',
+            'label': field.getAttribute('label') || '',
+            'value': field.querySelector('value')?.textContent,
+            'required': !!field.querySelector('required')
+        });
     } else if (field.getAttribute('var') === 'ocr') {
     } else if (field.getAttribute('var') === 'ocr') {
         // Captcha
         // Captcha
         const uri = field.querySelector('uri');
         const uri = field.querySelector('uri');