Commit 506aa331 authored by JC Brand's avatar JC Brand

Wait for promises before opening chats in API methods

parent cb118b9c
......@@ -39,6 +39,9 @@
- New API method `_converse.api.vcard.update`.
- The `contactStatusChanged` event has been renamed to `contactPresenceChanged`
and a event `presenceChanged` is now also triggered on the contact.
- `_converse.api.chats.open` and `_converse.api.rooms.open` now returns a
`Presence` which resolves with the `Backbone.Model` representing the chat
object.
## UI changes
......
......@@ -76149,17 +76149,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
this.model.on('show', this.show, this);
this.model.occupants.on('add', this.showJoinNotification, this);
this.model.occupants.on('remove', this.showLeaveNotification, this);
this.model.occupants.on('change:show', occupant => {
if (!occupant.isMember() || _.includes(occupant.get('states'), '303')) {
return;
}
if (occupant.get('show') === 'offline') {
this.showLeaveNotification(occupant);
} else if (occupant.get('show') === 'online') {
this.showJoinNotification(occupant);
}
});
this.model.occupants.on('change:show', this.showJoinOrLeaveNotification, this);
this.createEmojiPicker();
this.createOccupantsView();
this.render().insertIntoDOM();
......@@ -76169,8 +76159,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
const handler = () => {
if (!u.isPersistableModel(this.model)) {
// Happens during tests, nothing to do if this
// is a hanging chatbox (i.e. not in the
// collection anymore).
// is a hanging chatbox (i.e. not in the collection anymore).
return;
}
......@@ -77047,6 +77036,18 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
}
},
showJoinOrLeaveNotification(occupant) {
if (!occupant.isMember() || _.includes(occupant.get('states'), '303')) {
return;
}
if (occupant.get('show') === 'offline') {
this.showLeaveNotification(occupant);
} else if (occupant.get('show') === 'online') {
this.showJoinNotification(occupant);
}
},
showJoinNotification(occupant) {
if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) {
return;
......@@ -77094,10 +77095,9 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
showLeaveNotification(occupant) {
const nick = occupant.get('nick'),
stat = occupant.get('status'),
last_el = this.content.lastElementChild,
last_msg_date = last_el.getAttribute('data-isodate');
last_el = this.content.lastElementChild;
if (_.includes(_.get(last_el, 'classList', []), 'chat-info') && moment(last_msg_date).isSame(new Date(), "day") && _.get(last_el, 'dataset', {}).join === `"${nick}"`) {
if (last_el && _.includes(_.get(last_el, 'classList', []), 'chat-info') && moment(last_el.getAttribute('data-isodate')).isSame(new Date(), "day") && _.get(last_el, 'dataset', {}).join === `"${nick}"`) {
let message;
if (_.isNil(stat)) {
......@@ -77128,7 +77128,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
'data': `data-leave="${nick}"`
};
if (_.includes(_.get(last_el, 'classList', []), 'chat-info') && _.get(last_el, 'dataset', {}).leavejoin === `"${nick}"`) {
if (last_el && _.includes(_.get(last_el, 'classList', []), 'chat-info') && _.get(last_el, 'dataset', {}).leavejoin === `"${nick}"`) {
last_el.outerHTML = tpl_info(data);
} else {
const el = u.stringToElement(tpl_info(data));
......@@ -83843,8 +83843,13 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 400) {
jed_instance = new Jed(window.JSON.parse(xhr.responseText));
resolve();
try {
const data = window.JSON.parse(xhr.responseText);
jed_instance = new Jed(data);
resolve();
} catch (e) {
xhr.onerror(e);
}
} else {
xhr.onerror();
}
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
......@@ -13,32 +13,37 @@
function (done, _converse) {
test_utils.createContacts(_converse, 'current');
_converse.emit('rosterContactsFetched');
test_utils.openControlBox();
_converse.minimized_chats.toggleview.model.browserStorage._clear();
_converse.minimized_chats.initToggle();
var contact_jid, chatview;
contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, contact_jid);
chatview = _converse.chatboxviews.get(contact_jid);
expect(chatview.model.get('minimized')).toBeFalsy();
expect($(_converse.minimized_chats.el).is(':visible')).toBeFalsy();
chatview.el.querySelector('.toggle-chatbox-button').click();
expect(chatview.model.get('minimized')).toBeTruthy();
expect($(_converse.minimized_chats.el).is(':visible')).toBeTruthy();
expect(_converse.minimized_chats.keys().length).toBe(1);
expect(_converse.minimized_chats.keys()[0]).toBe(contact_jid);
contact_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, contact_jid);
chatview = _converse.chatboxviews.get(contact_jid);
expect(chatview.model.get('minimized')).toBeFalsy();
chatview.el.querySelector('.toggle-chatbox-button').click();
expect(chatview.model.get('minimized')).toBeTruthy();
expect($(_converse.minimized_chats.el).is(':visible')).toBeTruthy();
expect(_converse.minimized_chats.keys().length).toBe(2);
expect(_.includes(_converse.minimized_chats.keys(), contact_jid)).toBeTruthy();
done();
let contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
let chatview;
test_utils.openChatBoxFor(_converse, contact_jid)
.then(() => {
chatview = _converse.chatboxviews.get(contact_jid);
expect(chatview.model.get('minimized')).toBeFalsy();
expect($(_converse.minimized_chats.el).is(':visible')).toBeFalsy();
chatview.el.querySelector('.toggle-chatbox-button').click();
expect(chatview.model.get('minimized')).toBeTruthy();
expect($(_converse.minimized_chats.el).is(':visible')).toBeTruthy();
expect(_converse.minimized_chats.keys().length).toBe(1);
expect(_converse.minimized_chats.keys()[0]).toBe(contact_jid);
contact_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
return test_utils.openChatBoxFor(_converse, contact_jid);
}).then(() => {
chatview = _converse.chatboxviews.get(contact_jid);
expect(chatview.model.get('minimized')).toBeFalsy();
chatview.el.querySelector('.toggle-chatbox-button').click();
expect(chatview.model.get('minimized')).toBeTruthy();
expect($(_converse.minimized_chats.el).is(':visible')).toBeTruthy();
expect(_converse.minimized_chats.keys().length).toBe(2);
expect(_.includes(_converse.minimized_chats.keys(), contact_jid)).toBeTruthy();
done();
});
}));
it("can be toggled to hide or show minimized chats",
......@@ -47,24 +52,26 @@
function (done, _converse) {
test_utils.createContacts(_converse, 'current');
_converse.emit('rosterContactsFetched');
test_utils.openControlBox();
_converse.minimized_chats.toggleview.model.browserStorage._clear();
_converse.minimized_chats.initToggle();
var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, contact_jid);
var chatview = _converse.chatboxviews.get(contact_jid);
expect($(_converse.minimized_chats.el).is(':visible')).toBeFalsy();
chatview.model.set({'minimized': true});
expect($(_converse.minimized_chats.el).is(':visible')).toBeTruthy();
expect(_converse.minimized_chats.keys().length).toBe(1);
expect(_converse.minimized_chats.keys()[0]).toBe(contact_jid);
expect($(_converse.minimized_chats.el.querySelector('.minimized-chats-flyout')).is(':visible')).toBeTruthy();
expect(_converse.minimized_chats.toggleview.model.get('collapsed')).toBeFalsy();
_converse.minimized_chats.el.querySelector('#toggle-minimized-chats').click();
return test_utils.waitUntil(() => u.isVisible(u.isVisible(_converse.minimized_chats.el.querySelector('.minimized-chats-flyout'))))
.then(function () {
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, contact_jid)
.then(() => {
const chatview = _converse.chatboxviews.get(contact_jid);
expect(u.isVisible(_converse.minimized_chats.el)).toBeFalsy();
chatview.model.set({'minimized': true});
expect(u.isVisible(_converse.minimized_chats.el)).toBeTruthy();
expect(_converse.minimized_chats.keys().length).toBe(1);
expect(_converse.minimized_chats.keys()[0]).toBe(contact_jid);
expect(u.isVisible(_converse.minimized_chats.el.querySelector('.minimized-chats-flyout'))).toBeTruthy();
expect(_converse.minimized_chats.toggleview.model.get('collapsed')).toBeFalsy();
_converse.minimized_chats.el.querySelector('#toggle-minimized-chats').click();
return test_utils.waitUntil(() => u.isVisible(_converse.minimized_chats.el.querySelector('.minimized-chats-flyout')));
}).then(() => {
expect(_converse.minimized_chats.toggleview.model.get('collapsed')).toBeTruthy();
done();
});
......
This diff is collapsed.
......@@ -92,12 +92,14 @@
it("can be sent without a hint",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) {
test_utils.createContacts(_converse, 'current');
test_utils.createContacts(_converse, 'current', 1);
_converse.emit('rosterContactsFetched');
test_utils.openControlBox();
var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
// XXX: We need to send a presence from the contact, so that we
// have a resource, that resource is then queried to see
......@@ -108,9 +110,9 @@
'to': 'dummy@localhost'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
test_utils.openChatBoxFor(_converse, contact_jid);
test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]).then(function () {
test_utils.openChatBoxFor(_converse, contact_jid)
.then(() => test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]))
.then(() => {
var view = _converse.chatboxviews.get(contact_jid);
spyOn(view, 'onMessageSubmitted').and.callThrough();
spyOn(_converse.connection, 'send');
......@@ -167,10 +169,12 @@
it("can be sent with a hint",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) {
test_utils.createContacts(_converse, 'current');
test_utils.createContacts(_converse, 'current', 1);
_converse.emit('rosterContactsFetched');
test_utils.openControlBox();
var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
......@@ -183,9 +187,9 @@
'to': 'dummy@localhost'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
test_utils.openChatBoxFor(_converse, contact_jid);
test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]).then(function () {
test_utils.openChatBoxFor(_converse, contact_jid)
.then(() => test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]))
.then(() => {
var view = _converse.chatboxviews.get(contact_jid);
var spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
spoiler_toggle.click();
......@@ -206,17 +210,17 @@
expect(view.onMessageSubmitted).toHaveBeenCalled();
/* Test the XML stanza
*
* <message from="dummy@localhost/resource"
* to="max.frankfurter@localhost"
* type="chat"
* id="4547c38b-d98b-45a5-8f44-b4004dbc335e"
* xmlns="jabber:client">
* <body>This is the spoiler</body>
* <active xmlns="http://jabber.org/protocol/chatstates"/>
* <spoiler xmlns="urn:xmpp:spoiler:0">This is the hint</spoiler>
* </message>"
*/
*
* <message from="dummy@localhost/resource"
* to="max.frankfurter@localhost"
* type="chat"
* id="4547c38b-d98b-45a5-8f44-b4004dbc335e"
* xmlns="jabber:client">
* <body>This is the spoiler</body>
* <active xmlns="http://jabber.org/protocol/chatstates"/>
* <spoiler xmlns="urn:xmpp:spoiler:0">This is the hint</spoiler>
* </message>"
*/
var stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
var spoiler_el = stanza.querySelector('spoiler[xmlns="urn:xmpp:spoiler:0"]');
......
......@@ -58,16 +58,17 @@
test_utils.createContacts(_converse, 'current');
_converse.emit('rosterContactsFetched');
let view, modal;
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
const show_modal_button = view.el.querySelector('.show-user-details-modal');
expect(u.isVisible(show_modal_button)).toBeTruthy();
show_modal_button.click();
const modal = view.user_details_modal;
test_utils.waitUntil(() => u.isVisible(modal.el), 2000)
.then(function () {
test_utils.openChatBoxFor(_converse, contact_jid)
.then(() => {
view = _converse.chatboxviews.get(contact_jid);
const show_modal_button = view.el.querySelector('.show-user-details-modal');
expect(u.isVisible(show_modal_button)).toBeTruthy();
show_modal_button.click();
modal = view.user_details_modal;
return test_utils.waitUntil(() => u.isVisible(modal.el), 2000);
}).then(function () {
spyOn(window, 'confirm').and.returnValue(true);
spyOn(view.model.contact, 'removeFromRoster').and.callFake(function (callback, errback) {
errback();
......
......@@ -561,8 +561,7 @@
const handler = () => {
if (!u.isPersistableModel(this.model)) {
// Happens during tests, nothing to do if this
// is a hanging chatbox (i.e. not in the
// collection anymore).
// is a hanging chatbox (i.e. not in the collection anymore).
return;
}
this.populateAndJoin();
......
......@@ -148,8 +148,13 @@
);
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 400) {
jed_instance = new Jed(window.JSON.parse(xhr.responseText));
resolve();
try {
const data = window.JSON.parse(xhr.responseText);
jed_instance = new Jed(data);
resolve();
} catch (e) {
xhr.onerror(e);
}
} else {
xhr.onerror();
}
......
......@@ -98,8 +98,9 @@
return views;
};
utils.openChatBoxFor = function (converse, jid) {
return converse.roster.get(jid).trigger("open");
utils.openChatBoxFor = function (_converse, jid) {
_converse.roster.get(jid).trigger("open");
return utils.waitUntil(() => _converse.chatboxviews.get(jid));
};
utils.openChatRoomViaModal = function (_converse, jid, nick) {
......@@ -121,64 +122,64 @@
};
utils.openChatRoom = function (_converse, room, server, nick) {
_converse.api.rooms.open(`${room}@${server}`);
return _converse.api.rooms.open(`${room}@${server}`);
};
utils.openAndEnterChatRoom = function (_converse, room, server, nick) {
let last_stanza;
return new Promise(function (resolve, reject) {
_converse.api.rooms.open(`${room}@${server}`);
const view = _converse.chatboxviews.get((room+'@'+server).toLowerCase());
// We pretend this is a new room, so no disco info is returned.
let last_stanza = _.last(_converse.connection.IQ_stanzas).nodeTree;
const IQ_id = last_stanza.getAttribute('id');
const features_stanza = $iq({
'from': room+'@'+server,
'id': IQ_id,
'to': nick+'@'+server,
'type': 'error'
}).c('error', {'type': 'cancel'})
.c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
_converse.connection._dataRecv(utils.createRequest(features_stanza));
utils.waitUntil(() => {
return _.filter(
_converse.connection.IQ_stanzas, (node) => {
const query = node.nodeTree.querySelector('query');
if (query && query.getAttribute('node') === 'x-roomuser-item') {
last_stanza = node.nodeTree;
return true;
}
}).length
}).then(function () {
// The XMPP server returns the reserved nick for this user.
return new Promise((resolve, reject) => {
return _converse.api.rooms.open(`${room}@${server}`).then(() => {
const view = _converse.chatboxviews.get((room+'@'+server).toLowerCase());
// We pretend this is a new room, so no disco info is returned.
let last_stanza = _.last(_converse.connection.IQ_stanzas).nodeTree;
const IQ_id = last_stanza.getAttribute('id');
const stanza = $iq({
'type': 'result',
'id': IQ_id,
'from': view.model.get('jid'),
'to': _converse.connection.jid
}).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info', 'node': 'x-roomuser-item'})
.c('identity', {'category': 'conference', 'name': nick, 'type': 'text'});
_converse.connection._dataRecv(utils.createRequest(stanza));
// The user has just entered the room (because join was called)
// and receives their own presence from the server.
// See example 24: http://xmpp.org/extensions/xep-0045.html#enter-pres
var presence = $pres({
to: _converse.connection.jid,
from: room+'@'+server+'/'+nick,
id: 'DC352437-C019-40EC-B590-AF29E879AF97'
}).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
.c('item').attrs({
affiliation: 'member',
jid: _converse.bare_jid,
role: 'participant'
}).up()
.c('status').attrs({code:'110'});
_converse.connection._dataRecv(utils.createRequest(presence));
resolve();
const features_stanza = $iq({
'from': room+'@'+server,
'id': IQ_id,
'to': nick+'@'+server,
'type': 'error'
}).c('error', {'type': 'cancel'})
.c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
_converse.connection._dataRecv(utils.createRequest(features_stanza));
utils.waitUntil(() => {
return _.filter(
_converse.connection.IQ_stanzas, (node) => {
const query = node.nodeTree.querySelector('query');
if (query && query.getAttribute('node') === 'x-roomuser-item') {
last_stanza = node.nodeTree;
return true;
}
}).length
}).then(function () {
// The XMPP server returns the reserved nick for this user.
const IQ_id = last_stanza.getAttribute('id');
const stanza = $iq({
'type': 'result',
'id': IQ_id,
'from': view.model.get('jid'),
'to': _converse.connection.jid
}).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info', 'node': 'x-roomuser-item'})
.c('identity', {'category': 'conference', 'name': nick, 'type': 'text'});
_converse.connection._dataRecv(utils.createRequest(stanza));
// The user has just entered the room (because join was called)
// and receives their own presence from the server.
// See example 24: http://xmpp.org/extensions/xep-0045.html#enter-pres
var presence = $pres({
to: _converse.connection.jid,
from: room+'@'+server+'/'+nick,
id: 'DC352437-C019-40EC-B590-AF29E879AF97'
}).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
.c('item').attrs({
affiliation: 'member',
jid: _converse.bare_jid,
role: 'participant'
}).up()
.c('status').attrs({code:'110'});
_converse.connection._dataRecv(utils.createRequest(presence));
resolve();
}).catch(_.partial(console.error, _));
}).catch(_.partial(console.error, _));
}).catch(_.partial(console.error, _));
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment