Use our own confirm dialog consistently

This commit is contained in:
JC Brand 2022-05-11 10:01:18 +02:00
parent a3c0f90476
commit a57853156e
18 changed files with 87 additions and 72 deletions

View File

@ -12,6 +12,7 @@ module.exports = function(config) {
{ pattern: 'dist/*.css.map', included: false }, { pattern: 'dist/*.css.map', included: false },
{ pattern: "dist/icons.js", served: true }, { pattern: "dist/icons.js", served: true },
{ pattern: "dist/emojis.js", served: true }, { pattern: "dist/emojis.js", served: true },
"src/shared/tests/tests.css",
"node_modules/lodash/lodash.min.js", "node_modules/lodash/lodash.min.js",
"dist/converse.js", "dist/converse.js",
"dist/converse.css", "dist/converse.css",

View File

@ -102,9 +102,9 @@ export async function onDirectMUCInvitation (message) {
let contact = _converse.roster.get(from); let contact = _converse.roster.get(from);
contact = contact ? contact.getDisplayName() : from; contact = contact ? contact.getDisplayName() : from;
if (!reason) { if (!reason) {
result = confirm(__('%1$s has invited you to join a groupchat: %2$s', contact, room_jid)); result = await api.confirm(__('%1$s has invited you to join a groupchat: %2$s', contact, room_jid));
} else { } else {
result = confirm( result = await api.confirm(
__( __(
'%1$s has invited you to join a groupchat: %2$s, and left the following reason: "%3$s"', '%1$s has invited you to join a groupchat: %2$s, and left the following reason: "%3$s"',
contact, contact,
@ -114,7 +114,7 @@ export async function onDirectMUCInvitation (message) {
); );
} }
} }
if (result === true) { if (result) {
const chatroom = await openChatRoom(room_jid, { 'password': x_el.getAttribute('password') }); const chatroom = await openChatRoom(room_jid, { 'password': x_el.getAttribute('password') });
if (chatroom.session.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED) { if (chatroom.session.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED) {
_converse.chatboxes.get(room_jid).rejoin(); _converse.chatboxes.get(room_jid).rejoin();

View File

@ -19,7 +19,8 @@ describe("The User Details Modal", function () {
show_modal_button.click(); show_modal_button.click();
const modal = _converse.api.modal.get('user-details-modal'); const modal = _converse.api.modal.get('user-details-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000); await u.waitUntil(() => u.isVisible(modal.el), 1000);
spyOn(window, 'confirm').and.returnValue(true); spyOn(_converse.api, 'confirm').and.returnValue(Promise.resolve(true));
spyOn(view.model.contact, 'removeFromRoster').and.callFake(callback => callback()); spyOn(view.model.contact, 'removeFromRoster').and.callFake(callback => callback());
let remove_contact_button = modal.el.querySelector('button.remove-contact'); let remove_contact_button = modal.el.querySelector('button.remove-contact');
expect(u.isVisible(remove_contact_button)).toBeTruthy(); expect(u.isVisible(remove_contact_button)).toBeTruthy();
@ -45,7 +46,7 @@ describe("The User Details Modal", function () {
show_modal_button.click(); show_modal_button.click();
let modal = _converse.api.modal.get('user-details-modal'); let modal = _converse.api.modal.get('user-details-modal');
await u.waitUntil(() => u.isVisible(modal.el), 2000); await u.waitUntil(() => u.isVisible(modal.el), 2000);
spyOn(window, 'confirm').and.returnValue(true); spyOn(_converse.api, 'confirm').and.returnValue(Promise.resolve(true));
spyOn(view.model.contact, 'removeFromRoster').and.callFake((callback, errback) => errback()); spyOn(view.model.contact, 'removeFromRoster').and.callFake((callback, errback) => errback());
let remove_contact_button = modal.el.querySelector('button.remove-contact'); let remove_contact_button = modal.el.querySelector('button.remove-contact');

View File

@ -83,11 +83,11 @@ const UserDetailsModal = BootstrapModal.extend({
u.removeClass('fa-spin', refresh_icon); u.removeClass('fa-spin', refresh_icon);
}, },
removeContact (ev) { async removeContact (ev) {
ev?.preventDefault?.(); ev?.preventDefault?.();
if (!api.settings.get('allow_contact_removal')) { return; } if (!api.settings.get('allow_contact_removal')) { return; }
const result = confirm(__("Are you sure you want to remove this contact?")); const result = await api.confirm(__("Are you sure you want to remove this contact?"));
if (result === true) { if (result) {
// XXX: The `dismissHandler` in bootstrap.native tries to // XXX: The `dismissHandler` in bootstrap.native tries to
// reference the remove button after it's been cleared from // reference the remove button after it's been cleared from
// the DOM, so we delay removing the contact to give it time. // the DOM, so we delay removing the contact to give it time.

View File

@ -557,9 +557,9 @@ describe("Bookmarks", function () {
expect(els[3].textContent).toBe("noname@conference.shakespeare.lit"); expect(els[3].textContent).toBe("noname@conference.shakespeare.lit");
expect(els[4].textContent).toBe("The Play's the Thing"); expect(els[4].textContent).toBe("The Play's the Thing");
spyOn(window, 'confirm').and.returnValue(true); spyOn(_converse.api, 'confirm').and.callFake(() => Promise.resolve(true));
document.querySelector('#chatrooms .bookmarks.rooms-list .room-item:nth-child(2) a:nth-child(2)').click(); document.querySelector('#chatrooms .bookmarks.rooms-list .room-item:nth-child(2) a:nth-child(2)').click();
expect(window.confirm).toHaveBeenCalled(); expect(_converse.api.confirm).toHaveBeenCalled();
await u.waitUntil(() => document.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item').length === 4) await u.waitUntil(() => document.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item').length === 4)
els = document.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item a.list-item-link'); els = document.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item a.list-item-link');
expect(els[0].textContent).toBe("1st Bookmark"); expect(els[0].textContent).toBe("1st Bookmark");

View File

@ -25,11 +25,12 @@ export function getHeadingButtons (view, buttons) {
return buttons; return buttons;
} }
export function removeBookmarkViaEvent (ev) { export async function removeBookmarkViaEvent (ev) {
ev.preventDefault(); ev.preventDefault();
const name = ev.target.getAttribute('data-bookmark-name'); const name = ev.target.getAttribute('data-bookmark-name');
const jid = ev.target.getAttribute('data-room-jid'); const jid = ev.target.getAttribute('data-room-jid');
if (confirm(__('Are you sure you want to remove the bookmark "%1$s"?', name))) { const result = await api.confirm(__('Are you sure you want to remove the bookmark "%1$s"?', name));
if (result) {
invokeMap(_converse.bookmarks.where({ jid }), Model.prototype.destroy); invokeMap(_converse.bookmarks.where({ jid }), Model.prototype.destroy);
} }
} }

View File

@ -49,7 +49,7 @@ describe("Chatboxes", function () {
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid); await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.chatboxviews.get(contact_jid);
spyOn(window, 'confirm').and.returnValue(true); spyOn(_converse.api, 'confirm').and.callFake(() => Promise.resolve(true));
for (const i of Array(10).keys()) { for (const i of Array(10).keys()) {
mock.sendMessage(view, `Message ${i}`); mock.sendMessage(view, `Message ${i}`);
@ -64,7 +64,7 @@ describe("Chatboxes", function () {
preventDefault: function preventDefault () {}, preventDefault: function preventDefault () {},
keyCode: 13 // Enter keyCode: 13 // Enter
}); });
await u.waitUntil(() => window.confirm.calls.count() === 1); await u.waitUntil(() => _converse.api.confirm.calls.count() === 1);
await u.waitUntil(() => sizzle('converse-chat-message', view).length === 0); await u.waitUntil(() => sizzle('converse-chat-message', view).length === 0);
expect(true).toBe(true); expect(true).toBe(true);
})); }));
@ -916,15 +916,15 @@ describe("Chatboxes", function () {
message = '/clear'; message = '/clear';
const message_form = view.querySelector('converse-message-form'); const message_form = view.querySelector('converse-message-form');
spyOn(window, 'confirm').and.callFake(() => true); spyOn(_converse.api, 'confirm').and.callFake(() => Promise.resolve(true));
view.querySelector('.chat-textarea').value = message; view.querySelector('.chat-textarea').value = message;
message_form.onKeyDown({ message_form.onKeyDown({
target: view.querySelector('textarea.chat-textarea'), target: view.querySelector('textarea.chat-textarea'),
preventDefault: function preventDefault () {}, preventDefault: function preventDefault () {},
keyCode: 13 keyCode: 13
}); });
await u.waitUntil(() => window.confirm.calls.count() === 1); await u.waitUntil(() => _converse.api.confirm.calls.count() === 1);
expect(window.confirm).toHaveBeenCalledWith('Are you sure you want to clear the messages from this conversation?'); expect(_converse.api.confirm).toHaveBeenCalledWith('Are you sure you want to clear the messages from this conversation?');
await u.waitUntil(() => view.model.messages.length === 0); await u.waitUntil(() => view.model.messages.length === 0);
await u.waitUntil(() => !view.querySelectorAll('.chat-msg__body').length); await u.waitUntil(() => !view.querySelectorAll('.chat-msg__body').length);
})); }));

View File

@ -268,23 +268,26 @@ describe("A Chat Message", function () {
expect(view.querySelectorAll('.chat-msg .chat-msg__action').length).toBe(2); expect(view.querySelectorAll('.chat-msg .chat-msg__action').length).toBe(2);
// Test confirmation dialog // Test confirmation dialog
spyOn(window, 'confirm').and.returnValue(true); spyOn(_converse.api, 'confirm').and.callFake(() => Promise.resolve(true));
textarea.value = 'But soft, what light through yonder airlock breaks?'; textarea.value = 'But soft, what light through yonder airlock breaks?';
action = view.querySelector('.chat-msg .chat-msg__action'); action = view.querySelector('.chat-msg .chat-msg__action');
action.style.opacity = 1; action.style.opacity = 1;
action.click(); action.click();
expect(window.confirm).toHaveBeenCalledWith(
await u.waitUntil(() => _converse.api.confirm.calls.count());
expect(_converse.api.confirm).toHaveBeenCalledWith(
'You have an unsent message which will be lost if you continue. Are you sure?'); 'You have an unsent message which will be lost if you continue. Are you sure?');
expect(view.model.messages.at(0).get('correcting')).toBe(true); expect(view.model.messages.at(0).get('correcting')).toBe(true);
expect(textarea.value).toBe('But soft, what light through yonder window breaks?'); expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
textarea.value = 'But soft, what light through yonder airlock breaks?' textarea.value = 'But soft, what light through yonder airlock breaks?'
action.click(); action.click();
await u.waitUntil(() => _converse.api.confirm.calls.count() === 2);
expect(view.model.messages.at(0).get('correcting')).toBe(false); expect(view.model.messages.at(0).get('correcting')).toBe(false);
expect(window.confirm.calls.count()).toBe(2); expect(_converse.api.confirm.calls.argsFor(0)).toEqual(
expect(window.confirm.calls.argsFor(0)).toEqual(
['You have an unsent message which will be lost if you continue. Are you sure?']); ['You have an unsent message which will be lost if you continue. Are you sure?']);
expect(window.confirm.calls.argsFor(1)).toEqual( expect(_converse.api.confirm.calls.argsFor(1)).toEqual(
['You have an unsent message which will be lost if you continue. Are you sure?']); ['You have an unsent message which will be lost if you continue. Are you sure?']);
})); }));

View File

@ -31,8 +31,8 @@ export async function getHeadingStandaloneButton (promise_or_data) {
} }
export async function clearMessages (chat) { export async function clearMessages (chat) {
const result = confirm(__('Are you sure you want to clear the messages from this conversation?')); const result = await api.confirm(__('Are you sure you want to clear the messages from this conversation?'));
if (result === true) { if (result) {
await chat.clearMessages(); await chat.clearMessages();
} }
} }

View File

@ -1385,7 +1385,7 @@ describe("Groupchats", function () {
const from_jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit'; const from_jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => _converse.roster.get(from_jid).vcard.get('fullname')); await u.waitUntil(() => _converse.roster.get(from_jid).vcard.get('fullname'));
spyOn(window, 'confirm').and.callFake(() => true); spyOn(_converse.api, 'confirm').and.callFake(() => Promise.resolve(true));
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo'); await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
const view = _converse.chatboxviews.get('lounge@montague.lit'); const view = _converse.chatboxviews.get('lounge@montague.lit');
await view.close(); // Hack, otherwise we have to mock stanzas. await view.close(); // Hack, otherwise we have to mock stanzas.
@ -1402,7 +1402,7 @@ describe("Groupchats", function () {
</message>`); </message>`);
await _converse.onDirectMUCInvitation(stanza); await _converse.onDirectMUCInvitation(stanza);
expect(window.confirm).toHaveBeenCalledWith( expect(_converse.api.confirm).toHaveBeenCalledWith(
name + ' has invited you to join a groupchat: '+ muc_jid + name + ' has invited you to join a groupchat: '+ muc_jid +
', and left the following reason: "'+reason+'"'); ', and left the following reason: "'+reason+'"');
expect(_converse.chatboxes.models.length).toBe(2); expect(_converse.chatboxes.models.length).toBe(2);
@ -2461,15 +2461,15 @@ describe("Groupchats", function () {
const view = _converse.chatboxviews.get('lounge@montague.lit'); const view = _converse.chatboxviews.get('lounge@montague.lit');
const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea')); const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
textarea.value = '/clear'; textarea.value = '/clear';
spyOn(window, 'confirm').and.callFake(() => false); spyOn(_converse.api, 'confirm').and.callFake(() => Promise.resolve(false));
const message_form = view.querySelector('converse-muc-message-form'); const message_form = view.querySelector('converse-muc-message-form');
message_form.onKeyDown({ message_form.onKeyDown({
target: textarea, target: textarea,
preventDefault: function preventDefault () {}, preventDefault: function preventDefault () {},
keyCode: 13 keyCode: 13
}); });
await u.waitUntil(() => window.confirm.calls.count() === 1); await u.waitUntil(() => _converse.api.confirm.calls.count() === 1);
expect(window.confirm).toHaveBeenCalledWith('Are you sure you want to clear the messages from this conversation?'); expect(_converse.api.confirm).toHaveBeenCalledWith('Are you sure you want to clear the messages from this conversation?');
})); }));
it("takes /owner to make a user an owner", mock.initConverse([], {}, async function (_converse) { it("takes /owner to make a user an owner", mock.initConverse([], {}, async function (_converse) {

View File

@ -313,7 +313,7 @@ describe("A groupchat shown in the groupchats list", function () {
async function (_converse) { async function (_converse) {
const u = converse.env.utils; const u = converse.env.utils;
spyOn(window, 'confirm').and.callFake(() => true); spyOn(_converse.api, 'confirm').and.callFake(() => Promise.resolve(true));
expect(_converse.chatboxes.length).toBe(1); expect(_converse.chatboxes.length).toBe(1);
await mock.waitForRoster(_converse, 'current', 0); await mock.waitForRoster(_converse, 'current', 0);
await mock.openChatRoom(_converse, 'lounge', 'conference.shakespeare.lit', 'JC'); await mock.openChatRoom(_converse, 'lounge', 'conference.shakespeare.lit', 'JC');
@ -328,7 +328,7 @@ describe("A groupchat shown in the groupchats list", function () {
const rooms_list = document.querySelector('converse-rooms-list'); const rooms_list = document.querySelector('converse-rooms-list');
const close_el = rooms_list.querySelector(".close-room"); const close_el = rooms_list.querySelector(".close-room");
close_el.click(); close_el.click();
expect(window.confirm).toHaveBeenCalledWith( expect(_converse.api.confirm).toHaveBeenCalledWith(
'Are you sure you want to leave the groupchat lounge@conference.shakespeare.lit?'); 'Are you sure you want to leave the groupchat lounge@conference.shakespeare.lit?');
await u.waitUntil(() => rooms_list.querySelectorAll(".open-room").length === 0); await u.waitUntil(() => rooms_list.querySelectorAll(".open-room").length === 0);

View File

@ -60,10 +60,13 @@ export class Profile extends CustomElement {
async generateOMEMODeviceBundle (ev) { async generateOMEMODeviceBundle (ev) {
ev.preventDefault(); ev.preventDefault();
if (confirm(__(
const result = await api.confirm(__(
'Are you sure you want to generate new OMEMO keys? ' + 'Are you sure you want to generate new OMEMO keys? ' +
'This will remove your old keys and all previously encrypted messages will no longer be decryptable on this device.' 'This will remove your old keys and all previously ' +
))) { 'encrypted messages will no longer be decryptable on this device.'));
if (result) {
await api.omemo.bundle.generate(); await api.omemo.bundle.generate();
await this.setAttributes(); await this.setAttributes();
this.requestUpdate(); this.requestUpdate();

View File

@ -74,7 +74,8 @@ export class RoomsList extends CustomElement {
async closeRoom (ev) { // eslint-disable-line class-methods-use-this async closeRoom (ev) { // eslint-disable-line class-methods-use-this
ev.preventDefault(); ev.preventDefault();
const name = ev.target.getAttribute('data-room-name'); const name = ev.target.getAttribute('data-room-name');
if (confirm(__("Are you sure you want to leave the groupchat %1$s?", name))) { const result = await api.confirm(__("Are you sure you want to leave the groupchat %1$s?", name));
if (result) {
const jid = ev.target.getAttribute('data-room-jid'); const jid = ev.target.getAttribute('data-room-jid');
const room = await api.rooms.get(jid); const room = await api.rooms.get(jid);
room.close(); room.close();

View File

@ -77,10 +77,12 @@ export default class RosterContact extends CustomElement {
this.model.openChat(); this.model.openChat();
} }
removeContact (ev) { async removeContact (ev) {
ev?.preventDefault?.(); ev?.preventDefault?.();
if (!api.settings.get('allow_contact_removal')) { return; } if (!api.settings.get('allow_contact_removal')) { return; }
if (!confirm(__("Are you sure you want to remove this contact?"))) { return; }
const result = await api.confirm(__("Are you sure you want to remove this contact?"));
if (!result) return;
try { try {
this.model.removeFromRoster(); this.model.removeFromRoster();
@ -108,10 +110,10 @@ export default class RosterContact extends CustomElement {
this.model.authorize().subscribe(); this.model.authorize().subscribe();
} }
declineRequest (ev) { async declineRequest (ev) {
if (ev && ev.preventDefault) { ev.preventDefault(); } if (ev && ev.preventDefault) { ev.preventDefault(); }
const result = confirm(__("Are you sure you want to decline this contact request?")); const result = await api.confirm(__("Are you sure you want to decline this contact request?"));
if (result === true) { if (result) {
this.model.unauthorize().destroy(); this.model.unauthorize().destroy();
} }
return this; return this;

View File

@ -447,7 +447,7 @@ describe("The Protocol", function () {
const jid = 'abram@montague.lit'; const jid = 'abram@montague.lit';
await mock.openControlBox(_converse); await mock.openControlBox(_converse);
await mock.waitForRoster(_converse, 'current'); await mock.waitForRoster(_converse, 'current');
spyOn(window, 'confirm').and.returnValue(true); spyOn(_converse.api, 'confirm').and.callFake(() => Promise.resolve(true));
// We now have a contact we want to remove // We now have a contact we want to remove
expect(_converse.roster.get(jid) instanceof _converse.RosterContact).toBeTruthy(); expect(_converse.roster.get(jid) instanceof _converse.RosterContact).toBeTruthy();
@ -457,7 +457,7 @@ describe("The Protocol", function () {
// remove the first user // remove the first user
header.parentElement.querySelector('li .remove-xmpp-contact').click(); header.parentElement.querySelector('li .remove-xmpp-contact').click();
expect(window.confirm).toHaveBeenCalled(); expect(_converse.api.confirm).toHaveBeenCalled();
/* Section 8.6 Removing a Roster Item and Cancelling All /* Section 8.6 Removing a Roster Item and Cancelling All
* Subscriptions * Subscriptions
@ -478,14 +478,14 @@ describe("The Protocol", function () {
* </query> * </query>
* </iq> * </iq>
*/ */
const sent_iq = _converse.connection.IQ_stanzas.pop(); const iq_stanzas = _converse.connection.IQ_stanzas;
await u.waitUntil(() => Strophe.serialize(iq_stanzas.at(-1)) ===
expect(Strophe.serialize(sent_iq)).toBe( `<iq id="${iq_stanzas.at(-1).getAttribute('id')}" type="set" xmlns="jabber:client">`+
`<iq id="${sent_iq.getAttribute('id')}" type="set" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:roster">`+ `<query xmlns="jabber:iq:roster">`+
`<item jid="abram@montague.lit" subscription="remove"/>`+ `<item jid="abram@montague.lit" subscription="remove"/>`+
`</query>`+ `</query>`+
`</iq>`); `</iq>`);
const sent_iq = iq_stanzas.at(-1);
// Receive confirmation from the contact's server // Receive confirmation from the contact's server
// <iq type='result' id='remove1'/> // <iq type='result' id='remove1'/>

View File

@ -642,7 +642,7 @@ describe("The Contacts Roster", function () {
const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit'; const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
const contact = _converse.roster.get(jid); const contact = _converse.roster.get(jid);
var sent_IQ; var sent_IQ;
spyOn(window, 'confirm').and.returnValue(true); spyOn(_converse.api, 'confirm').and.callFake(() => Promise.resolve(true));
spyOn(contact, 'unauthorize').and.callFake(function () { return contact; }); spyOn(contact, 'unauthorize').and.callFake(function () { return contact; });
spyOn(contact, 'removeFromRoster').and.callThrough(); spyOn(contact, 'removeFromRoster').and.callThrough();
const rosterview = document.querySelector('converse-roster'); const rosterview = document.querySelector('converse-roster');
@ -653,7 +653,7 @@ describe("The Contacts Roster", function () {
}); });
sizzle(`.remove-xmpp-contact[title="Click to remove ${name} as a contact"]`, rosterview).pop().click(); sizzle(`.remove-xmpp-contact[title="Click to remove ${name} as a contact"]`, rosterview).pop().click();
await u.waitUntil(() => (sizzle(".pending-contact-name:contains('"+name+"')", rosterview).length === 0), 1000); await u.waitUntil(() => (sizzle(".pending-contact-name:contains('"+name+"')", rosterview).length === 0), 1000);
expect(window.confirm).toHaveBeenCalled(); expect(_converse.api.confirm).toHaveBeenCalled();
expect(contact.removeFromRoster).toHaveBeenCalled(); expect(contact.removeFromRoster).toHaveBeenCalled();
expect(Strophe.serialize(sent_IQ)).toBe( expect(Strophe.serialize(sent_IQ)).toBe(
`<iq type="set" xmlns="jabber:client">`+ `<iq type="set" xmlns="jabber:client">`+
@ -684,18 +684,19 @@ describe("The Contacts Roster", function () {
}, 700) }, 700)
const remove_el = await u.waitUntil(() => sizzle(`.remove-xmpp-contact[title="Click to remove ${name} as a contact"]`, rosterview).pop()); const remove_el = await u.waitUntil(() => sizzle(`.remove-xmpp-contact[title="Click to remove ${name} as a contact"]`, rosterview).pop());
spyOn(window, 'confirm').and.returnValue(true); spyOn(_converse.api, 'confirm').and.callFake(() => Promise.resolve(true));
remove_el.click(); remove_el.click();
expect(window.confirm).toHaveBeenCalled(); expect(_converse.api.confirm).toHaveBeenCalled();
const iq = _converse.connection.IQ_stanzas.pop(); const iq_stanzas = _converse.connection.IQ_stanzas;
expect(Strophe.serialize(iq)).toBe( await u.waitUntil(() => Strophe.serialize(iq_stanzas.at(-1)) ===
`<iq id="${iq.getAttribute('id')}" type="set" xmlns="jabber:client">`+ `<iq id="${iq_stanzas.at(-1).getAttribute('id')}" type="set" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:roster">`+ `<query xmlns="jabber:iq:roster">`+
`<item jid="lord.capulet@montague.lit" subscription="remove"/>`+ `<item jid="lord.capulet@montague.lit" subscription="remove"/>`+
`</query>`+ `</query>`+
`</iq>`); `</iq>`);
const iq = iq_stanzas.at(-1);
const stanza = u.toStanza(`<iq id="${iq.getAttribute('id')}" to="romeo@montague.lit/orchard" type="result"/>`); const stanza = u.toStanza(`<iq id="${iq.getAttribute('id')}" to="romeo@montague.lit/orchard" type="result"/>`);
_converse.connection._dataRecv(mock.createRequest(stanza)); _converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => rosterview.querySelector(`ul[data-group="Pending contacts"]`) === null); await u.waitUntil(() => rosterview.querySelector(`ul[data-group="Pending contacts"]`) === null);
@ -709,7 +710,7 @@ describe("The Contacts Roster", function () {
await Promise.all(_converse.roster.map(contact => u.waitUntil(() => contact.vcard.get('fullname')))); await Promise.all(_converse.roster.map(contact => u.waitUntil(() => contact.vcard.get('fullname'))));
await u.waitUntil(() => _converse.roster.at(0).vcard.get('fullname')) await u.waitUntil(() => _converse.roster.at(0).vcard.get('fullname'))
const rosterview = document.querySelector('converse-roster'); const rosterview = document.querySelector('converse-roster');
spyOn(window, 'confirm').and.returnValue(true); spyOn(_converse.api, 'confirm').and.returnValue(Promise.resolve(true));
for (let i=0; i<mock.pend_names.length; i++) { for (let i=0; i<mock.pend_names.length; i++) {
const name = mock.pend_names[i]; const name = mock.pend_names[i];
sizzle(`.remove-xmpp-contact[title="Click to remove ${name} as a contact"]`, rosterview).pop().click(); sizzle(`.remove-xmpp-contact[title="Click to remove ${name} as a contact"]`, rosterview).pop().click();
@ -824,16 +825,18 @@ describe("The Contacts Roster", function () {
const name = mock.cur_names[0]; const name = mock.cur_names[0];
const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit'; const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
const contact = _converse.roster.get(jid); const contact = _converse.roster.get(jid);
spyOn(window, 'confirm').and.returnValue(true); spyOn(_converse.api, 'confirm').and.returnValue(Promise.resolve(true));
spyOn(contact, 'removeFromRoster').and.callThrough(); spyOn(contact, 'removeFromRoster').and.callThrough();
let sent_IQ; let sent_IQ;
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback) { spyOn(_converse.connection, 'sendIQ').and.callFake((iq, callback) => {
sent_IQ = iq; sent_IQ = iq;
callback(); callback();
}); });
sizzle(`.remove-xmpp-contact[title="Click to remove ${name} as a contact"]`, rosterview).pop().click(); sizzle(`.remove-xmpp-contact[title="Click to remove ${name} as a contact"]`, rosterview).pop().click();
expect(window.confirm).toHaveBeenCalled(); expect(_converse.api.confirm).toHaveBeenCalled();
await u.waitUntil(() => sent_IQ);
expect(Strophe.serialize(sent_IQ)).toBe( expect(Strophe.serialize(sent_IQ)).toBe(
`<iq type="set" xmlns="jabber:client">`+ `<iq type="set" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:roster"><item jid="mercutio@montague.lit" subscription="remove"/></query>`+ `<query xmlns="jabber:iq:roster"><item jid="mercutio@montague.lit" subscription="remove"/></query>`+
@ -858,15 +861,13 @@ describe("The Contacts Roster", function () {
}); });
const rosterview = document.querySelector('converse-roster'); const rosterview = document.querySelector('converse-roster');
await u.waitUntil(() => sizzle('.roster-group', rosterview).filter(u.isVisible).map(e => e.querySelector('li')).length, 1000); await u.waitUntil(() => sizzle('.roster-group', rosterview).filter(u.isVisible).map(e => e.querySelector('li')).length, 1000);
spyOn(window, 'confirm').and.returnValue(true); spyOn(_converse.api, 'confirm').and.returnValue(Promise.resolve(true));
spyOn(contact, 'removeFromRoster').and.callThrough(); spyOn(contact, 'removeFromRoster').and.callThrough();
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback) { spyOn(_converse.connection, 'sendIQ').and.callFake((iq, callback) => callback?.());
if (typeof callback === "function") { return callback(); }
});
expect(u.isVisible(rosterview.querySelector('.roster-group'))).toBe(true); expect(u.isVisible(rosterview.querySelector('.roster-group'))).toBe(true);
sizzle(`.remove-xmpp-contact[title="Click to remove ${name} as a contact"]`, rosterview).pop().click(); sizzle(`.remove-xmpp-contact[title="Click to remove ${name} as a contact"]`, rosterview).pop().click();
expect(window.confirm).toHaveBeenCalled(); expect(_converse.api.confirm).toHaveBeenCalled();
expect(_converse.connection.sendIQ).toHaveBeenCalled(); await u.waitUntil(() => _converse.connection.sendIQ.calls.count());
expect(contact.removeFromRoster).toHaveBeenCalled(); expect(contact.removeFromRoster).toHaveBeenCalled();
await u.waitUntil(() => rosterview.querySelectorAll('.roster-group').length === 0); await u.waitUntil(() => rosterview.querySelectorAll('.roster-group').length === 0);
})); }));
@ -1126,7 +1127,7 @@ describe("The Contacts Roster", function () {
await mock.openControlBox(_converse); await mock.openControlBox(_converse);
await mock.waitForRoster(_converse, "current", 0); await mock.waitForRoster(_converse, "current", 0);
const name = mock.req_names[0]; const name = mock.req_names[0];
spyOn(window, 'confirm').and.returnValue(true); spyOn(_converse.api, 'confirm').and.returnValue(Promise.resolve(true));
_converse.roster.create({ _converse.roster.create({
'jid': name.replace(/ /g,'.').toLowerCase() + '@montague.lit', 'jid': name.replace(/ /g,'.').toLowerCase() + '@montague.lit',
'subscription': 'none', 'subscription': 'none',
@ -1139,7 +1140,7 @@ describe("The Contacts Roster", function () {
expect(u.isVisible(rosterview.querySelector(`ul[data-group="Contact requests"]`))).toEqual(true); 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); 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(); sizzle('.roster-group', rosterview).filter(u.isVisible).map(e => e.querySelector('li .decline-xmpp-request'))[0].click();
expect(window.confirm).toHaveBeenCalled(); expect(_converse.api.confirm).toHaveBeenCalled();
await u.waitUntil(() => rosterview.querySelector(`ul[data-group="Contact requests"]`) === null); await u.waitUntil(() => rosterview.querySelector(`ul[data-group="Contact requests"]`) === null);
})); }));
@ -1191,12 +1192,12 @@ describe("The Contacts Roster", function () {
const name = mock.req_names.sort()[1]; const name = mock.req_names.sort()[1];
const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit'; const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
const contact = _converse.roster.get(jid); const contact = _converse.roster.get(jid);
spyOn(window, 'confirm').and.returnValue(true); spyOn(_converse.api, 'confirm').and.returnValue(Promise.resolve(true));
spyOn(contact, 'unauthorize').and.callFake(function () { return contact; }); spyOn(contact, 'unauthorize').and.callFake(function () { return contact; });
const req_contact = await u.waitUntil(() => sizzle(".req-contact-name:contains('"+name+"')", rosterview).pop()); const req_contact = await u.waitUntil(() => sizzle(".req-contact-name:contains('"+name+"')", rosterview).pop());
req_contact.parentElement.parentElement.querySelector('.decline-xmpp-request').click(); req_contact.parentElement.parentElement.querySelector('.decline-xmpp-request').click();
expect(window.confirm).toHaveBeenCalled(); expect(_converse.api.confirm).toHaveBeenCalled();
expect(contact.unauthorize).toHaveBeenCalled(); await u.waitUntil(() => contact.unauthorize.calls.count());
// There should now be one less contact // There should now be one less contact
expect(_converse.roster.length).toEqual(mock.req_names.length-1); expect(_converse.roster.length).toEqual(mock.req_names.length-1);
})); }));

View File

@ -65,16 +65,15 @@ class MessageActions extends CustomElement {
`; `;
} }
onMessageEditButtonClicked (ev) { async onMessageEditButtonClicked (ev) {
ev.preventDefault(); ev.preventDefault();
const currently_correcting = this.model.collection.findWhere('correcting'); const currently_correcting = this.model.collection.findWhere('correcting');
// TODO: Use state intead of DOM querying // TODO: Use state intead of DOM querying
// Then this code can also be put on the model // Then this code can also be put on the model
const unsent_text = u.ancestor(this, '.chatbox')?.querySelector('.chat-textarea')?.value; const unsent_text = u.ancestor(this, '.chatbox')?.querySelector('.chat-textarea')?.value;
if (unsent_text && (!currently_correcting || currently_correcting.getMessageText() !== unsent_text)) { if (unsent_text && (!currently_correcting || currently_correcting.getMessageText() !== unsent_text)) {
if (!confirm(__('You have an unsent message which will be lost if you continue. Are you sure?'))) { const result = await api.confirm(__('You have an unsent message which will be lost if you continue. Are you sure?'));
return; if (!result) return;
}
} }
if (currently_correcting !== this.model) { if (currently_correcting !== this.model) {
currently_correcting?.save('correcting', false); currently_correcting?.save('correcting', false);

View File

@ -0,0 +1,3 @@
body {
overflow: auto !important;
}