(function (root, factory) { define(["mock", "converse-api", "test_utils", "utils" ], factory); } (this, function (mock, converse, test_utils, utils) { var _ = converse.env._; var $ = converse.env.jQuery; var $pres = converse.env.$pres; var $iq = converse.env.$iq; var $msg = converse.env.$msg; var Strophe = converse.env.Strophe; return describe("ChatRooms", function () { describe("The \"rooms\" API", function () { it("has a method 'close' which closes rooms by JID or all rooms when called with no arguments", mock.initConverse(function (_converse) { test_utils.createContacts(_converse, 'current'); runs(function () { test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); test_utils.openChatRoom(_converse, 'leisure', 'localhost', 'dummy'); test_utils.openChatRoom(_converse, 'news', 'localhost', 'dummy'); expect(_converse.chatboxviews.get('lounge@localhost').$el.is(':visible')).toBeTruthy(); expect(_converse.chatboxviews.get('leisure@localhost').$el.is(':visible')).toBeTruthy(); expect(_converse.chatboxviews.get('news@localhost').$el.is(':visible')).toBeTruthy(); }); waits('100'); runs(function () { _converse.api.rooms.close('lounge@localhost'); expect(_converse.chatboxviews.get('lounge@localhost')).toBeUndefined(); expect(_converse.chatboxviews.get('leisure@localhost').$el.is(':visible')).toBeTruthy(); expect(_converse.chatboxviews.get('news@localhost').$el.is(':visible')).toBeTruthy(); _converse.api.rooms.close(['leisure@localhost', 'news@localhost']); expect(_converse.chatboxviews.get('lounge@localhost')).toBeUndefined(); expect(_converse.chatboxviews.get('leisure@localhost')).toBeUndefined(); expect(_converse.chatboxviews.get('news@localhost')).toBeUndefined(); test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); test_utils.openChatRoom(_converse, 'leisure', 'localhost', 'dummy'); expect(_converse.chatboxviews.get('lounge@localhost').$el.is(':visible')).toBeTruthy(); expect(_converse.chatboxviews.get('leisure@localhost').$el.is(':visible')).toBeTruthy(); }); waits('100'); runs(function () { _converse.api.rooms.close(); expect(_converse.chatboxviews.get('lounge@localhost')).toBeUndefined(); expect(_converse.chatboxviews.get('leisure@localhost')).toBeUndefined(); }); })); it("has a method 'get' which returns a wrapped chat room (if it exists)", mock.initConverse(function (_converse) { test_utils.createContacts(_converse, 'current'); waits('300'); // ChatBox.show() is debounced for 250ms runs(function () { test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); var jid = 'lounge@localhost'; var room = _converse.api.rooms.get(jid); expect(room instanceof Object).toBeTruthy(); expect(room.is_chatroom).toBeTruthy(); var chatroomview = _converse.chatboxviews.get(jid); expect(chatroomview.$el.is(':visible')).toBeTruthy(); chatroomview.close(); }); waits('300'); // ChatBox.show() is debounced for 250ms runs(function () { // Test with mixed case test_utils.openChatRoom(_converse, 'Leisure', 'localhost', 'dummy'); var jid = 'Leisure@localhost'; var room = _converse.api.rooms.get(jid); expect(room instanceof Object).toBeTruthy(); var chatroomview = _converse.chatboxviews.get(jid.toLowerCase()); expect(chatroomview.$el.is(':visible')).toBeTruthy(); }); waits('300'); // ChatBox.show() is debounced for 250ms runs(function () { var jid = 'leisure@localhost'; var room = _converse.api.rooms.get(jid); expect(room instanceof Object).toBeTruthy(); var chatroomview = _converse.chatboxviews.get(jid.toLowerCase()); expect(chatroomview.$el.is(':visible')).toBeTruthy(); jid = 'leiSure@localhost'; room = _converse.api.rooms.get(jid); expect(room instanceof Object).toBeTruthy(); chatroomview = _converse.chatboxviews.get(jid.toLowerCase()); expect(chatroomview.$el.is(':visible')).toBeTruthy(); chatroomview.close(); // Non-existing room jid = 'lounge2@localhost'; room = _converse.api.rooms.get(jid); expect(typeof room === 'undefined').toBeTruthy(); }); })); it("has a method 'open' which opens (optionally configures) and returns a wrapped chat box", mock.initConverse(function (_converse) { test_utils.createContacts(_converse, 'current'); var chatroomview; var jid = 'lounge@localhost'; var room = _converse.api.rooms.open(jid); runs(function () { // Test on chat room that doesn't exist. expect(room instanceof Object).toBeTruthy(); expect(room.is_chatroom).toBeTruthy(); chatroomview = _converse.chatboxviews.get(jid); expect(chatroomview.$el.is(':visible')).toBeTruthy(); }); waits('300'); // ChatBox.show() is debounced for 250ms runs(function () { // Test again, now that the room exists. room = _converse.api.rooms.open(jid); expect(room instanceof Object).toBeTruthy(); expect(room.is_chatroom).toBeTruthy(); chatroomview = _converse.chatboxviews.get(jid); expect(chatroomview.$el.is(':visible')).toBeTruthy(); }); waits('300'); // ChatBox.show() is debounced for 250ms runs(function () { // Test with mixed case in JID jid = 'Leisure@localhost'; room = _converse.api.rooms.open(jid); expect(room instanceof Object).toBeTruthy(); chatroomview = _converse.chatboxviews.get(jid.toLowerCase()); expect(chatroomview.$el.is(':visible')).toBeTruthy(); jid = 'leisure@localhost'; room = _converse.api.rooms.open(jid); expect(room instanceof Object).toBeTruthy(); chatroomview = _converse.chatboxviews.get(jid.toLowerCase()); expect(chatroomview.$el.is(':visible')).toBeTruthy(); jid = 'leiSure@localhost'; room = _converse.api.rooms.open(jid); expect(room instanceof Object).toBeTruthy(); chatroomview = _converse.chatboxviews.get(jid.toLowerCase()); expect(chatroomview.$el.is(':visible')).toBeTruthy(); chatroomview.close(); }); waits('300'); // ChatBox.show() is debounced for 250ms runs(function () { _converse.muc_instant_rooms = false; var sent_IQ, IQ_id; var sendIQ = _converse.connection.sendIQ; spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) { sent_IQ = iq; IQ_id = sendIQ.bind(this)(iq, callback, errback); }); // Test with configuration _converse.api.rooms.open('room@conference.example.org', { 'nick': 'some1', 'auto_configure': true, 'roomconfig': { 'changesubject': false, 'membersonly': true, 'persistentroom': true, 'publicroom': true, 'roomdesc': 'Welcome to this room', 'whois': 'anyone' } }); // We pretend this is a new room, so no disco info is returned. var features_stanza = $iq({ from: 'room@conference.example.org', '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({ from:'room@conference.example.org/some1', to:'dummy@localhost/pda' }) .c('x', {xmlns:'http://jabber.org/protocol/muc#user'}) .c('item', { affiliation: 'owner', jid: 'dummy@localhost/pda', role: 'moderator' }).up() .c('status', {code:'110'}).up() .c('status', {code:'201'}); _converse.connection._dataRecv(test_utils.createRequest(presence)); expect(_converse.connection.sendIQ).toHaveBeenCalled(); expect(sent_IQ.toLocaleString()).toBe( "" ); _converse.connection._dataRecv(test_utils.createRequest($( ''+ ' '+ ' '+ ' Configuration for room@conference.example.org'+ ' Complete and submit this form to configure the room.'+ ' '+ ' http://jabber.org/protocol/muc#roomconfig'+ ' '+ ' '+ ' Room'+ ' '+ ' '+ ' '+ ' 1'+ ' '+ ' '+ ' '+ ' '+ ' '+ ' '+ ' '+ ' 20'+ ' '+ ' '+ ' ')[0])); var $sent_stanza = $(sent_IQ.toLocaleString()); expect($sent_stanza.find('field[var="muc#roomconfig_roomname"] value').text()).toBe('Room'); expect($sent_stanza.find('field[var="muc#roomconfig_roomdesc"] value').text()).toBe('Welcome to this room'); expect($sent_stanza.find('field[var="muc#roomconfig_persistentroom"] value').text()).toBe('1'); expect($sent_stanza.find('field[var="muc#roomconfig_publicroom"] value ').text()).toBe('1'); expect($sent_stanza.find('field[var="muc#roomconfig_changesubject"] value').text()).toBe('0'); expect($sent_stanza.find('field[var="muc#roomconfig_whois"] value ').text()).toBe('anyone'); expect($sent_stanza.find('field[var="muc#roomconfig_membersonly"] value').text()).toBe('1'); expect($sent_stanza.find('field[var="muc#roomconfig_historylength"] value').text()).toBe('20'); }); })); }); describe("A Chat Room", function () { it("can have spaces and special characters in its name", mock.initConverse(function (_converse) { test_utils.openChatRoom(_converse, 'lounge & leisure', 'localhost', 'dummy'); var view = _converse.chatboxviews.get( Strophe.escapeNode('lounge & leisure')+'@localhost'); expect(view instanceof _converse.ChatRoomView).toBe(true); })); it("can be configured if you're its owner", mock.initConverse(function (_converse) { var view; var sent_IQ, IQ_id; var sendIQ = _converse.connection.sendIQ; spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) { sent_IQ = iq; IQ_id = sendIQ.bind(this)(iq, callback, errback); }); runs(function () { _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'}); view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); spyOn(view, 'saveAffiliationAndRole').andCallThrough(); // 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.$('.configure-chatroom-button').is(':visible')).toBeTruthy(); expect(view.$('.toggle-chatbox-button').is(':visible')).toBeTruthy(); expect(view.$('.toggle-bookmark').is(':visible')).toBeTruthy(); view.$('.configure-chatroom-button').click(); }); waits(50); runs (function () { /* 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( ""+ ""+ ""); /* 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)); }); waits(50); runs (function () { expect(view.$('form.chatroom-form').length).toBe(1); expect(view.$('form.chatroom-form fieldset').length).toBe(2); var $membersonly = view.$('input[name="muc#roomconfig_membersonly"]'); expect($membersonly.length).toBe(1); expect($membersonly.attr('type')).toBe('checkbox'); $membersonly.prop('checked', true); var $moderated = view.$('input[name="muc#roomconfig_moderatedroom"]'); expect($moderated.length).toBe(1); expect($moderated.attr('type')).toBe('checkbox'); $moderated.prop('checked', true); var $password = view.$('input[name="muc#roomconfig_roomsecret"]'); expect($password.length).toBe(1); expect($password.attr('type')).toBe('password'); var $allowpm = view.$('select[name="muc#roomconfig_allowpm"]'); expect($allowpm.length).toBe(1); $allowpm.val('moderators'); var $presencebroadcast = view.$('select[name="muc#roomconfig_presencebroadcast"]'); expect($presencebroadcast.length).toBe(1); $presencebroadcast.val(['moderator']); view.$('input[type="submit"]').click(); }); waits(50); runs (function () { 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'); }); })); it("shows users currently present in the room", mock.initConverse(function (_converse) { test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy'); var name; var view = _converse.chatboxviews.get('lounge@localhost'), $occupants = view.$('.occupant-list'); var presence, role; for (var i=0; i-1; i--) { name = mock.chatroom_names[i]; role = mock.chatroom_roles[name].role; // See example 21 http://xmpp.org/extensions/xep-0045.html#enter-pres presence = $pres({ to:'dummy@localhost/pda', from:'lounge@localhost/'+name, type: 'unavailable' }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) .c('item').attrs({ affiliation: mock.chatroom_roles[name].affiliation, jid: name.replace(/ /g,'.').toLowerCase() + '@localhost', role: 'none' }).nodeTree; _converse.connection._dataRecv(test_utils.createRequest(presence)); expect($occupants.find('li').length).toBe(i+1); } })); it("indicates moderators by means of a special css class and tooltip", mock.initConverse(function (_converse) { test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy'); var view = _converse.chatboxviews.get('lounge@localhost'); var presence = $pres({ to:'dummy@localhost/pda', from:'lounge@localhost/moderatorman' }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) .c('item').attrs({ affiliation: 'admin', jid: name.replace(/ /g,'.').toLowerCase() + '@localhost', role: 'moderator', }).up() .c('status').attrs({code:'110'}).nodeTree; _converse.connection._dataRecv(test_utils.createRequest(presence)); var occupant = view.$el.find('.occupant-list').find('li'); expect(occupant.length).toBe(2); expect($(occupant).first().text()).toBe("dummy"); expect($(occupant).last().text()).toBe("moderatorman"); expect($(occupant).last().attr('class').indexOf('moderator')).not.toBe(-1); expect($(occupant).last().attr('title')).toBe('This user is a moderator. Click to mention this user in your message.'); })); it("will use the user's reserved nickname, if it exists", mock.initConverse(function (_converse) { var sent_IQ, IQ_id; var sendIQ = _converse.connection.sendIQ; spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) { sent_IQ = iq; IQ_id = sendIQ.bind(this)(iq, callback, errback); }); test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); // We pretend this is a new room, so no disco info is returned. var features_stanza = $iq({ from: 'lounge@localhost', '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 view = _converse.chatboxviews.get('lounge@localhost'); spyOn(view, 'join').andCallThrough(); /* * * */ expect(sent_IQ.toLocaleString()).toBe( ""+ "" ); /* * * * * */ var 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': 'thirdwitch', 'type': 'text'}); _converse.connection._dataRecv(test_utils.createRequest(stanza)); expect(view.join).toHaveBeenCalled(); // 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:'dummy@localhost/resource', from:'lounge@localhost/thirdwitch', id:'DC352437-C019-40EC-B590-AF29E879AF97' }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) .c('item').attrs({ affiliation: 'member', jid: 'dummy@localhost/resource', role: 'occupant' }).up() .c('status').attrs({code:'110'}).up() .c('status').attrs({code:'210'}).nodeTree; _converse.connection._dataRecv(test_utils.createRequest(presence)); var info_text = view.$el.find('.chat-content .chat-info').text(); expect(info_text).toBe('Your nickname has been automatically set to: thirdwitch'); })); it("allows the user to invite their roster contacts to enter the chat room", mock.initConverse(function (_converse) { test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); spyOn(_converse, 'emit'); spyOn(window, 'prompt').andCallFake(function () { return null; }); var $input; var view = _converse.chatboxviews.get('lounge@localhost'); view.$el.find('.chat-area').remove(); test_utils.createContacts(_converse, 'current'); // We need roster contacts, so that we have someone to invite $input = view.$el.find('input.invited-contact.tt-input'); var $hint = view.$el.find('input.invited-contact.tt-hint'); runs (function () { expect($input.length).toBe(1); expect($input.attr('placeholder')).toBe('Invite'); $input.val("Felix"); $input.trigger('input'); }); waits(350); // Needed, due to debounce runs (function () { expect($input.val()).toBe('Felix'); expect($hint.val()).toBe('Felix Amsel'); var $sugg = view.$el.find('[data-jid="felix.amsel@localhost"]'); expect($sugg.length).toBe(1); $sugg.trigger('click'); expect(window.prompt).toHaveBeenCalled(); }); })); it("can be joined automatically, based upon a received invite", mock.initConverse(function (_converse) { test_utils.createContacts(_converse, 'current'); // We need roster contacts, who can invite us spyOn(window, 'confirm').andCallFake(function () { return true; }); test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy'); var view = _converse.chatboxviews.get('lounge@localhost'); view.close(); view.model.destroy(); // Manually calling this, otherwise we have to mock stanzas. var name = mock.cur_names[0]; var from_jid = name.replace(/ /g,'.').toLowerCase() + '@localhost'; var room_jid = 'lounge@localhost'; var reason = "Please join this chat room"; var message = $( "" + ""+ "" )[0]; expect(_converse.chatboxes.models.length).toBe(1); expect(_converse.chatboxes.models[0].id).toBe("controlbox"); _converse.onDirectMUCInvitation(message); expect(window.confirm).toHaveBeenCalledWith( name + ' has invited you to join a chat room: '+ room_jid + ', and left the following reason: "'+reason+'"'); expect(_converse.chatboxes.models.length).toBe(2); expect(_converse.chatboxes.models[0].id).toBe('controlbox'); expect(_converse.chatboxes.models[1].id).toBe(room_jid); })); it("shows received groupchat messages", mock.initConverse(function (_converse) { test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); spyOn(_converse, 'emit'); var view = _converse.chatboxviews.get('lounge@localhost'); if (!view.$el.find('.chat-area').length) { view.renderChatArea(); } var nick = mock.chatroom_names[0]; var text = 'This is a received message'; var message = $msg({ from: 'lounge@localhost/'+nick, id: '1', to: 'dummy@localhost', type: 'groupchat' }).c('body').t(text); view.onChatRoomMessage(message.nodeTree); var $chat_content = view.$el.find('.chat-content'); expect($chat_content.find('.chat-message').length).toBe(1); expect($chat_content.find('.chat-msg-content').text()).toBe(text); expect(_converse.emit).toHaveBeenCalledWith('message', message.nodeTree); })); it("shows sent groupchat messages", mock.initConverse(function (_converse) { test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy'); spyOn(_converse, 'emit'); var view = _converse.chatboxviews.get('lounge@localhost'); if (!view.$el.find('.chat-area').length) { view.renderChatArea(); } var text = 'This is a sent message'; view.$el.find('.chat-textarea').text(text); view.$el.find('textarea.chat-textarea').trigger($.Event('keypress', {keyCode: 13})); expect(_converse.emit).toHaveBeenCalledWith('messageSend', text); var $chat_content = view.$el.find('.chat-content'); expect($chat_content.find('.chat-message').length).toBe(1); // Let's check that if we receive the same message again, it's // not shown. var message = $msg({ from: 'lounge@localhost/dummy', to: 'dummy@localhost.com', type: 'groupchat', id: view.model.messages.at(0).get('msgid') }).c('body').t(text); view.onChatRoomMessage(message.nodeTree); expect($chat_content.find('.chat-message').length).toBe(1); expect($chat_content.find('.chat-msg-content').last().text()).toBe(text); // We don't emit an event if it's our own message expect(_converse.emit.callCount, 1); })); it("will cause the chat area to be scrolled down only if it was at the bottom already", mock.initConverse(function (_converse) { var message = 'This message is received while the chat area is scrolled up'; test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy'); var view = _converse.chatboxviews.get('lounge@localhost'); spyOn(view, 'scrollDown').andCallThrough(); runs(function () { /* Create enough messages so that there's a * scrollbar. */ for (var i=0; i<20; i++) { _converse.chatboxes.onMessage( $msg({ from: 'lounge@localhost/someone', to: 'dummy@localhost.com', type: 'groupchat', id: (new Date()).getTime(), }).c('body').t('Message: '+i).tree()); } }); waits(50); runs(function () { view.$content.scrollTop(0); }); waits(250); runs(function () { expect(view.model.get('scrolled')).toBeTruthy(); _converse.chatboxes.onMessage( $msg({ from: 'lounge@localhost/someone', to: 'dummy@localhost.com', type: 'groupchat', id: (new Date()).getTime(), }).c('body').t(message).tree()); }); waits(150); runs(function () { // Now check that the message appears inside the chatbox in the DOM var $chat_content = view.$el.find('.chat-content'); var msg_txt = $chat_content.find('.chat-message:last').find('.chat-msg-content').text(); expect(msg_txt).toEqual(message); expect(view.$content.scrollTop()).toBe(0); }); })); it("shows received chatroom subject messages", mock.initConverse(function (_converse) { test_utils.openAndEnterChatRoom(_converse, 'jdev', 'conference.jabber.org', 'jc'); var text = 'Jabber/XMPP Development | RFCs and Extensions: http://xmpp.org/ | Protocol and XSF discussions: xsf@muc.xmpp.org'; var stanza = Strophe.xmlHtmlNode( ''+ ' '+text+''+ ' '+ ' '+ '').firstChild; _converse.connection._dataRecv(test_utils.createRequest(stanza)); var view = _converse.chatboxviews.get('jdev@conference.jabber.org'); var $chat_content = view.$el.find('.chat-content'); expect($chat_content.find('.chat-info').length).toBe(1); expect($chat_content.find('.chat-info').text()).toBe('Topic set by ralphm to: '+text); })); it("informs users if their nicknames has been changed.", mock.initConverse(function (_converse) { /* The service then sends two presence stanzas to the full JID * of each occupant (including the occupant who is changing his * or her room nickname), one of type "unavailable" for the old * nickname and one indicating availability for the new * nickname. * * See: http://xmpp.org/extensions/xep-0045.html#changenick * * * * * * * * * * * * * * * */ var __ = utils.__.bind(_converse); test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'oldnick'); var view = _converse.chatboxviews.get('lounge@localhost'); var $chat_content = view.$el.find('.chat-content'); // The user has just entered the room and receives their own // presence from the server. // See example 24: // http://xmpp.org/extensions/xep-0045.html#enter-pres var presence = $pres({ to:'dummy@localhost/pda', from:'lounge@localhost/oldnick', id:'DC352437-C019-40EC-B590-AF29E879AF97' }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) .c('item').attrs({ affiliation: 'member', jid: 'dummy@localhost/pda', role: 'occupant' }).up() .c('status').attrs({code:'110'}).up() .c('status').attrs({code:'210'}).nodeTree; _converse.connection._dataRecv(test_utils.createRequest(presence)); var $occupants = view.$('.occupant-list'); expect($occupants.children().length).toBe(1); expect($occupants.children().first(0).text()).toBe("oldnick"); expect($chat_content.find('div.chat-info').length).toBe(1); expect($chat_content.find('div.chat-info').html()).toBe(__(_converse.muc.new_nickname_messages["210"], "oldnick")); presence = $pres().attrs({ from:'lounge@localhost/oldnick', id:'DC352437-C019-40EC-B590-AF29E879AF98', to:'dummy@localhost/pda', type:'unavailable' }) .c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) .c('item').attrs({ affiliation: 'member', jid: 'dummy@localhost/pda', nick: 'newnick', role: 'occupant' }).up() .c('status').attrs({code:'303'}).up() .c('status').attrs({code:'110'}).nodeTree; _converse.connection._dataRecv(test_utils.createRequest(presence)); expect($chat_content.find('div.chat-info').length).toBe(2); expect($chat_content.find('div.chat-info').last().html()).toBe(__(_converse.muc.new_nickname_messages["303"], "newnick")); $occupants = view.$('.occupant-list'); expect($occupants.children().length).toBe(0); presence = $pres().attrs({ from:'lounge@localhost/newnick', id:'5B4F27A4-25ED-43F7-A699-382C6B4AFC67', to:'dummy@localhost/pda' }) .c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) .c('item').attrs({ affiliation: 'member', jid: 'dummy@localhost/pda', role: 'occupant' }).up() .c('status').attrs({code:'110'}).nodeTree; _converse.connection._dataRecv(test_utils.createRequest(presence)); expect($chat_content.find('div.chat-info').length).toBe(2); expect($chat_content.find('div.chat-info').last().html()).toBe(__(_converse.muc.new_nickname_messages["303"], "newnick")); $occupants = view.$('.occupant-list'); expect($occupants.children().length).toBe(1); expect($occupants.children().first(0).text()).toBe("newnick"); })); it("queries for the room information before attempting to join the user", mock.initConverse(function (_converse) { var sent_IQ, IQ_id; var sendIQ = _converse.connection.sendIQ; spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) { sent_IQ = iq; IQ_id = sendIQ.bind(this)(iq, callback, errback); }); _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'}); // Check that the room queried for the feautures. expect(sent_IQ.toLocaleString()).toBe( ""+ ""+ ""); /* * * * * * * * * * * * */ var features_stanza = $iq({ from: 'coven@chat.shakespeare.lit', 'id': IQ_id, 'to': 'dummy@localhost/desktop', 'type': 'result' }) .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'}) .c('identity', { 'category': 'conference', 'name': 'A Dark Cave', 'type': 'text' }).up() .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up() .c('feature', {'var': 'muc_passwordprotected'}).up() .c('feature', {'var': 'muc_hidden'}).up() .c('feature', {'var': 'muc_temporary'}).up() .c('feature', {'var': 'muc_open'}).up() .c('feature', {'var': 'muc_unmoderated'}).up() .c('feature', {'var': 'muc_nonanonymous'}); _converse.connection._dataRecv(test_utils.createRequest(features_stanza)); var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); expect(view.model.get('features_fetched')).toBe(true); expect(view.model.get('passwordprotected')).toBe(true); expect(view.model.get('hidden')).toBe(true); expect(view.model.get('temporary')).toBe(true); expect(view.model.get('open')).toBe(true); expect(view.model.get('unmoderated')).toBe(true); expect(view.model.get('nonanonymous')).toBe(true); })); it("indicates when a room is no longer anonymous", mock.initConverse(function (_converse) { var sent_IQ, IQ_id; var sendIQ = _converse.connection.sendIQ; spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) { sent_IQ = iq; IQ_id = sendIQ.bind(this)(iq, callback, errback); }); _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'}); // 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 view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); /* * * * * * */ var message = $msg({ type:'groupchat', to: 'dummy@localhost/_converse.js-27854181', from: 'coven@chat.shakespeare.lit' }).c('x', {xmlns: Strophe.NS.MUC_USER}) .c('status', {code: '104'}).up() .c('status', {code: '172'}); _converse.connection._dataRecv(test_utils.createRequest(message)); var $chat_body = view.$('.chatroom-body'); expect($chat_body.html().trim().indexOf( '
This room is now no longer anonymous
' )).not.toBe(-1); })); it("informs users if they have been kicked out of the chat room", mock.initConverse(function (_converse) { /* * * * * Avaunt, you cullion! * * * * * */ test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy'); var presence = $pres().attrs({ from:'lounge@localhost/dummy', to:'dummy@localhost/pda', type:'unavailable' }) .c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) .c('item').attrs({ affiliation: 'none', jid: 'dummy@localhost/pda', role: 'none' }) .c('actor').attrs({nick: 'Fluellen'}).up() .c('reason').t('Avaunt, you cullion!').up() .up() .c('status').attrs({code:'110'}).up() .c('status').attrs({code:'307'}).nodeTree; var view = _converse.chatboxviews.get('lounge@localhost'); view.onChatRoomPresence(presence); expect(view.$('.chat-area').is(':visible')).toBeFalsy(); expect(view.$('.occupants').is(':visible')).toBeFalsy(); var $chat_body = view.$('.chatroom-body'); expect($chat_body.html().trim().indexOf( '

You have been kicked from this room

'+ '

This action was done by Fluellen.

'+ '

The reason given is: "Avaunt, you cullion!".

' )).not.toBe(-1); })); it("can be saved to, and retrieved from, browserStorage", mock.initConverse(function (_converse) { test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); // We instantiate a new ChatBoxes collection, which by default // will be empty. test_utils.openControlBox(); var newchatboxes = new _converse.ChatBoxes(); expect(newchatboxes.length).toEqual(0); // The chatboxes will then be fetched from browserStorage inside the // onConnected method newchatboxes.onConnected(); expect(newchatboxes.length).toEqual(2); // Check that the chatrooms retrieved from browserStorage // have the same attributes values as the original ones. var attrs = ['id', 'box_id', 'visible']; var new_attrs, old_attrs; for (var i=0; i'+ // 'This is yet another subject'+ // ''); $textarea.val('/Subject This is yet another subject'); $textarea.trigger($.Event('keypress', {keyCode: 13})); expect(sent_stanza.textContent).toBe('This is yet another subject'); })); it("to clear messages", mock.initConverse(function (_converse) { test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); var view = _converse.chatboxviews.get('lounge@localhost'); spyOn(view, 'onMessageSubmitted').andCallThrough(); spyOn(view, 'clearChatRoomMessages'); view.$el.find('.chat-textarea').text('/clear'); view.$el.find('textarea.chat-textarea').trigger($.Event('keypress', {keyCode: 13})); expect(view.onMessageSubmitted).toHaveBeenCalled(); expect(view.clearChatRoomMessages).toHaveBeenCalled(); })); it("to make a user an owner", mock.initConverse(function (_converse) { var sent_IQ, IQ_id; var sendIQ = _converse.connection.sendIQ; spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) { sent_IQ = iq; IQ_id = sendIQ.bind(this)(iq, callback, errback); }); test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); var view = _converse.chatboxviews.get('lounge@localhost'); spyOn(view, 'onMessageSubmitted').andCallThrough(); spyOn(view, 'setAffiliation').andCallThrough(); spyOn(view, 'showStatusNotification').andCallThrough(); spyOn(view, 'validateRoleChangeCommand').andCallThrough(); view.$el.find('.chat-textarea').text('/owner'); view.$el.find('textarea.chat-textarea').trigger($.Event('keypress', {keyCode: 13})); expect(view.onMessageSubmitted).toHaveBeenCalled(); expect(view.validateRoleChangeCommand).toHaveBeenCalled(); expect(view.showStatusNotification).toHaveBeenCalledWith( "Error: the \"owner\" command takes two arguments, the user's nickname and optionally a reason.", true ); expect(view.setAffiliation).not.toHaveBeenCalled(); // Call now with the correct amount of arguments. // XXX: Calling onMessageSubmitted directly, trying // again via triggering Event doesn't work for some weird // reason. view.onMessageSubmitted('/owner annoyingGuy@localhost You\'re annoying'); expect(view.validateRoleChangeCommand.callCount).toBe(2); expect(view.showStatusNotification.callCount).toBe(1); expect(view.setAffiliation).toHaveBeenCalled(); // Check that the member list now gets updated expect(sent_IQ.toLocaleString()).toBe( ""+ ""+ ""+ "You're annoying"+ ""+ ""+ ""); })); it("to ban a user", mock.initConverse(function (_converse) { var sent_IQ, IQ_id; var sendIQ = _converse.connection.sendIQ; spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) { sent_IQ = iq; IQ_id = sendIQ.bind(this)(iq, callback, errback); }); test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); var view = _converse.chatboxviews.get('lounge@localhost'); spyOn(view, 'onMessageSubmitted').andCallThrough(); spyOn(view, 'setAffiliation').andCallThrough(); spyOn(view, 'showStatusNotification').andCallThrough(); spyOn(view, 'validateRoleChangeCommand').andCallThrough(); view.$el.find('.chat-textarea').text('/ban'); view.$el.find('textarea.chat-textarea').trigger($.Event('keypress', {keyCode: 13})); expect(view.onMessageSubmitted).toHaveBeenCalled(); expect(view.validateRoleChangeCommand).toHaveBeenCalled(); expect(view.showStatusNotification).toHaveBeenCalledWith( "Error: the \"ban\" command takes two arguments, the user's nickname and optionally a reason.", true ); expect(view.setAffiliation).not.toHaveBeenCalled(); // Call now with the correct amount of arguments. // XXX: Calling onMessageSubmitted directly, trying // again via triggering Event doesn't work for some weird // reason. view.onMessageSubmitted('/ban annoyingGuy@localhost You\'re annoying'); expect(view.validateRoleChangeCommand.callCount).toBe(2); expect(view.showStatusNotification.callCount).toBe(1); expect(view.setAffiliation).toHaveBeenCalled(); // Check that the member list now gets updated expect(sent_IQ.toLocaleString()).toBe( ""+ ""+ ""+ "You're annoying"+ ""+ ""+ ""); })); }); describe("When attempting to enter a chatroom", function () { var submitRoomForm = function (_converse) { var roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; var $input = roomspanel.$el.find('input.new-chatroom-name'); var $nick = roomspanel.$el.find('input.new-chatroom-nick'); var $server = roomspanel.$el.find('input.new-chatroom-server'); $input.val('problematic'); $nick.val('dummy'); $server.val('muc.localhost'); roomspanel.$el.find('form').submit(); }; it("will show an error message if the room requires a password", mock.initConverse(function (_converse) { submitRoomForm(_converse); var presence = $pres().attrs({ from:'lounge@localhost/thirdwitch', id:'n13mt3l', to:'dummy@localhost/pda', type:'error'}) .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() .c('error').attrs({by:'lounge@localhost', type:'auth'}) .c('not-authorized').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; var view = _converse.chatboxviews.get('problematic@muc.localhost'); spyOn(view, 'renderPasswordForm').andCallThrough(); runs(function () { view.onChatRoomPresence(presence); }); waits(250); runs(function () { var $chat_body = view.$el.find('.chatroom-body'); expect(view.renderPasswordForm).toHaveBeenCalled(); expect($chat_body.find('form.chatroom-form').length).toBe(1); expect($chat_body.find('legend').text()).toBe('This chatroom requires a password'); }); })); it("will show an error message if the room is members-only and the user not included", mock.initConverse(function (_converse) { submitRoomForm(_converse); var presence = $pres().attrs({ from:'lounge@localhost/thirdwitch', id:'n13mt3l', to:'dummy@localhost/pda', type:'error'}) .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() .c('error').attrs({by:'lounge@localhost', type:'auth'}) .c('registration-required').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; var view = _converse.chatboxviews.get('problematic@muc.localhost'); spyOn(view, 'showErrorMessage').andCallThrough(); view.onChatRoomPresence(presence); expect(view.$el.find('.chatroom-body p:last').text()).toBe('You are not on the member list of this room'); })); it("will show an error message if the user has been banned", mock.initConverse(function (_converse) { submitRoomForm(_converse); var presence = $pres().attrs({ from:'lounge@localhost/thirdwitch', id:'n13mt3l', to:'dummy@localhost/pda', type:'error'}) .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() .c('error').attrs({by:'lounge@localhost', type:'auth'}) .c('forbidden').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; var view = _converse.chatboxviews.get('problematic@muc.localhost'); spyOn(view, 'showErrorMessage').andCallThrough(); view.onChatRoomPresence(presence); expect(view.$el.find('.chatroom-body p:last').text()).toBe('You have been banned from this room'); })); it("will render a nickname form if a nickname conflict happens and muc_nickname_from_jid=false", mock.initConverse(function (_converse) { submitRoomForm(_converse); var presence = $pres().attrs({ from:'lounge@localhost/thirdwitch', id:'n13mt3l', to:'dummy@localhost/pda', type:'error'}) .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() .c('error').attrs({by:'lounge@localhost', type:'cancel'}) .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; var view = _converse.chatboxviews.get('problematic@muc.localhost'); spyOn(view, 'showErrorMessage').andCallThrough(); view.onChatRoomPresence(presence); expect(view.$el.find('.chatroom-body form.chatroom-form label:first').text()).toBe('Please choose your nickname'); })); it("will automatically choose a new nickname if a nickname conflict happens and muc_nickname_from_jid=true", mock.initConverse(function (_converse) { /* * * * * * */ submitRoomForm(_converse); _converse.muc_nickname_from_jid = true; var attrs = { from:'lounge@localhost/dummy', id:'n13mt3l', to:'dummy@localhost/pda', type:'error' }; var presence = $pres().attrs(attrs) .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() .c('error').attrs({by:'lounge@localhost', type:'cancel'}) .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; var view = _converse.chatboxviews.get('problematic@muc.localhost'); spyOn(view, 'showErrorMessage').andCallThrough(); spyOn(view, 'join').andCallThrough(); // Simulate repeatedly that there's already someone in the room // with that nickname view.onChatRoomPresence(presence); expect(view.join).toHaveBeenCalledWith('dummy-2'); attrs.from = 'lounge@localhost/dummy-2'; presence = $pres().attrs(attrs) .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() .c('error').attrs({by:'lounge@localhost', type:'cancel'}) .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; view.onChatRoomPresence(presence); expect(view.join).toHaveBeenCalledWith('dummy-3'); attrs.from = 'lounge@localhost/dummy-3'; presence = $pres().attrs(attrs) .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() .c('error').attrs({by:'lounge@localhost', type:'cancel'}) .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; view.onChatRoomPresence(presence); expect(view.join).toHaveBeenCalledWith('dummy-4'); })); it("will show an error message if the user is not allowed to have created the room", mock.initConverse(function (_converse) { submitRoomForm(_converse); var presence = $pres().attrs({ from:'lounge@localhost/thirdwitch', id:'n13mt3l', to:'dummy@localhost/pda', type:'error'}) .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() .c('error').attrs({by:'lounge@localhost', type:'cancel'}) .c('not-allowed').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; var view = _converse.chatboxviews.get('problematic@muc.localhost'); spyOn(view, 'showErrorMessage').andCallThrough(); view.onChatRoomPresence(presence); expect(view.$el.find('.chatroom-body p:last').text()).toBe('You are not allowed to create new rooms'); })); it("will show an error message if the user's nickname doesn't conform to room policy", mock.initConverse(function (_converse) { submitRoomForm(_converse); var presence = $pres().attrs({ from:'lounge@localhost/thirdwitch', id:'n13mt3l', to:'dummy@localhost/pda', type:'error'}) .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() .c('error').attrs({by:'lounge@localhost', type:'cancel'}) .c('not-acceptable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; var view = _converse.chatboxviews.get('problematic@muc.localhost'); spyOn(view, 'showErrorMessage').andCallThrough(); view.onChatRoomPresence(presence); expect(view.$el.find('.chatroom-body p:last').text()).toBe("Your nickname doesn't conform to this room's policies"); })); it("will show an error message if the room doesn't yet exist", mock.initConverse(function (_converse) { submitRoomForm(_converse); var presence = $pres().attrs({ from:'lounge@localhost/thirdwitch', id:'n13mt3l', to:'dummy@localhost/pda', type:'error'}) .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() .c('error').attrs({by:'lounge@localhost', type:'cancel'}) .c('item-not-found').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; var view = _converse.chatboxviews.get('problematic@muc.localhost'); spyOn(view, 'showErrorMessage').andCallThrough(); view.onChatRoomPresence(presence); expect(view.$el.find('.chatroom-body p:last').text()).toBe("This room does not (yet) exist"); })); it("will show an error message if the room has reached its maximum number of occupants", mock.initConverse(function (_converse) { submitRoomForm(_converse); var presence = $pres().attrs({ from:'lounge@localhost/thirdwitch', id:'n13mt3l', to:'dummy@localhost/pda', type:'error'}) .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() .c('error').attrs({by:'lounge@localhost', type:'cancel'}) .c('service-unavailable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; var view = _converse.chatboxviews.get('problematic@muc.localhost'); spyOn(view, 'showErrorMessage').andCallThrough(); view.onChatRoomPresence(presence); expect(view.$el.find('.chatroom-body p:last').text()).toBe("This room has reached its maximum number of occupants"); })); }); describe("Someone being invited to a chat room", function () { it("will first be added to the member list if the chat room is members only", mock.initConverse(function (_converse) { var sent_IQs = [], IQ_ids = []; var sendIQ = _converse.connection.sendIQ; spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) { sent_IQs.push(iq); IQ_ids.push(sendIQ.bind(this)(iq, callback, errback)); }); test_utils.openChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'dummy'); // State that the chat is members-only via the features IQ var features_stanza = $iq({ from: 'coven@chat.shakespeare.lit', 'id': IQ_ids.pop(), 'to': 'dummy@localhost/desktop', 'type': 'result' }) .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'}) .c('identity', { 'category': 'conference', 'name': 'A Dark Cave', 'type': 'text' }).up() .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up() .c('feature', {'var': 'muc_hidden'}).up() .c('feature', {'var': 'muc_temporary'}).up() .c('feature', {'var': 'muc_membersonly'}).up(); _converse.connection._dataRecv(test_utils.createRequest(features_stanza)); var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); expect(view.model.get('membersonly')).toBeTruthy(); test_utils.createContacts(_converse, 'current'); var sent_stanza, sent_id; spyOn(_converse.connection, 'send').andCallFake(function (stanza) { if (stanza.nodeTree && stanza.nodeTree.nodeName === 'message') { sent_id = stanza.nodeTree.getAttribute('id'); sent_stanza = stanza; } }); var name = mock.cur_names[0]; var invitee_jid = name.replace(/ /g,'.').toLowerCase() + '@localhost'; var reason = "Please join this chat room"; view.directInvite(invitee_jid, reason); var admin_iq_id = IQ_ids.pop(); var owner_iq_id = IQ_ids.pop(); var member_iq_id = IQ_ids.pop(); // Check in reverse order that we requested all three lists // (member, owner and admin). expect(sent_IQs.pop().toLocaleString()).toBe( ""+ ""+ ""+ ""+ ""); expect(sent_IQs.pop().toLocaleString()).toBe( ""+ ""+ ""+ ""+ ""); expect(sent_IQs.pop().toLocaleString()).toBe( ""+ ""+ ""+ ""+ ""); /* Now the service sends the member list to the user * * * * * * */ var member_list_stanza = $iq({ 'from': 'coven@chat.shakespeare.lit', 'id': member_iq_id, 'to': 'dummy@localhost/resource', 'type': 'result' }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN}) .c('item', { 'affiliation': 'member', 'jid': 'hag66@shakespeare.lit', 'nick': 'thirdwitch', 'role': 'participant' }); _converse.connection._dataRecv(test_utils.createRequest(member_list_stanza)); var admin_list_stanza = $iq({ 'from': 'coven@chat.shakespeare.lit', 'id': admin_iq_id, 'to': 'dummy@localhost/resource', 'type': 'result' }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN}) .c('item', { 'affiliation': 'admin', 'jid': 'wiccarocks@shakespeare.lit', 'nick': 'secondwitch' }); _converse.connection._dataRecv(test_utils.createRequest(admin_list_stanza)); var owner_list_stanza = $iq({ 'from': 'coven@chat.shakespeare.lit', 'id': owner_iq_id, 'to': 'dummy@localhost/resource', 'type': 'result' }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN}) .c('item', { 'affiliation': 'owner', 'jid': 'crone1@shakespeare.lit', }); _converse.connection._dataRecv(test_utils.createRequest(owner_list_stanza)); // Check that the member list now gets updated expect(sent_IQs.pop().toLocaleString()).toBe( ""+ ""+ ""+ "Please join this chat room"+ ""+ ""+ ""); // Finally check that the user gets invited. expect(sent_stanza.toLocaleString()).toBe( // Strophe adds the xmlns attr (although not in spec) ""+ ""+ "" ); })); }); describe("The affiliations delta", function () { it("can be computed in various ways", mock.initConverse(function (_converse) { test_utils.openChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'dummy'); var roomview = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); var exclude_existing = false; var remove_absentees = false; var new_list = []; var old_list = []; var delta = roomview.computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list); expect(delta.length).toBe(0); new_list = [{'jid': 'wiccarocks@shakespeare.lit', 'affiliation': 'member'}]; old_list = [{'jid': 'wiccarocks@shakespeare.lit', 'affiliation': 'member'}]; delta = roomview.computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list); expect(delta.length).toBe(0); // When remove_absentees is false, then affiliations in the old // list which are not in the new one won't be removed. old_list = [{'jid': 'oldhag666@shakespeare.lit', 'affiliation': 'owner'}, {'jid': 'wiccarocks@shakespeare.lit', 'affiliation': 'member'}]; delta = roomview.computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list); expect(delta.length).toBe(0); // With exclude_existing set to false, any changed affiliations // will be included in the delta (i.e. existing affiliations // are included in the comparison). old_list = [{'jid': 'wiccarocks@shakespeare.lit', 'affiliation': 'owner'}]; delta = roomview.computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list); expect(delta.length).toBe(1); expect(delta[0].jid).toBe('wiccarocks@shakespeare.lit'); expect(delta[0].affiliation).toBe('member'); // To also remove affiliations from the old list which are not // in the new list, we set remove_absentees to true remove_absentees = true; old_list = [{'jid': 'oldhag666@shakespeare.lit', 'affiliation': 'owner'}, {'jid': 'wiccarocks@shakespeare.lit', 'affiliation': 'member'}]; delta = roomview.computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list); expect(delta.length).toBe(1); expect(delta[0].jid).toBe('oldhag666@shakespeare.lit'); expect(delta[0].affiliation).toBe('none'); delta = roomview.computeAffiliationsDelta(exclude_existing, remove_absentees, [], old_list); expect(delta.length).toBe(2); expect(delta[0].jid).toBe('oldhag666@shakespeare.lit'); expect(delta[0].affiliation).toBe('none'); expect(delta[1].jid).toBe('wiccarocks@shakespeare.lit'); expect(delta[1].affiliation).toBe('none'); // To only add a user if they don't already have an // affiliation, we set 'exclude_existing' to true exclude_existing = true; old_list = [{'jid': 'wiccarocks@shakespeare.lit', 'affiliation': 'owner'}]; delta = roomview.computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list); expect(delta.length).toBe(0); })); }); }); }));