From ba09996998df38a5eb76903457fbb1077caabe25 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Thu, 8 Feb 2018 09:49:05 +0100 Subject: [PATCH] Don't allow PEP bookmarks if #publish-options is not advertised --- spec/bookmarks.js | 571 ++++++++++++++++++++------------------ spec/chatbox.js | 8 +- spec/chatroom.js | 466 +++++++++++++++---------------- spec/protocol.js | 2 +- src/converse-bookmarks.js | 43 +-- tests/mock.js | 7 - tests/utils.js | 16 +- 7 files changed, 572 insertions(+), 541 deletions(-) diff --git a/spec/bookmarks.js b/spec/bookmarks.js index fd4b23fbc..b36185aea 100644 --- a/spec/bookmarks.js +++ b/spec/bookmarks.js @@ -23,212 +23,90 @@ it("can be bookmarked", mock.initConverseWithPromises( null, ['rosterGroupsFetched'], {}, function (done, _converse) { - var sent_stanza, IQ_id; - var sendIQ = _converse.connection.sendIQ; - spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { - sent_stanza = iq; - IQ_id = sendIQ.bind(this)(iq, callback, errback); - }); - spyOn(_converse.connection, 'getUniqueId').and.callThrough(); - - test_utils.openChatRoom(_converse, 'theplay', 'conference.shakespeare.lit', 'JC'); - - var jid = 'theplay@conference.shakespeare.lit'; - var view = _converse.chatboxviews.get(jid); - spyOn(view, 'renderBookmarkForm').and.callThrough(); - spyOn(view, 'closeForm').and.callThrough(); - - test_utils.waitUntil(function () { - return !_.isNull(view.el.querySelector('.toggle-bookmark')); - }, 300).then(function () { - var $bookmark = $(view.el).find('.icon-pushpin'); - $bookmark[0].click(); - expect(view.renderBookmarkForm).toHaveBeenCalled(); - - view.el.querySelector('.button-cancel').click(); - expect(view.closeForm).toHaveBeenCalled(); - expect($bookmark.hasClass('on-button'), false); - - $bookmark[0].click(); - expect(view.renderBookmarkForm).toHaveBeenCalled(); - - /* Client uploads data: - * -------------------- - * - * - * - * - * - * - * JC - * - * - * - * - * - * - * - * http://jabber.org/protocol/pubsub#publish-options - * - * - * true - * - * - * whitelist - * - * - * - * - * - */ - expect(view.model.get('bookmarked')).toBeFalsy(); - var $form = $(view.el).find('.chatroom-form'); - $form.find('input[name="name"]').val('Play's the Thing'); - $form.find('input[name="autojoin"]').prop('checked', true); - $form.find('input[name="nick"]').val('JC'); - view.el.querySelector('.button-primary').click(); - - expect(view.model.get('bookmarked')).toBeTruthy(); - expect($bookmark.hasClass('on-button'), true); - - expect(sent_stanza.toLocaleString()).toBe( - ""+ - ""+ - ""+ - ""+ - ""+ - ""+ - "JC"+ - ""+ - ""+ - ""+ - ""+ - ""+ - ""+ - ""+ - "http://jabber.org/protocol/pubsub#publish-options"+ - ""+ - ""+ - "true"+ - ""+ - ""+ - "whitelist"+ - ""+ - ""+ - ""+ - ""+ - "" - ); - - /* Server acknowledges successful storage - * - * - */ - var stanza = $iq({ - 'to':_converse.connection.jid, - 'type':'result', - 'id':IQ_id - }); - _converse.connection._dataRecv(test_utils.createRequest(stanza)); - // We ignore this IQ stanza... (unless it's an error stanza), so - // nothing to test for here. - done(); - }); - })); - - it("will be automatically opened if 'autojoin' is set on the bookmark", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, function (done, _converse) { - - var jid = 'lounge@localhost'; - _converse.bookmarks.create({ - 'jid': jid, - 'autojoin': false, - 'name': 'The Lounge', - 'nick': ' Othello' - }); - expect(_.isUndefined(_converse.chatboxviews.get(jid))).toBeTruthy(); - - jid = 'theplay@conference.shakespeare.lit'; - _converse.bookmarks.create({ - 'jid': jid, - 'autojoin': true, - 'name': 'The Play', - 'nick': ' Othello' - }); - expect(_.isUndefined(_converse.chatboxviews.get(jid))).toBeFalsy(); - done(); - })); - - describe("when bookmarked", function () { - - it("displays that it's bookmarked through its bookmark icon", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, function (done, _converse) { - - test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); - var view = _converse.chatboxviews.get('lounge@localhost'); - - test_utils.waitUntil(function () { - return !_.isNull(view.el.querySelector('.toggle-bookmark')); - }, 300).then(function () { - var bookmark_icon = view.el.querySelector('.icon-pushpin'); - expect(_.includes(bookmark_icon.classList, 'button-on')).toBeFalsy(); - view.model.set('bookmarked', true); - expect(_.includes(bookmark_icon.classList, 'button-on')).toBeTruthy(); - view.model.set('bookmarked', false); - expect(_.includes(bookmark_icon.classList, 'button-on')).toBeFalsy(); - done(); - }); - })); - - it("can be unbookmarked", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, function (done, _converse) { - + test_utils.waitUntilDiscoConfirmed( + _converse, _converse.bare_jid, + [{'category': 'pubsub', 'type': 'pep'}], + ['http://jabber.org/protocol/pubsub#publish-options'] + ).then(function () { var sent_stanza, IQ_id; var sendIQ = _converse.connection.sendIQ; + spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { + sent_stanza = iq; + IQ_id = sendIQ.bind(this)(iq, callback, errback); + }); + spyOn(_converse.connection, 'getUniqueId').and.callThrough(); test_utils.openChatRoom(_converse, 'theplay', 'conference.shakespeare.lit', 'JC'); var jid = 'theplay@conference.shakespeare.lit'; var view = _converse.chatboxviews.get(jid); + spyOn(view, 'renderBookmarkForm').and.callThrough(); + spyOn(view, 'closeForm').and.callThrough(); test_utils.waitUntil(function () { return !_.isNull(view.el.querySelector('.toggle-bookmark')); }, 300).then(function () { - spyOn(view, 'toggleBookmark').and.callThrough(); - spyOn(_converse.bookmarks, 'sendBookmarkStanza').and.callThrough(); - view.delegateEvents(); + var $bookmark = $(view.el).find('.icon-pushpin'); + $bookmark[0].click(); + expect(view.renderBookmarkForm).toHaveBeenCalled(); + + view.el.querySelector('.button-cancel').click(); + expect(view.closeForm).toHaveBeenCalled(); + expect($bookmark.hasClass('on-button'), false); + + $bookmark[0].click(); + expect(view.renderBookmarkForm).toHaveBeenCalled(); + + /* Client uploads data: + * -------------------- + * + * + * + * + * + * + * JC + * + * + * + * + * + * + * + * http://jabber.org/protocol/pubsub#publish-options + * + * + * true + * + * + * whitelist + * + * + * + * + * + */ + expect(view.model.get('bookmarked')).toBeFalsy(); + var $form = $(view.el).find('.chatroom-form'); + $form.find('input[name="name"]').val('Play's the Thing'); + $form.find('input[name="autojoin"]').prop('checked', true); + $form.find('input[name="nick"]').val('JC'); + view.el.querySelector('.button-primary').click(); - _converse.bookmarks.create({ - 'jid': view.model.get('jid'), - 'autojoin': false, - 'name': 'The Play', - 'nick': ' Othello' - }); - expect(_converse.bookmarks.length).toBe(1); expect(view.model.get('bookmarked')).toBeTruthy(); - var $bookmark_icon = $(view.el.querySelector('.icon-pushpin')); - expect($bookmark_icon.hasClass('button-on')).toBeTruthy(); + expect($bookmark.hasClass('on-button'), true); - spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { - sent_stanza = iq; - IQ_id = sendIQ.bind(this)(iq, callback, errback); - }); - spyOn(_converse.connection, 'getUniqueId').and.callThrough(); - $bookmark_icon[0].click(); - expect(view.toggleBookmark).toHaveBeenCalled(); - expect($bookmark_icon.hasClass('button-on')).toBeFalsy(); - expect(_converse.bookmarks.length).toBe(0); - - // Check that an IQ stanza is sent out, containing no - // conferences to bookmark (since we removed the one and - // only bookmark). expect(sent_stanza.toLocaleString()).toBe( ""+ ""+ ""+ ""+ - ""+ + ""+ + ""+ + "JC"+ + ""+ + ""+ ""+ ""+ ""+ @@ -247,32 +125,186 @@ ""+ "" ); + + /* Server acknowledges successful storage + * + * + */ + var stanza = $iq({ + 'to':_converse.connection.jid, + 'type':'result', + 'id':IQ_id + }); + _converse.connection._dataRecv(test_utils.createRequest(stanza)); + // We ignore this IQ stanza... (unless it's an error stanza), so + // nothing to test for here. done(); }); + }); + })); + + it("will be automatically opened if 'autojoin' is set on the bookmark", mock.initConverseWithPromises( + null, ['rosterGroupsFetched'], {}, function (done, _converse) { + + test_utils.waitUntilDiscoConfirmed( + _converse, _converse.bare_jid, + [{'category': 'pubsub', 'type': 'pep'}], + ['http://jabber.org/protocol/pubsub#publish-options'] + ).then(function () { + var jid = 'lounge@localhost'; + _converse.bookmarks.create({ + 'jid': jid, + 'autojoin': false, + 'name': 'The Lounge', + 'nick': ' Othello' + }); + expect(_.isUndefined(_converse.chatboxviews.get(jid))).toBeTruthy(); + + jid = 'theplay@conference.shakespeare.lit'; + _converse.bookmarks.create({ + 'jid': jid, + 'autojoin': true, + 'name': 'The Play', + 'nick': ' Othello' + }); + expect(_.isUndefined(_converse.chatboxviews.get(jid))).toBeFalsy(); + done(); + }); + })); + + describe("when bookmarked", function () { + + it("displays that it's bookmarked through its bookmark icon", mock.initConverseWithPromises( + null, ['rosterGroupsFetched'], {}, function (done, _converse) { + + test_utils.waitUntilDiscoConfirmed( + _converse, _converse.bare_jid, + [{'category': 'pubsub', 'type': 'pep'}], + ['http://jabber.org/protocol/pubsub#publish-options'] + ).then(function () { + test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); + var view = _converse.chatboxviews.get('lounge@localhost'); + + test_utils.waitUntil(function () { + return !_.isNull(view.el.querySelector('.toggle-bookmark')); + }, 300).then(function () { + var bookmark_icon = view.el.querySelector('.icon-pushpin'); + expect(_.includes(bookmark_icon.classList, 'button-on')).toBeFalsy(); + view.model.set('bookmarked', true); + expect(_.includes(bookmark_icon.classList, 'button-on')).toBeTruthy(); + view.model.set('bookmarked', false); + expect(_.includes(bookmark_icon.classList, 'button-on')).toBeFalsy(); + done(); + }); + }); + })); + + it("can be unbookmarked", mock.initConverseWithPromises( + null, ['rosterGroupsFetched'], {}, function (done, _converse) { + + test_utils.waitUntilDiscoConfirmed( + _converse, _converse.bare_jid, + [{'category': 'pubsub', 'type': 'pep'}], + ['http://jabber.org/protocol/pubsub#publish-options'] + ).then(function () { + var sent_stanza, IQ_id; + var sendIQ = _converse.connection.sendIQ; + + test_utils.openChatRoom(_converse, 'theplay', 'conference.shakespeare.lit', 'JC'); + var jid = 'theplay@conference.shakespeare.lit'; + var view = _converse.chatboxviews.get(jid); + + test_utils.waitUntil(function () { + return !_.isNull(view.el.querySelector('.toggle-bookmark')); + }, 300).then(function () { + spyOn(view, 'toggleBookmark').and.callThrough(); + spyOn(_converse.bookmarks, 'sendBookmarkStanza').and.callThrough(); + view.delegateEvents(); + + _converse.bookmarks.create({ + 'jid': view.model.get('jid'), + 'autojoin': false, + 'name': 'The Play', + 'nick': ' Othello' + }); + expect(_converse.bookmarks.length).toBe(1); + expect(view.model.get('bookmarked')).toBeTruthy(); + var $bookmark_icon = $(view.el.querySelector('.icon-pushpin')); + expect($bookmark_icon.hasClass('button-on')).toBeTruthy(); + + spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { + sent_stanza = iq; + IQ_id = sendIQ.bind(this)(iq, callback, errback); + }); + spyOn(_converse.connection, 'getUniqueId').and.callThrough(); + $bookmark_icon[0].click(); + expect(view.toggleBookmark).toHaveBeenCalled(); + expect($bookmark_icon.hasClass('button-on')).toBeFalsy(); + expect(_converse.bookmarks.length).toBe(0); + + // Check that an IQ stanza is sent out, containing no + // conferences to bookmark (since we removed the one and + // only bookmark). + expect(sent_stanza.toLocaleString()).toBe( + ""+ + ""+ + ""+ + ""+ + ""+ + ""+ + ""+ + ""+ + ""+ + ""+ + "http://jabber.org/protocol/pubsub#publish-options"+ + ""+ + ""+ + "true"+ + ""+ + ""+ + "whitelist"+ + ""+ + ""+ + ""+ + ""+ + "" + ); + done(); + }); + }); })); }); describe("and when autojoin is set", function () { - it("will be be opened and joined automatically upon login", mock.initConverse(function (_converse) { - spyOn(_converse.api.rooms, 'open'); - var jid = 'theplay@conference.shakespeare.lit'; - var model = _converse.bookmarks.create({ - 'jid': jid, - 'autojoin': false, - 'name': 'The Play', - 'nick': '' - }); - expect(_converse.api.rooms.open).not.toHaveBeenCalled(); - _converse.bookmarks.remove(model); + it("will be be opened and joined automatically upon login", mock.initConverseWithPromises( + null, ['rosterGroupsFetched'], {}, function (done, _converse) { - _converse.bookmarks.create({ - 'jid': jid, - 'autojoin': true, - 'name': 'Hamlet', - 'nick': '' + test_utils.waitUntilDiscoConfirmed( + _converse, _converse.bare_jid, + [{'category': 'pubsub', 'type': 'pep'}], + ['http://jabber.org/protocol/pubsub#publish-options'] + ).then(function () { + spyOn(_converse.api.rooms, 'open'); + var jid = 'theplay@conference.shakespeare.lit'; + var model = _converse.bookmarks.create({ + 'jid': jid, + 'autojoin': false, + 'name': 'The Play', + 'nick': '' + }); + expect(_converse.api.rooms.open).not.toHaveBeenCalled(); + _converse.bookmarks.remove(model); + + _converse.bookmarks.create({ + 'jid': jid, + 'autojoin': true, + 'name': 'Hamlet', + 'nick': '' + }); + expect(_converse.api.rooms.open).toHaveBeenCalled(); + done(); }); - expect(_converse.api.rooms.open).toHaveBeenCalled(); })); }); }); @@ -283,53 +315,58 @@ ['send'], ['rosterGroupsFetched', 'connected'], {}, function (done, _converse) { - test_utils.openControlBox().openRoomsPanel(_converse); + test_utils.waitUntilDiscoConfirmed( + _converse, _converse.bare_jid, + [{'category': 'pubsub', 'type': 'pep'}], + ['http://jabber.org/protocol/pubsub#publish-options'] + ).then(function () { + test_utils.openControlBox().openRoomsPanel(_converse); + test_utils.waitUntil(function () { + return _converse.bookmarks; + }, 300).then(function () { + /* The stored data is automatically pushed to all of the user's + * connected resources. + * + * Publisher receives event notification + * ------------------------------------- + * + * + * + * + * + * + * JC + * + * + * + * + * + * + */ + var stanza = $msg({ + 'from': 'dummy@localhost', + 'to': 'dummy@localhost/resource', + 'type': 'headline', + 'id': 'rnfoo1' + }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'}) + .c('items', {'node': 'storage:bookmarks'}) + .c('item', {'id': 'current'}) + .c('storage', {'xmlns': 'storage:bookmarks'}) + .c('conference', {'name': 'The Play's the Thing', + 'autojoin': 'true', + 'jid':'theplay@conference.shakespeare.lit'}) + .c('nick').t('JC'); - test_utils.waitUntil(function () { - return _converse.bookmarks; - }, 300).then(function () { - /* The stored data is automatically pushed to all of the user's - * connected resources. - * - * Publisher receives event notification - * ------------------------------------- - * - * - * - * - * - * - * JC - * - * - * - * - * - * - */ - var stanza = $msg({ - 'from': 'dummy@localhost', - 'to': 'dummy@localhost/resource', - 'type': 'headline', - 'id': 'rnfoo1' - }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'}) - .c('items', {'node': 'storage:bookmarks'}) - .c('item', {'id': 'current'}) - .c('storage', {'xmlns': 'storage:bookmarks'}) - .c('conference', {'name': 'The Play's the Thing', - 'autojoin': 'true', - 'jid':'theplay@conference.shakespeare.lit'}) - .c('nick').t('JC'); - - _converse.connection._dataRecv(test_utils.createRequest(stanza)); - expect(_converse.bookmarks.length).toBe(1); - expect(_converse.chatboxviews.get('theplay@conference.shakespeare.lit')).not.toBeUndefined(); - done(); + _converse.connection._dataRecv(test_utils.createRequest(stanza)); + expect(_converse.bookmarks.length).toBe(1); + expect(_converse.chatboxviews.get('theplay@conference.shakespeare.lit')).not.toBeUndefined(); + done(); + }); }); })); @@ -337,9 +374,11 @@ ['send'], ['chatBoxesFetched', 'roomsPanelRendered', 'rosterGroupsFetched'], {}, function (done, _converse) { - test_utils.waitUntil(function () { - return _converse.bookmarks; - }, 300).then(function () { + test_utils.waitUntilDiscoConfirmed( + _converse, _converse.bare_jid, + [{'category': 'pubsub', 'type': 'pep'}], + ['http://jabber.org/protocol/pubsub#publish-options'] + ).then(function () { /* Client requests all items * ------------------------- * @@ -420,9 +459,11 @@ it("shows a list of bookmarks", mock.initConverseWithPromises( ['send'], ['rosterGroupsFetched'], {}, function (done, _converse) { - test_utils.waitUntil(function () { - return _converse.bookmarks; - }, 300).then(function () { + test_utils.waitUntilDiscoConfirmed( + _converse, _converse.bare_jid, + [{'category': 'pubsub', 'type': 'pep'}], + ['http://jabber.org/protocol/pubsub#publish-options'] + ).then(function () { test_utils.openControlBox().openRoomsPanel(_converse); var IQ_id; @@ -495,9 +536,11 @@ it("remembers the toggle state of the bookmarks list", mock.initConverseWithPromises( ['send'], ['rosterGroupsFetched'], {}, function (done, _converse) { - test_utils.waitUntil(function () { - return _converse.bookmarks; - }, 300).then(function () { + test_utils.waitUntilDiscoConfirmed( + _converse, _converse.bare_jid, + [{'category': 'pubsub', 'type': 'pep'}], + ['http://jabber.org/protocol/pubsub#publish-options'] + ).then(function () { var IQ_id; expect(_.filter(_converse.connection.send.calls.all(), function (call) { var stanza = call.args[0]; @@ -561,9 +604,11 @@ { hide_open_bookmarks: true }, function (done, _converse) { - test_utils.waitUntil(function () { - return _converse.bookmarks; - }, 300).then(function () { + test_utils.waitUntilDiscoConfirmed( + _converse, _converse.bare_jid, + [{'category': 'pubsub', 'type': 'pep'}], + ['http://jabber.org/protocol/pubsub#publish-options'] + ).then(function () { test_utils.openControlBox().openRoomsPanel(_converse); // XXX Create bookmarks view here, otherwise we need to mock stanza // traffic for it to get created. diff --git a/spec/chatbox.js b/spec/chatbox.js index 957436ef7..bf83e560c 100644 --- a/spec/chatbox.js +++ b/spec/chatbox.js @@ -57,7 +57,7 @@ null, ['rosterGroupsFetched'], {}, function (done, _converse) { - test_utils.waitUntilFeatureSupportConfirmed(_converse, 'localhost', 'vcard-temp') + test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp']) .then(function () { return test_utils.waitUntil(function () { return _converse.xmppstatus.get('fullname'); @@ -1228,7 +1228,7 @@ function (done, _converse) { var contact, sent_stanza, IQ_id, stanza; - test_utils.waitUntilFeatureSupportConfirmed(_converse, 'localhost', 'vcard-temp') + test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp']) .then(function () { return test_utils.waitUntil(function () { return _converse.xmppstatus.get('fullname'); @@ -1842,7 +1842,7 @@ function (done, _converse) { var contact, sent_stanza, IQ_id, stanza; - test_utils.waitUntilFeatureSupportConfirmed(_converse, 'localhost', 'vcard-temp') + test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp']) .then(function () { return test_utils.waitUntil(function () { return _converse.xmppstatus.get('fullname'); @@ -1989,7 +1989,7 @@ function (done, _converse) { var contact, sent_stanza, IQ_id, stanza; - test_utils.waitUntilFeatureSupportConfirmed(_converse, 'localhost', 'vcard-temp') + test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp']) .then(function () { return test_utils.waitUntil(function () { return _converse.xmppstatus.get('fullname'); diff --git a/spec/chatroom.js b/spec/chatroom.js index 4840c24dc..dd652c33f 100644 --- a/spec/chatroom.js +++ b/spec/chatroom.js @@ -826,7 +826,7 @@ null, ['rosterGroupsFetched'], {}, function (done, _converse) { - test_utils.waitUntilFeatureSupportConfirmed(_converse, 'localhost', 'vcard-temp') + test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp']) .then(function () { return test_utils.waitUntil(function () { return _converse.xmppstatus.get('fullname'); @@ -891,221 +891,215 @@ _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'}); view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); + spyOn(view, 'saveAffiliationAndRole').and.callThrough(); + + // We pretend this is a new room, so no disco info is returned. + var features_stanza = $iq({ + from: 'coven@chat.shakespeare.lit', + 'id': IQ_id, + 'to': 'dummy@localhost/desktop', + 'type': 'error' + }).c('error', {'type': 'cancel'}) + .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"}); + _converse.connection._dataRecv(test_utils.createRequest(features_stanza)); + + /* + * + * + * + * + * + */ + var presence = $pres({ + to: 'dummy@localhost/_converse.js-29092160', + from: 'coven@chat.shakespeare.lit/some1' + }).c('x', {xmlns: Strophe.NS.MUC_USER}) + .c('item', { + 'affiliation': 'owner', + 'jid': 'dummy@localhost/_converse.js-29092160', + 'role': 'moderator' + }).up() + .c('status', {code: '110'}); + _converse.connection._dataRecv(test_utils.createRequest(presence)); + expect(view.saveAffiliationAndRole).toHaveBeenCalled(); + expect($(view.el.querySelector('.toggle-chatbox-button')).is(':visible')).toBeTruthy(); + test_utils.waitUntil(function () { - return !_.isNull(view.el.querySelector('.toggle-bookmark')); + return !_.isNull(view.el.querySelector('.configure-chatroom-button')); }, 300).then(function () { + expect($(view.el.querySelector('.configure-chatroom-button')).is(':visible')).toBeTruthy(); - spyOn(view, 'saveAffiliationAndRole').and.callThrough(); + view.el.querySelector('.configure-chatroom-button').click(); - // We pretend this is a new room, so no disco info is returned. - var features_stanza = $iq({ - from: 'coven@chat.shakespeare.lit', - 'id': IQ_id, - 'to': 'dummy@localhost/desktop', - 'type': 'error' - }).c('error', {'type': 'cancel'}) - .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"}); - _converse.connection._dataRecv(test_utils.createRequest(features_stanza)); - - /* - * - * - * - * - * + /* Check that an IQ is sent out, asking for the + * configuration form. + * See: // http://xmpp.org/extensions/xep-0045.html#example-163 + * + * + * + * */ - var presence = $pres({ - to: 'dummy@localhost/_converse.js-29092160', - from: 'coven@chat.shakespeare.lit/some1' - }).c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'owner', - 'jid': 'dummy@localhost/_converse.js-29092160', - 'role': 'moderator' - }).up() - .c('status', {code: '110'}); - _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect(view.saveAffiliationAndRole).toHaveBeenCalled(); - expect($(view.el.querySelector('.toggle-chatbox-button')).is(':visible')).toBeTruthy(); - expect($(view.el.querySelector('.toggle-bookmark')).is(':visible')).toBeTruthy(); + expect(sent_IQ.toLocaleString()).toBe( + ""+ + ""+ + ""); + + /* Server responds with the configuration form. + * See: // http://xmpp.org/extensions/xep-0045.html#example-165 + */ + var config_stanza = $iq({from: 'coven@chat.shakespeare.lit', + 'id': IQ_id, + 'to': 'dummy@localhost/desktop', + 'type': 'result'}) + .c('query', { 'xmlns': 'http://jabber.org/protocol/muc#owner'}) + .c('x', { 'xmlns': 'jabber:x:data', 'type': 'form'}) + .c('title').t('Configuration for "coven" Room').up() + .c('instructions').t('Complete this form to modify the configuration of your room.').up() + .c('field', {'type': 'hidden', 'var': 'FORM_TYPE'}) + .c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up() + .c('field', { + 'label': 'Natural-Language Room Name', + 'type': 'text-single', + 'var': 'muc#roomconfig_roomname'}) + .c('value').t('A Dark Cave').up().up() + .c('field', { + 'label': 'Short Description of Room', + 'type': 'text-single', + 'var': 'muc#roomconfig_roomdesc'}) + .c('value').t('The place for all good witches!').up().up() + .c('field', { + 'label': 'Enable Public Logging?', + 'type': 'boolean', + 'var': 'muc#roomconfig_enablelogging'}) + .c('value').t(0).up().up() + .c('field', { + 'label': 'Allow Occupants to Change Subject?', + 'type': 'boolean', + 'var': 'muc#roomconfig_changesubject'}) + .c('value').t(0).up().up() + .c('field', { + 'label': 'Allow Occupants to Invite Others?', + 'type': 'boolean', + 'var': 'muc#roomconfig_allowinvites'}) + .c('value').t(0).up().up() + .c('field', { + 'label': 'Who Can Send Private Messages?', + 'type': 'list-single', + 'var': 'muc#roomconfig_allowpm'}) + .c('value').t('anyone').up() + .c('option', {'label': 'Anyone'}) + .c('value').t('anyone').up().up() + .c('option', {'label': 'Anyone with Voice'}) + .c('value').t('participants').up().up() + .c('option', {'label': 'Moderators Only'}) + .c('value').t('moderators').up().up() + .c('option', {'label': 'Nobody'}) + .c('value').t('none').up().up().up() + .c('field', { + 'label': 'Roles for which Presence is Broadcasted', + 'type': 'list-multi', + 'var': 'muc#roomconfig_presencebroadcast'}) + .c('value').t('moderator').up() + .c('value').t('participant').up() + .c('value').t('visitor').up() + .c('option', {'label': 'Moderator'}) + .c('value').t('moderator').up().up() + .c('option', {'label': 'Participant'}) + .c('value').t('participant').up().up() + .c('option', {'label': 'Visitor'}) + .c('value').t('visitor').up().up().up() + .c('field', { + 'label': 'Roles and Affiliations that May Retrieve Member List', + 'type': 'list-multi', + 'var': 'muc#roomconfig_getmemberlist'}) + .c('value').t('moderator').up() + .c('value').t('participant').up() + .c('value').t('visitor').up() + .c('option', {'label': 'Moderator'}) + .c('value').t('moderator').up().up() + .c('option', {'label': 'Participant'}) + .c('value').t('participant').up().up() + .c('option', {'label': 'Visitor'}) + .c('value').t('visitor').up().up().up() + .c('field', { + 'label': 'Make Room Publicly Searchable?', + 'type': 'boolean', + 'var': 'muc#roomconfig_publicroom'}) + .c('value').t(0).up().up() + .c('field', { + 'label': 'Make Room Publicly Searchable?', + 'type': 'boolean', + 'var': 'muc#roomconfig_publicroom'}) + .c('value').t(0).up().up() + .c('field', { + 'label': 'Make Room Persistent?', + 'type': 'boolean', + 'var': 'muc#roomconfig_persistentroom'}) + .c('value').t(0).up().up() + .c('field', { + 'label': 'Make Room Moderated?', + 'type': 'boolean', + 'var': 'muc#roomconfig_moderatedroom'}) + .c('value').t(0).up().up() + .c('field', { + 'label': 'Make Room Members Only?', + 'type': 'boolean', + 'var': 'muc#roomconfig_membersonly'}) + .c('value').t(0).up().up() + .c('field', { + 'label': 'Password Required for Entry?', + 'type': 'boolean', + 'var': 'muc#roomconfig_passwordprotectedroom'}) + .c('value').t(1).up().up() + .c('field', {'type': 'fixed'}) + .c('value').t('If a password is required to enter this room,'+ + 'you must specify the password below.').up().up() + .c('field', { + 'label': 'Password', + 'type': 'text-private', + 'var': 'muc#roomconfig_roomsecret'}) + .c('value').t('cauldronburn'); + _converse.connection._dataRecv(test_utils.createRequest(config_stanza)); test_utils.waitUntil(function () { - return !_.isNull(view.el.querySelector('.configure-chatroom-button')); + return $(view.el.querySelector('form.chatroom-form')).length; }, 300).then(function () { - expect($(view.el.querySelector('.configure-chatroom-button')).is(':visible')).toBeTruthy(); + expect($(view.el.querySelector('form.chatroom-form')).length).toBe(1); + expect(view.el.querySelectorAll('form.chatroom-form fieldset').length).toBe(2); + var $membersonly = $(view.el.querySelector('input[name="muc#roomconfig_membersonly"]')); + expect($membersonly.length).toBe(1); + expect($membersonly.attr('type')).toBe('checkbox'); + $membersonly.prop('checked', true); - view.el.querySelector('.configure-chatroom-button').click(); + var $moderated = $(view.el.querySelector('input[name="muc#roomconfig_moderatedroom"]')); + expect($moderated.length).toBe(1); + expect($moderated.attr('type')).toBe('checkbox'); + $moderated.prop('checked', true); - /* Check that an IQ is sent out, asking for the - * configuration form. - * See: // http://xmpp.org/extensions/xep-0045.html#example-163 - * - * - * - * - */ - expect(sent_IQ.toLocaleString()).toBe( - ""+ - ""+ - ""); + var $password = $(view.el.querySelector('input[name="muc#roomconfig_roomsecret"]')); + expect($password.length).toBe(1); + expect($password.attr('type')).toBe('password'); - /* Server responds with the configuration form. - * See: // http://xmpp.org/extensions/xep-0045.html#example-165 - */ - var config_stanza = $iq({from: 'coven@chat.shakespeare.lit', - 'id': IQ_id, - 'to': 'dummy@localhost/desktop', - 'type': 'result'}) - .c('query', { 'xmlns': 'http://jabber.org/protocol/muc#owner'}) - .c('x', { 'xmlns': 'jabber:x:data', 'type': 'form'}) - .c('title').t('Configuration for "coven" Room').up() - .c('instructions').t('Complete this form to modify the configuration of your room.').up() - .c('field', {'type': 'hidden', 'var': 'FORM_TYPE'}) - .c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up() - .c('field', { - 'label': 'Natural-Language Room Name', - 'type': 'text-single', - 'var': 'muc#roomconfig_roomname'}) - .c('value').t('A Dark Cave').up().up() - .c('field', { - 'label': 'Short Description of Room', - 'type': 'text-single', - 'var': 'muc#roomconfig_roomdesc'}) - .c('value').t('The place for all good witches!').up().up() - .c('field', { - 'label': 'Enable Public Logging?', - 'type': 'boolean', - 'var': 'muc#roomconfig_enablelogging'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Allow Occupants to Change Subject?', - 'type': 'boolean', - 'var': 'muc#roomconfig_changesubject'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Allow Occupants to Invite Others?', - 'type': 'boolean', - 'var': 'muc#roomconfig_allowinvites'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Who Can Send Private Messages?', - 'type': 'list-single', - 'var': 'muc#roomconfig_allowpm'}) - .c('value').t('anyone').up() - .c('option', {'label': 'Anyone'}) - .c('value').t('anyone').up().up() - .c('option', {'label': 'Anyone with Voice'}) - .c('value').t('participants').up().up() - .c('option', {'label': 'Moderators Only'}) - .c('value').t('moderators').up().up() - .c('option', {'label': 'Nobody'}) - .c('value').t('none').up().up().up() - .c('field', { - 'label': 'Roles for which Presence is Broadcasted', - 'type': 'list-multi', - 'var': 'muc#roomconfig_presencebroadcast'}) - .c('value').t('moderator').up() - .c('value').t('participant').up() - .c('value').t('visitor').up() - .c('option', {'label': 'Moderator'}) - .c('value').t('moderator').up().up() - .c('option', {'label': 'Participant'}) - .c('value').t('participant').up().up() - .c('option', {'label': 'Visitor'}) - .c('value').t('visitor').up().up().up() - .c('field', { - 'label': 'Roles and Affiliations that May Retrieve Member List', - 'type': 'list-multi', - 'var': 'muc#roomconfig_getmemberlist'}) - .c('value').t('moderator').up() - .c('value').t('participant').up() - .c('value').t('visitor').up() - .c('option', {'label': 'Moderator'}) - .c('value').t('moderator').up().up() - .c('option', {'label': 'Participant'}) - .c('value').t('participant').up().up() - .c('option', {'label': 'Visitor'}) - .c('value').t('visitor').up().up().up() - .c('field', { - 'label': 'Make Room Publicly Searchable?', - 'type': 'boolean', - 'var': 'muc#roomconfig_publicroom'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Make Room Publicly Searchable?', - 'type': 'boolean', - 'var': 'muc#roomconfig_publicroom'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Make Room Persistent?', - 'type': 'boolean', - 'var': 'muc#roomconfig_persistentroom'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Make Room Moderated?', - 'type': 'boolean', - 'var': 'muc#roomconfig_moderatedroom'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Make Room Members Only?', - 'type': 'boolean', - 'var': 'muc#roomconfig_membersonly'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Password Required for Entry?', - 'type': 'boolean', - 'var': 'muc#roomconfig_passwordprotectedroom'}) - .c('value').t(1).up().up() - .c('field', {'type': 'fixed'}) - .c('value').t('If a password is required to enter this room,'+ - 'you must specify the password below.').up().up() - .c('field', { - 'label': 'Password', - 'type': 'text-private', - 'var': 'muc#roomconfig_roomsecret'}) - .c('value').t('cauldronburn'); - _converse.connection._dataRecv(test_utils.createRequest(config_stanza)); + var $allowpm = $(view.el.querySelector('select[name="muc#roomconfig_allowpm"]')); + expect($allowpm.length).toBe(1); + $allowpm.val('moderators'); - test_utils.waitUntil(function () { - return $(view.el.querySelector('form.chatroom-form')).length; - }, 300).then(function () { - expect($(view.el.querySelector('form.chatroom-form')).length).toBe(1); - expect(view.el.querySelectorAll('form.chatroom-form fieldset').length).toBe(2); - var $membersonly = $(view.el.querySelector('input[name="muc#roomconfig_membersonly"]')); - expect($membersonly.length).toBe(1); - expect($membersonly.attr('type')).toBe('checkbox'); - $membersonly.prop('checked', true); + var $presencebroadcast = $(view.el.querySelector('select[name="muc#roomconfig_presencebroadcast"]')); + expect($presencebroadcast.length).toBe(1); + $presencebroadcast.val(['moderator']); - var $moderated = $(view.el.querySelector('input[name="muc#roomconfig_moderatedroom"]')); - expect($moderated.length).toBe(1); - expect($moderated.attr('type')).toBe('checkbox'); - $moderated.prop('checked', true); + view.el.querySelector('input[type="submit"]').click(); - var $password = $(view.el.querySelector('input[name="muc#roomconfig_roomsecret"]')); - expect($password.length).toBe(1); - expect($password.attr('type')).toBe('password'); - - var $allowpm = $(view.el.querySelector('select[name="muc#roomconfig_allowpm"]')); - expect($allowpm.length).toBe(1); - $allowpm.val('moderators'); - - var $presencebroadcast = $(view.el.querySelector('select[name="muc#roomconfig_presencebroadcast"]')); - expect($presencebroadcast.length).toBe(1); - $presencebroadcast.val(['moderator']); - - view.el.querySelector('input[type="submit"]').click(); - - var $sent_stanza = $(sent_IQ.toLocaleString()); - expect($sent_stanza.find('field[var="muc#roomconfig_membersonly"] value').text()).toBe('1'); - expect($sent_stanza.find('field[var="muc#roomconfig_moderatedroom"] value').text()).toBe('1'); - expect($sent_stanza.find('field[var="muc#roomconfig_allowpm"] value').text()).toBe('moderators'); - expect($sent_stanza.find('field[var="muc#roomconfig_presencebroadcast"] value').text()).toBe('moderator'); - done(); - }); + var $sent_stanza = $(sent_IQ.toLocaleString()); + expect($sent_stanza.find('field[var="muc#roomconfig_membersonly"] value').text()).toBe('1'); + expect($sent_stanza.find('field[var="muc#roomconfig_moderatedroom"] value').text()).toBe('1'); + expect($sent_stanza.find('field[var="muc#roomconfig_allowpm"] value').text()).toBe('moderators'); + expect($sent_stanza.find('field[var="muc#roomconfig_presencebroadcast"] value').text()).toBe('moderator'); + done(); }); }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); })); @@ -1985,28 +1979,24 @@ var view = _converse.chatboxviews.get('lounge@localhost'), trimmed_chatboxes = _converse.minimized_chats; - test_utils.waitUntil(function () { - return !_.isNull(view.el.querySelector('.toggle-bookmark')); - }, 300).then(function () { - spyOn(view, 'minimize').and.callThrough(); - spyOn(view, 'maximize').and.callThrough(); - spyOn(_converse, 'emit'); - view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called - view.el.querySelector('.toggle-chatbox-button').click(); + spyOn(view, 'minimize').and.callThrough(); + spyOn(view, 'maximize').and.callThrough(); + spyOn(_converse, 'emit'); + view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called + view.el.querySelector('.toggle-chatbox-button').click(); - expect(view.minimize).toHaveBeenCalled(); - expect(_converse.emit).toHaveBeenCalledWith('chatBoxMinimized', jasmine.any(Object)); - expect(u.isVisible(view.el)).toBeFalsy(); - expect(view.model.get('minimized')).toBeTruthy(); - expect(view.minimize).toHaveBeenCalled(); - var trimmedview = trimmed_chatboxes.get(view.model.get('id')); - trimmedview.el.querySelector("a.restore-chat").click(); - expect(view.maximize).toHaveBeenCalled(); - expect(_converse.emit).toHaveBeenCalledWith('chatBoxMaximized', jasmine.any(Object)); - expect(view.model.get('minimized')).toBeFalsy(); - expect(_converse.emit.calls.count(), 3); - done(); - }); + expect(view.minimize).toHaveBeenCalled(); + expect(_converse.emit).toHaveBeenCalledWith('chatBoxMinimized', jasmine.any(Object)); + expect(u.isVisible(view.el)).toBeFalsy(); + expect(view.model.get('minimized')).toBeTruthy(); + expect(view.minimize).toHaveBeenCalled(); + var trimmedview = trimmed_chatboxes.get(view.model.get('id')); + trimmedview.el.querySelector("a.restore-chat").click(); + expect(view.maximize).toHaveBeenCalled(); + expect(_converse.emit).toHaveBeenCalledWith('chatBoxMaximized', jasmine.any(Object)); + expect(view.model.get('minimized')).toBeFalsy(); + expect(_converse.emit.calls.count(), 3); + done(); })); it("can be closed again by clicking a DOM element with class 'close-chatbox-button'", @@ -2016,23 +2006,19 @@ test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); var view = _converse.chatboxviews.get('lounge@localhost'); - test_utils.waitUntil(function () { - return !_.isNull(view.el.querySelector('.toggle-bookmark')); - }, 300).then(function () { - spyOn(view, 'close').and.callThrough(); - spyOn(_converse, 'emit'); - spyOn(view, 'leave'); - view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called - view.el.querySelector('.close-chatbox-button').click(); - expect(view.close).toHaveBeenCalled(); - expect(view.leave).toHaveBeenCalled(); - // XXX: After refactoring, the chat box only gets closed - // once we have confirmation from the server. To test this, - // we would have to mock the returned presence stanza. - // See the "leave" method on the ChatRoomView. - // expect(_converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object)); - done(); - }); + spyOn(view, 'close').and.callThrough(); + spyOn(_converse, 'emit'); + spyOn(view, 'leave'); + view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called + view.el.querySelector('.close-chatbox-button').click(); + expect(view.close).toHaveBeenCalled(); + expect(view.leave).toHaveBeenCalled(); + // XXX: After refactoring, the chat box only gets closed + // once we have confirmation from the server. To test this, + // we would have to mock the returned presence stanza. + // See the "leave" method on the ChatRoomView. + // expect(_converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object)); + done(); })); }); diff --git a/spec/protocol.js b/spec/protocol.js index cc401f2f9..129184a3e 100644 --- a/spec/protocol.js +++ b/spec/protocol.js @@ -55,7 +55,7 @@ function (done, _converse) { var contact, sent_stanza, IQ_id, stanza; - test_utils.waitUntilFeatureSupportConfirmed(_converse, 'localhost', 'vcard-temp') + test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp']) .then(function () { return test_utils.waitUntil(function () { return _converse.xmppstatus.get('fullname'); diff --git a/src/converse-bookmarks.js b/src/converse-bookmarks.js index ca3baa4c3..9b45c8a46 100644 --- a/src/converse-bookmarks.js +++ b/src/converse-bookmarks.js @@ -71,32 +71,28 @@ this.setBookmarkState(); }, - renderHeading () { + renderBookmarkToggle () { const { _converse } = this.__super__, { __ } = _converse; + const bookmark_button = tpl_chatroom_bookmark_toggle( + _.assignIn(this.model.toJSON(), { + info_toggle_bookmark: __('Bookmark this room'), + bookmarked: this.model.get('bookmarked') + })); + const close_button = this.el.querySelector('.close-chatbox-button'); + close_button.insertAdjacentHTML('afterend', bookmark_button); + }, + renderHeading () { + this.__super__.renderHeading.apply(this, arguments); + const { _converse } = this.__super__; if (_converse.allow_bookmarks) { _converse.api.disco.getIdentity('pubsub', 'pep', _converse.bare_jid).then((identity) => { if (_.isNil(identity)) { return; } - const div = document.createElement('div'); - div.innerHTML = this.generateHeadingHTML(); - - const bookmark_button = tpl_chatroom_bookmark_toggle( - _.assignIn( - this.model.toJSON(), - { - info_toggle_bookmark: __('Bookmark this room'), - bookmarked: this.model.get('bookmarked') - } - )); - const close_button = div.querySelector('.close-chatbox-button'); - close_button.insertAdjacentHTML('afterend', bookmark_button); - this.el.querySelector('.chat-head-chatroom').innerHTML = div.innerHTML; - }); - } else { - return this.__super__.renderHeading.apply(this, arguments); + this.renderBookmarkToggle(); + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); } }, @@ -537,9 +533,14 @@ if (!_converse.allow_bookmarks) { return; } - // Only initialize bookmarks if the server supports PEP - _converse.api.disco.getIdentity('pubsub', 'pep', _converse.bare_jid).then((identity) => { - if (_.isNil(identity)) { + Promise.all([ + _converse.api.disco.getIdentity('pubsub', 'pep', _converse.bare_jid), + _converse.api.disco.supports(Strophe.NS.PUBSUB+'#publish-options', _converse.bare_jid) + ]).then((args) => { + const identity = args[0], + options_support = args[1]; + + if (_.isNil(identity) || !options_support.supported) { _converse.emit('bookmarksInitialized'); return; } diff --git a/tests/mock.js b/tests/mock.js index 889a276aa..037ee14c3 100644 --- a/tests/mock.js +++ b/tests/mock.js @@ -119,13 +119,6 @@ }, settings || {})); _converse.ChatBoxViews.prototype.trimChat = function () {}; - var entity = _converse.api.disco.entities.get(_converse.bare_jid, true); - entity.identities.create({ - 'category': 'pubsub', - 'type': 'pep' - }); - entity.waitUntilFeaturesDiscovered.resolve(); - window.converse_disable_effects = true; return _converse; } diff --git a/tests/utils.js b/tests/utils.js index d10e9ecd5..c5e300fe0 100644 --- a/tests/utils.js +++ b/tests/utils.js @@ -14,7 +14,7 @@ } utils.waitUntil = waitUntilPromise.default; - utils.waitUntilFeatureSupportConfirmed = function (_converse, entity_jid, feature_name) { + utils.waitUntilDiscoConfirmed = function (_converse, entity_jid, identities, features) { var IQ_disco, stanza; return utils.waitUntil(function () { IQ_disco = _.filter(_converse.connection.IQ_stanzas, function (iq) { @@ -24,13 +24,19 @@ return !_.isUndefined(IQ_disco); }, 300).then(function () { var info_IQ_id = IQ_disco.nodeTree.getAttribute('id'); - stanza = $iq({ + var stanza = $iq({ 'type': 'result', - 'from': 'localhost', + 'from': entity_jid, 'to': 'dummy@localhost/resource', 'id': info_IQ_id - }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'}) - .c('feature', {'var': feature_name}); + }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'}); + + _.forEach(identities, function (identity) { + stanza.c('identity', {'category': 'pubsub', 'type': 'pep'}).up() + }); + _.forEach(features, function (feature) { + stanza.c('feature', {'var': feature}).up(); + }); _converse.connection._dataRecv(utils.createRequest(stanza)); }); }