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
This commit is contained in:
JC Brand 2022-10-19 09:46:43 +02:00
parent 83351fb98f
commit 21c41f9265
8 changed files with 116 additions and 34 deletions

View File

@ -19,6 +19,7 @@
- #2879: Quotes, lines not aligned to the first line
- #2925: Fix missing disco-items in browser storage.
- #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.
- #3007: Fix links becoming text when a message is edited
- #3018: Fix MUC icons not functioning.

46
package-lock.json generated
View File

@ -19,7 +19,7 @@
"favico.js-slevomat": "^0.3.11",
"filesize": "^7.0.0",
"jed": "1.1.1",
"lit": "^2.2.6",
"lit": "^2.4.0",
"localforage-webextensionstorage-driver": "^3.0.0",
"lodash-es": "^4.17.21",
"pluggable.js": "3.0.1",
@ -3712,9 +3712,9 @@
}
},
"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": {
"version": "2.1.8-no-fsevents.3",
@ -10909,13 +10909,13 @@
}
},
"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": {
"@lit/reactive-element": "^1.3.0",
"@lit/reactive-element": "^1.4.0",
"lit-element": "^3.2.0",
"lit-html": "^2.2.0"
"lit-html": "^2.4.0"
}
},
"node_modules/lit-element": {
@ -10928,9 +10928,9 @@
}
},
"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": {
"@types/trusted-types": "^2.0.2"
}
@ -20548,9 +20548,9 @@
}
},
"@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": {
"version": "2.1.8-no-fsevents.3",
@ -26097,13 +26097,13 @@
}
},
"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": {
"@lit/reactive-element": "^1.3.0",
"@lit/reactive-element": "^1.4.0",
"lit-element": "^3.2.0",
"lit-html": "^2.2.0"
"lit-html": "^2.4.0"
}
},
"lit-element": {
@ -26116,9 +26116,9 @@
}
},
"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": {
"@types/trusted-types": "^2.0.2"
}

View File

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

View File

@ -85,6 +85,7 @@ export default class AdHocCommands extends CustomElement {
hideCommandForm (ev) {
ev.preventDefault();
this.nonce = u.getUniqueId();
this.showform = ''
}
@ -117,7 +118,9 @@ export default class AdHocCommands extends CustomElement {
result = await api.sendIQ(iq);
} catch (e) {
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(e);
}

View File

@ -5,6 +5,9 @@ export default (o, command) => {
const i18n_hide = __('Hide');
const i18n_run = __('Execute');
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}>
${ command.alert ? html`<div class="alert alert-${command.alert_type}" role="alert">${command.alert}</div>` : '' }
<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}>
</fieldset>
</form>
</span>
`;
}

View File

@ -1,12 +1,13 @@
/*global mock, converse */
const { sizzle, u, stx } = converse.env;
const { Strophe, sizzle, u, stx } = converse.env;
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 entity_jid = 'muc.montague.lit';
const { IQ_stanzas } = _converse.connection;
const modal = await api.modal.show('converse-user-settings-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');
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`
<iq type="result"
@ -51,6 +52,9 @@ describe("Ad-hoc commands", function () {
<item jid="${entity_jid}"
node="restart"
name="Restart Service"/>
<item jid="${entity_jid}"
node="adduser"
name="Add User"/>
</query>
</iq>`));
@ -58,12 +62,75 @@ describe("Ad-hoc commands", function () {
expect(heading.textContent).toBe('Commands found:');
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[1].textContent.trim()).toBe('Configure Service');
expect(items[2].textContent.trim()).toBe('Reset Service Configuration');
expect(items[3].textContent.trim()).toBe('Start Service');
expect(items[4].textContent.trim()).toBe('Stop 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));
}));
});

View File

@ -20,7 +20,7 @@ export async function fetchCommandForm (command) {
command.sessionid = cmd_el.getAttribute('sessionid');
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)
.map(f => u.xForm2TemplateResult(f, cmd_el));
.map(f => u.xForm2TemplateResult(f, cmd_el, { domain: jid }));
} catch (e) {
if (e === null) {

View File

@ -438,7 +438,7 @@ u.fadeIn = function (el, callback) {
* @param { Object } options
* @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') {
const values = u.queryChildren(field, 'value').map(el => el?.textContent);
const options = u.queryChildren(field, 'option').map(option => {
@ -474,8 +474,7 @@ u.xForm2TemplateResult = function (field, stanza, options) {
'id': u.getUniqueId(),
'name': field.getAttribute('var'),
'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') {
return tpl_form_url({
@ -491,6 +490,14 @@ u.xForm2TemplateResult = function (field, stanza, options) {
'value': field.querySelector('value')?.textContent,
'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') {
// Captcha
const uri = field.querySelector('uri');