Merge branch 'master' of github.com:jcbrand/converse.js
This commit is contained in:
commit
6553b5ba73
23
CHANGES.rst
23
CHANGES.rst
@ -4,22 +4,27 @@ Changelog
|
||||
0.3 (unreleased)
|
||||
----------------
|
||||
|
||||
- Add vCard support [jcbrand]
|
||||
- Remember custom status messages upon reload. [jcbrand]
|
||||
- Remove jquery-ui dependency. [jcbrand]
|
||||
- Add vCard support
|
||||
[jcbrand]
|
||||
- Remember custom status messages upon reload.
|
||||
[jcbrand]
|
||||
- Remove jquery-ui dependency.
|
||||
[jcbrand]
|
||||
- Use backbone.localStorage to store the contacts roster, open chatboxes and
|
||||
chat messages. [jcbrand]
|
||||
- Fixed user status handling, which wasn't 100% according to the
|
||||
spec. [jcbrand]
|
||||
- Separate messages according to day in chats. [jcbrand]
|
||||
chat messages.
|
||||
[jcbrand]
|
||||
- Fixed user status handling, which wasn't 100% according to the spec.
|
||||
[jcbrand]
|
||||
- Separate messages according to day in chats.
|
||||
[jcbrand]
|
||||
- Add support for specifying the BOSH bind URL as configuration setting.
|
||||
[jcbrand]
|
||||
- Improve the message counter to only increment when the window is not focused
|
||||
[witekdev]
|
||||
- Make fetching of list of chatrooms on a server a configuration option.
|
||||
[jcbrand]
|
||||
- Use service discovery to show whether a chatroom is password protected as
|
||||
well as its number of occupents. [jcbrand]
|
||||
- Use service discovery to show all available features on a room.
|
||||
[jcbrand]
|
||||
|
||||
|
||||
0.2 (2013-03-28)
|
||||
|
@ -57,6 +57,7 @@
|
||||
* (String) nick - Optional nickname to use in the chat room.
|
||||
* (Function) msg_handler_cb - The function call to handle messages from the specified chat room.
|
||||
* (Function) pres_handler_cb - The function call back to handle presence in the chat room.
|
||||
* (Function) roster_cb - The function call back to handle roster changes in the chat room.
|
||||
* (String) password - The optional password to use. (password protected rooms only)
|
||||
*/
|
||||
var msg, room_nick, _this = this;
|
||||
|
48
converse.css
48
converse.css
@ -92,7 +92,11 @@ img.spinner {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
padding: 0.5em 0 0 0.5em;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
ul.participant-list li.moderator {
|
||||
color: #FE0007;
|
||||
}
|
||||
|
||||
.chatroom form.sendXMPPMessage {
|
||||
@ -223,6 +227,7 @@ div.chat-title {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-shadow: rgba(0,0,0,0.51) 0 -1px 0;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.chat-head-chatbox,
|
||||
@ -441,7 +446,7 @@ form.search-xmpp-contact input {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
width: 160px;
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
#available-chatrooms dt,
|
||||
@ -468,6 +473,41 @@ dd.available-chatroom,
|
||||
text-shadow: 0 1px 0 rgba(250, 250, 250, 1);
|
||||
}
|
||||
|
||||
.room-info {
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
p.room-info {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: block;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
a.room-info {
|
||||
background: url('images/information.png') no-repeat right top;
|
||||
width: 22px;
|
||||
float: right;
|
||||
display: none;
|
||||
}
|
||||
|
||||
a.open-room {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
dd.available-chatroom:hover a.room-info {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
dd.available-chatroom:hover a.open-room {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
#converse-roster dd a.remove-xmpp-contact {
|
||||
background: url('images/delete_icon.png') no-repeat right top;
|
||||
padding: 0 0 1em 0;
|
||||
@ -477,11 +517,11 @@ dd.available-chatroom,
|
||||
display: none;
|
||||
}
|
||||
|
||||
#converse-roster dd:hover *[class*="remove-xmpp-contact"] {
|
||||
#converse-roster dd:hover a.remove-xmpp-contact {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#converse-roster dd:hover *[class*="open-chat"] {
|
||||
#converse-roster dd:hover a.open-chat {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
@ -541,10 +581,12 @@ form#converse-login {
|
||||
|
||||
form#converse-login input {
|
||||
display: block;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
form#converse-login .login-submit {
|
||||
margin-top: 1em;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
form.set-xmpp-status,
|
||||
|
250
converse.js
250
converse.js
@ -684,14 +684,56 @@
|
||||
events: {
|
||||
'submit form.add-chatroom': 'createChatRoom',
|
||||
'click input#show-rooms': 'showRooms',
|
||||
'click a.open-room': 'createChatRoom'
|
||||
'click a.open-room': 'createChatRoom',
|
||||
'click a.room-info': 'showRoomInfo'
|
||||
},
|
||||
room_template: _.template(
|
||||
'<dd class="available-chatroom">' +
|
||||
'<a class="open-room {{classes}}" data-room-jid="{{jid}}"' +
|
||||
' title="{{desc}}"' +
|
||||
' href="#">' +
|
||||
'{{name}}</a> {{occ}}</dd>'),
|
||||
'<dd class="available-chatroom">'+
|
||||
'<a class="open-room" data-room-jid="{{jid}}" title="Click to open this room" href="#">{{name}}</a>'+
|
||||
'<a class="room-info" data-room-jid="{{jid}}" title="Show more information on this room" href="#"> </a>'+
|
||||
'</dd>'),
|
||||
|
||||
room_description_template: _.template(
|
||||
'<div class="room-info">'+
|
||||
'<p class="room-info"><strong>Description:</strong> {{desc}}</p>' +
|
||||
'<p class="room-info"><strong>Occupants:</strong> {{occ}}</p>' +
|
||||
'<p class="room-info"><strong>Features:</strong> <ul>'+
|
||||
'{[ if (passwordprotected) { ]}' +
|
||||
'<li class="room-info locked">Requires authentication</li>' +
|
||||
'{[ } ]}' +
|
||||
'{[ if (hidden) { ]}' +
|
||||
'<li class="room-info">Hidden</li>' +
|
||||
'{[ } ]}' +
|
||||
'{[ if (membersonly) { ]}' +
|
||||
'<li class="room-info">Requires an invitation</li>' +
|
||||
'{[ } ]}' +
|
||||
'{[ if (moderated) { ]}' +
|
||||
'<li class="room-info">Moderated</li>' +
|
||||
'{[ } ]}' +
|
||||
'{[ if (nonanonymous) { ]}' +
|
||||
'<li class="room-info">Non-anonymous</li>' +
|
||||
'{[ } ]}' +
|
||||
'{[ if (open) { ]}' +
|
||||
'<li class="room-info">Open room</li>' +
|
||||
'{[ } ]}' +
|
||||
'{[ if (persistent) { ]}' +
|
||||
'<li class="room-info">Permanent room</li>' +
|
||||
'{[ } ]}' +
|
||||
'{[ if (publicroom) { ]}' +
|
||||
'<li class="room-info">Public</li>' +
|
||||
'{[ } ]}' +
|
||||
'{[ if (semianonymous) { ]}' +
|
||||
'<li class="room-info">Semi-anonymous</li>' +
|
||||
'{[ } ]}' +
|
||||
'{[ if (temporary) { ]}' +
|
||||
'<li class="room-info">Temporary room</li>' +
|
||||
'{[ } ]}' +
|
||||
'{[ if (unmoderated) { ]}' +
|
||||
'<li class="room-info">Unmoderated</li>' +
|
||||
'{[ } ]}' +
|
||||
'</p>' +
|
||||
'</div>'
|
||||
),
|
||||
|
||||
tab_template: _.template('<li><a class="s" href="#chatrooms">Rooms</a></li>'),
|
||||
|
||||
@ -720,37 +762,23 @@
|
||||
converse.connection.muc.listRooms(
|
||||
this.muc_domain,
|
||||
$.proxy(function (iq) { // Success
|
||||
var name, jid, i, that = this, $available_chatrooms = this.$el.find('#available-chatrooms');
|
||||
this.rdict = {};
|
||||
var name, jid, i, fragment,
|
||||
that = this,
|
||||
$available_chatrooms = this.$el.find('#available-chatrooms');
|
||||
this.rooms = $(iq).find('query').find('item');
|
||||
this.rooms.each(function (i) { that.rdict[$(this).attr('jid')] = this; });
|
||||
this.fragment = document.createDocumentFragment();
|
||||
if (this.rooms.length) {
|
||||
$available_chatrooms.html('<dt>Rooms on '+this.muc_domain+'</dt>');
|
||||
_.each(this.rooms, $.proxy(function (room, idx) {
|
||||
converse.connection.disco.info(
|
||||
$(room).attr('jid'),
|
||||
null,
|
||||
$.proxy(function (stanza) {
|
||||
var name = $(stanza).find('identity').attr('name');
|
||||
var desc = $(stanza).find('field[var="muc#roominfo_description"] value').text();
|
||||
var occ = $(stanza).find('field[var="muc#roominfo_occupants"] value').text();
|
||||
var locked = $(stanza).find('feature[var="muc_passwordprotected"]').length;
|
||||
var jid = $(stanza).attr('from');
|
||||
var classes = locked && 'locked' || '';
|
||||
delete this.rdict[jid];
|
||||
this.$el.find('#available-chatrooms').append(
|
||||
this.room_template({'name':name,
|
||||
'desc':desc,
|
||||
'occ':occ,
|
||||
'jid':jid,
|
||||
'classes': classes
|
||||
}));
|
||||
if (_.keys(this.rdict).length === 0) {
|
||||
$('input#show-rooms').show().siblings('img.spinner').remove();
|
||||
}
|
||||
}, this));
|
||||
}, this));
|
||||
fragment = document.createDocumentFragment();
|
||||
for (i=0; i<this.rooms.length; i++) {
|
||||
name = Strophe.unescapeNode($(this.rooms[i]).attr('name')||$(this.rooms[i]).attr('jid'));
|
||||
jid = $(this.rooms[i]).attr('jid');
|
||||
fragment.appendChild($(this.room_template({
|
||||
'name':name,
|
||||
'jid':jid
|
||||
}))[0]);
|
||||
}
|
||||
$available_chatrooms.append(fragment);
|
||||
$('input#show-rooms').show().siblings('img.spinner').remove();
|
||||
} else {
|
||||
$available_chatrooms.html('<dt>No rooms on '+this.muc_domain+'</dt>');
|
||||
$('input#show-rooms').show().siblings('img.spinner').remove();
|
||||
@ -780,6 +808,53 @@
|
||||
this.updateRoomsList();
|
||||
},
|
||||
|
||||
showRoomInfo: function (ev) {
|
||||
var target = ev.target,
|
||||
$dd = $(target).parent('dd'),
|
||||
$div = $dd.find('div.room-info');
|
||||
if ($div.length) {
|
||||
$div.remove();
|
||||
} else {
|
||||
$dd.append('<img class="spinner" src="images/spinner.gif"/>');
|
||||
converse.connection.disco.info(
|
||||
$(target).attr('data-room-jid'),
|
||||
null,
|
||||
$.proxy(function (stanza) {
|
||||
var $stanza = $(stanza);
|
||||
// All MUC features shown here: http://xmpp.org/registrar/disco-features.html
|
||||
var desc = $stanza.find('field[var="muc#roominfo_description"] value').text();
|
||||
var occ = $stanza.find('field[var="muc#roominfo_occupants"] value').text();
|
||||
var hidden = $stanza.find('feature[var="muc_hidden"]').length;
|
||||
var membersonly = $stanza.find('feature[var="muc_membersonly"]').length;
|
||||
var moderated = $stanza.find('feature[var="muc_moderated"]').length;
|
||||
var nonanonymous = $stanza.find('feature[var="muc_nonanonymous"]').length;
|
||||
var open = $stanza.find('feature[var="muc_open"]').length;
|
||||
var passwordprotected = $stanza.find('feature[var="muc_passwordprotected"]').length;
|
||||
var persistent = $stanza.find('feature[var="muc_persistent"]').length;
|
||||
var publicroom = $stanza.find('feature[var="muc_public"]').length;
|
||||
var semianonymous = $stanza.find('feature[var="muc_semianonymous"]').length;
|
||||
var temporary = $stanza.find('feature[var="muc_temporary"]').length;
|
||||
var unmoderated = $stanza.find('feature[var="muc_unmoderated"]').length;
|
||||
$dd.find('img.spinner').replaceWith(
|
||||
this.room_description_template({
|
||||
'desc':desc,
|
||||
'occ':occ,
|
||||
'hidden':hidden,
|
||||
'membersonly':membersonly,
|
||||
'moderated':moderated,
|
||||
'nonanonymous':nonanonymous,
|
||||
'open':open,
|
||||
'passwordprotected':passwordprotected,
|
||||
'persistent':persistent,
|
||||
'publicroom': publicroom,
|
||||
'semianonymous':semianonymous,
|
||||
'temporary':temporary,
|
||||
'unmoderated':unmoderated
|
||||
}));
|
||||
}, this));
|
||||
}
|
||||
},
|
||||
|
||||
createChatRoom: function (ev) {
|
||||
ev.preventDefault();
|
||||
var name, server, jid, $name, $server, errors;
|
||||
@ -986,7 +1061,8 @@
|
||||
this.model.get('nick'),
|
||||
$.proxy(this.onChatRoomMessage, this),
|
||||
$.proxy(this.onChatRoomPresence, this),
|
||||
$.proxy(this.onChatRoomRoster, this));
|
||||
$.proxy(this.onChatRoomRoster, this),
|
||||
null);
|
||||
|
||||
this.model.messages.on('add', this.showMessage, this);
|
||||
this.model.on('destroy', function (model, response, options) {
|
||||
@ -1004,22 +1080,84 @@
|
||||
|
||||
onLeave: function () {},
|
||||
|
||||
showRoomConfigOptions: function (stanza) {
|
||||
// FIXME: Show a proper configuration form
|
||||
var $chat_content = this.$el.find('.chat-content'),
|
||||
$stanza = $(stanza),
|
||||
$fields = $stanza.find('field'),
|
||||
title = $stanza.find('title').text(),
|
||||
instructions = $stanza.find('instructions').text(),
|
||||
i;
|
||||
$chat_content.append(title);
|
||||
$chat_content.append(instructions);
|
||||
for (i=0; i<$fields.length; i++) {
|
||||
$field = $($fields[i]);
|
||||
$chat_content.append('<label>'+$field.attr('label')+'</label>');
|
||||
// $chat_content.append('<input type="text" name=">'+$field.attr('label')+'</label>');
|
||||
}
|
||||
},
|
||||
|
||||
onChatRoomPresence: function (presence, room) {
|
||||
var nick = room.nick,
|
||||
$presence = $(presence),
|
||||
from = $presence.attr('from');
|
||||
from = $presence.attr('from'), item;
|
||||
if ($presence.attr('type') !== 'error') {
|
||||
if ($presence.find("status[code='201']").length) {
|
||||
// This is a new chatroom. We create an instant
|
||||
// chatroom, and let the user manually set any
|
||||
// configuration setting. (2nd part is TODO)
|
||||
converse.connection.muc.createInstantRoom(room.name);
|
||||
/* TODO: Find a place for this code (it configures a
|
||||
* newly created chatroom).
|
||||
* -------------------------------------------------
|
||||
$item = $presence.find('item');
|
||||
if ($item.length) {
|
||||
if ($item.attr('affiliation') == 'owner') {
|
||||
if (false) {
|
||||
} else {
|
||||
converse.connection.muc.configure(
|
||||
room.name,
|
||||
$.proxy(this.showRoomConfigOptions, this)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
// check for status 110 to see if it's our own presence
|
||||
if ($presence.find("status[code='110']").length) {
|
||||
// check if server changed our nick
|
||||
if ($presence.find("status[code='210']").length) {
|
||||
// check if server changed our nick
|
||||
this.model.set({'nick': Strophe.getResourceFromJid(from)});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var error = $presence.find('error');
|
||||
if ($(error).attr('type') == 'auth') {
|
||||
this.$el.find('.chat-content').append('Sorry, this chatroom is restricted');
|
||||
var $error = $presence.find('error'),
|
||||
$chat_content = this.$el.find('.chat-content');
|
||||
if ($error.attr('type') == 'auth') {
|
||||
if ($error.find('not-authorized').length) {
|
||||
$chat_content.append('This chatroom requires a password');
|
||||
} else if ($error.find('registration-required').length) {
|
||||
$chat_content.append('You are not on the member list of this room');
|
||||
} else if ($error.find('forbidden').length) {
|
||||
$chat_content.append('You have been banned from this room');
|
||||
}
|
||||
} else if ($error.attr('type') == 'modify') {
|
||||
if ($error.find('jid-malformed').length) {
|
||||
$chat_content.append('No nickname was specified');
|
||||
}
|
||||
} else if ($error.attr('type') == 'cancel') {
|
||||
if ($error.find('not-allowed').length) {
|
||||
$chat_content.append('You are not allowed to create new rooms');
|
||||
} else if ($error.find('not-acceptable').length) {
|
||||
$chat_content.append("Your nickname doesn't conform to the room's policies");
|
||||
} else if ($error.find('conflict').length) {
|
||||
$chat_content.append("Your nickname is already taken");
|
||||
} else if ($error.find('item-not-found').length) {
|
||||
$chat_content.append("This room does not (yet) exist");
|
||||
} else if ($error.find('service-unavailable').length) {
|
||||
$chat_content.append("This room has reached it's maximum number of occupants");
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@ -1108,16 +1246,32 @@
|
||||
return true;
|
||||
},
|
||||
|
||||
occupant_template: _.template(
|
||||
'<li class="{{role}}" '+
|
||||
'{[ if (role === "moderator") { ]}' +
|
||||
'title="This user is a moderator"' +
|
||||
'{[ } ]}'+
|
||||
'{[ if (role === "participant") { ]}' +
|
||||
'title="This user can send messages in this room"' +
|
||||
'{[ } ]}'+
|
||||
'{[ if (role === "visitor") { ]}' +
|
||||
'title="This user can NOT send messages in this room"' +
|
||||
'{[ } ]}'+
|
||||
'>{{nick}}</li>'
|
||||
),
|
||||
|
||||
onChatRoomRoster: function (roster, room) {
|
||||
// underscore size is needed because roster is an object
|
||||
var controlboxview = converse.chatboxesview.views.controlbox,
|
||||
roster_size = _.size(roster),
|
||||
$participant_list = this.$el.find('.participant-list'),
|
||||
participants = [],
|
||||
i;
|
||||
participants = [], keys = _.keys(roster), i;
|
||||
this.$el.find('.participant-list').empty();
|
||||
for (i=0; i<roster_size; i++) {
|
||||
participants.push('<li>' + Strophe.unescapeNode(_.keys(roster)[i]) + '</li>');
|
||||
participants.push(
|
||||
this.occupant_template({
|
||||
role: roster[keys[i]].role,
|
||||
nick: Strophe.unescapeNode(keys[i])
|
||||
}));
|
||||
}
|
||||
$participant_list.append(participants.join(""));
|
||||
return true;
|
||||
@ -1344,7 +1498,6 @@
|
||||
this.$el.addClass('current-xmpp-contact');
|
||||
this.$el.html(this.template(item.toJSON()));
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
@ -1802,7 +1955,6 @@
|
||||
converse.connection.send($pres().c('show').t(this.get('status')).up().c('status').t(status_message));
|
||||
this.save({'status_message': status_message});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
converse.XMPPStatusView = Backbone.View.extend({
|
||||
@ -1834,7 +1986,6 @@
|
||||
'<a class="change-xmpp-status-message" href="#" Title="Click here to write a custom status message"></a>' +
|
||||
'</div>'),
|
||||
|
||||
|
||||
renderStatusChangeForm: function (ev) {
|
||||
ev.preventDefault();
|
||||
var status_message = this.model.get('status') || 'offline';
|
||||
@ -1937,6 +2088,7 @@
|
||||
* This collection stores Feature Models, representing features
|
||||
* provided by available XMPP entities (e.g. servers)
|
||||
* See XEP-0030 for more details: http://xmpp.org/extensions/xep-0030.html
|
||||
* All features are shown here: http://xmpp.org/registrar/disco-features.html
|
||||
*/
|
||||
model: converse.Feature,
|
||||
initialize: function () {
|
||||
@ -2114,8 +2266,8 @@
|
||||
|
||||
converse.onConnected = function (connection) {
|
||||
this.connection = connection;
|
||||
// this.connection.xmlInput = function (body) { console.log(body); };
|
||||
// this.connection.xmlOutput = function (body) { console.log(body); };
|
||||
this.connection.xmlInput = function (body) { console.log(body); };
|
||||
this.connection.xmlOutput = function (body) { console.log(body); };
|
||||
this.bare_jid = Strophe.getBareJidFromJid(this.connection.jid);
|
||||
this.domain = Strophe.getDomainFromJid(this.connection.jid);
|
||||
this.features = new this.Features();
|
||||
|
43
mock.js
Normal file
43
mock.js
Normal file
@ -0,0 +1,43 @@
|
||||
(function (root, factory) {
|
||||
define("mock",
|
||||
['converse'],
|
||||
function() {
|
||||
return factory();
|
||||
});
|
||||
}(this, function (converse) {
|
||||
var mock_connection = {
|
||||
'muc': {
|
||||
'listRooms': function () {},
|
||||
'join': function () {},
|
||||
'leave': function () {}
|
||||
},
|
||||
'jid': 'dummy@localhost',
|
||||
'addHandler': function (handler, ns, name, type, id, from, options) {
|
||||
return function () {};
|
||||
},
|
||||
'send': function () {},
|
||||
'roster': {
|
||||
'add': function () {},
|
||||
'authorize': function () {},
|
||||
'unauthorize': function () {},
|
||||
'get': function () {},
|
||||
'subscribe': function () {},
|
||||
'registerCallback': function () {}
|
||||
},
|
||||
'vcard': {
|
||||
'get': function (callback, jid) {
|
||||
var name = jid.split('@')[0].replace('.', ' ').split(' ');
|
||||
var firstname = name[0].charAt(0).toUpperCase()+name[0].slice(1);
|
||||
var lastname = name[1].charAt(0).toUpperCase()+name[1].slice(1);
|
||||
var fullname = firstname+' '+lastname;
|
||||
var vcard = $iq().c('vCard').c('FN').t(fullname);
|
||||
callback(vcard.tree());
|
||||
}
|
||||
},
|
||||
'disco': {
|
||||
'info': function () {},
|
||||
'items': function () {}
|
||||
}
|
||||
};
|
||||
return mock_connection;
|
||||
}));
|
239
spec/ChatRoomSpec.js
Normal file
239
spec/ChatRoomSpec.js
Normal file
@ -0,0 +1,239 @@
|
||||
(function (root, factory) {
|
||||
define([
|
||||
"converse",
|
||||
"mock"
|
||||
], function (converse, mock_connection) {
|
||||
return factory(converse, mock_connection);
|
||||
}
|
||||
);
|
||||
} (this, function (converse, mock_connection) {
|
||||
return describe("ChatRooms", $.proxy(function() {
|
||||
var chatroom_names = [
|
||||
'Dyon van de Wege', 'Thomas Kalb', 'Dirk Theissen', 'Felix Hofmann', 'Ka Lek', 'Anne Ebersbacher'
|
||||
];
|
||||
describe("A Chat Room", $.proxy(function () {
|
||||
beforeEach($.proxy(function () {
|
||||
if (!$("div#controlbox").is(':visible')) {
|
||||
$('.toggle-online-users').click();
|
||||
}
|
||||
var roomspanel = this.chatboxesview.views.controlbox.roomspanel;
|
||||
var $input = roomspanel.$el.find('input.new-chatroom-name');
|
||||
var $server = roomspanel.$el.find('input.new-chatroom-server');
|
||||
$input.val('lounge');
|
||||
$server.val('muc.localhost');
|
||||
roomspanel.$el.find('form').submit();
|
||||
$('.toggle-online-users').click();
|
||||
}, converse));
|
||||
|
||||
it("shows users currently present in the room", $.proxy(function () {
|
||||
var chatroomview = this.chatboxesview.views['lounge@muc.localhost'];
|
||||
var $participant_list = chatroomview.$el.find('.participant-list');
|
||||
var roster = {}, room = {}, i;
|
||||
|
||||
for (i=0; i<chatroom_names.length-1; i++) {
|
||||
roster[chatroom_names[i]] = {};
|
||||
chatroomview.onChatRoomRoster(roster, room);
|
||||
expect($participant_list.find('li').length).toBe(1+i);
|
||||
expect($($participant_list.find('li')[i]).text()).toBe(chatroom_names[i]);
|
||||
}
|
||||
roster[converse.bare_jid] = {};
|
||||
chatroomview.onChatRoomRoster(roster, room);
|
||||
}, converse));
|
||||
|
||||
it("indicates moderators by means of a special css class and tooltip", $.proxy(function () {
|
||||
var chatroomview = this.chatboxesview.views['lounge@muc.localhost'];
|
||||
var $participant_list = chatroomview.$el.find('.participant-list');
|
||||
var roster = {}, idx = chatroom_names.length-1;
|
||||
roster[chatroom_names[idx]] = {};
|
||||
roster[chatroom_names[idx]].role = 'moderator';
|
||||
chatroomview.onChatRoomRoster(roster, {});
|
||||
occupant = $participant_list.find('li');
|
||||
expect(occupant.length).toBe(1);
|
||||
expect($(occupant).text()).toBe(chatroom_names[idx]);
|
||||
expect($(occupant).attr('class')).toBe('moderator');
|
||||
expect($(occupant).attr('title')).toBe('This user is a moderator');
|
||||
}, converse));
|
||||
|
||||
it("can be saved to, and retrieved from, localStorage", $.proxy(function () {
|
||||
// We instantiate a new ChatBoxes collection, which by default
|
||||
// will be empty.
|
||||
var newchatboxes = new this.ChatBoxes();
|
||||
expect(newchatboxes.length).toEqual(0);
|
||||
// The chatboxes will then be fetched from localStorage inside the
|
||||
// onConnected method
|
||||
newchatboxes.onConnected();
|
||||
expect(newchatboxes.length).toEqual(1);
|
||||
// Check that the chatrooms retrieved from localStorage
|
||||
// have the same attributes values as the original ones.
|
||||
attrs = ['id', 'box_id', 'visible'];
|
||||
for (i=0; i<attrs.length; i++) {
|
||||
new_attrs = _.pluck(_.pluck(newchatboxes.models, 'attributes'), attrs[i]);
|
||||
old_attrs = _.pluck(_.pluck(this.chatboxes.models, 'attributes'), attrs[i]);
|
||||
expect(_.isEqual(new_attrs, old_attrs)).toEqual(true);
|
||||
}
|
||||
this.rosterview.render();
|
||||
}, converse));
|
||||
|
||||
it("can be closed again by clicking a DOM element with class 'close-chatbox-button'", $.proxy(function () {
|
||||
var view = this.chatboxesview.views['lounge@muc.localhost'], chatroom = view.model, $el;
|
||||
spyOn(view, 'closeChat').andCallThrough();
|
||||
spyOn(converse.connection.muc, 'leave');
|
||||
view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
|
||||
view.$el.find('.close-chatbox-button').click();
|
||||
expect(view.closeChat).toHaveBeenCalled();
|
||||
expect(converse.connection.muc.leave).toHaveBeenCalled();
|
||||
}, converse));
|
||||
}, converse));
|
||||
|
||||
describe("When attempting to enter a chatroom", $.proxy(function () {
|
||||
beforeEach($.proxy(function () {
|
||||
var roomspanel = this.chatboxesview.views.controlbox.roomspanel;
|
||||
var $input = roomspanel.$el.find('input.new-chatroom-name');
|
||||
var $server = roomspanel.$el.find('input.new-chatroom-server');
|
||||
$input.val('problematic');
|
||||
$server.val('muc.localhost');
|
||||
roomspanel.$el.find('form').submit();
|
||||
}, converse));
|
||||
|
||||
afterEach($.proxy(function () {
|
||||
var view = this.chatboxesview.views['problematic@muc.localhost'];
|
||||
view.closeChat();
|
||||
}, converse));
|
||||
|
||||
it("will show an error message if the room requires a password", $.proxy(function () {
|
||||
var presence = $pres().attrs({
|
||||
from:'coven@chat.shakespeare.lit/thirdwitch',
|
||||
id:'n13mt3l',
|
||||
to:'hag66@shakespeare.lit/pda',
|
||||
type:'error'})
|
||||
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
||||
.c('error').attrs({by:'coven@chat.shakespeare.lit', type:'auth'})
|
||||
.c('not-authorized').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||
var view = this.chatboxesview.views['problematic@muc.localhost'];
|
||||
view.onChatRoomPresence(presence, {'nick': 'dummy'});
|
||||
var $chat_content = view.$el.find('.chat-content');
|
||||
expect($chat_content.text()).toBe('This chatroom requires a password');
|
||||
}, converse));
|
||||
|
||||
it("will show an error message if the room is members-only and the user not included", $.proxy(function () {
|
||||
var presence = $pres().attrs({
|
||||
from:'coven@chat.shakespeare.lit/thirdwitch',
|
||||
id:'n13mt3l',
|
||||
to:'hag66@shakespeare.lit/pda',
|
||||
type:'error'})
|
||||
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
||||
.c('error').attrs({by:'coven@chat.shakespeare.lit', type:'auth'})
|
||||
.c('registration-required').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||
var view = this.chatboxesview.views['problematic@muc.localhost'];
|
||||
view.onChatRoomPresence(presence, {'nick': 'dummy'});
|
||||
var $chat_content = view.$el.find('.chat-content');
|
||||
expect($chat_content.text()).toBe('You are not on the member list of this room');
|
||||
}, converse));
|
||||
|
||||
it("will show an error message if the user has been banned", $.proxy(function () {
|
||||
var presence = $pres().attrs({
|
||||
from:'coven@chat.shakespeare.lit/thirdwitch',
|
||||
id:'n13mt3l',
|
||||
to:'hag66@shakespeare.lit/pda',
|
||||
type:'error'})
|
||||
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
||||
.c('error').attrs({by:'coven@chat.shakespeare.lit', type:'auth'})
|
||||
.c('forbidden').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||
var view = this.chatboxesview.views['problematic@muc.localhost'];
|
||||
view.onChatRoomPresence(presence, {'nick': 'dummy'});
|
||||
var $chat_content = view.$el.find('.chat-content');
|
||||
expect($chat_content.text()).toBe('You have been banned from this room');
|
||||
}, converse));
|
||||
|
||||
it("will show an error message if no nickname was specified for the user", $.proxy(function () {
|
||||
var presence = $pres().attrs({
|
||||
from:'coven@chat.shakespeare.lit/thirdwitch',
|
||||
id:'n13mt3l',
|
||||
to:'hag66@shakespeare.lit/pda',
|
||||
type:'error'})
|
||||
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
||||
.c('error').attrs({by:'coven@chat.shakespeare.lit', type:'modify'})
|
||||
.c('jid-malformed').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||
var view = this.chatboxesview.views['problematic@muc.localhost'];
|
||||
view.onChatRoomPresence(presence, {'nick': 'dummy'});
|
||||
var $chat_content = view.$el.find('.chat-content');
|
||||
expect($chat_content.text()).toBe('No nickname was specified');
|
||||
}, converse));
|
||||
|
||||
it("will show an error message if the user is not allowed to have created the room", $.proxy(function () {
|
||||
var presence = $pres().attrs({
|
||||
from:'coven@chat.shakespeare.lit/thirdwitch',
|
||||
id:'n13mt3l',
|
||||
to:'hag66@shakespeare.lit/pda',
|
||||
type:'error'})
|
||||
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
||||
.c('error').attrs({by:'coven@chat.shakespeare.lit', type:'cancel'})
|
||||
.c('not-allowed').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||
var view = this.chatboxesview.views['problematic@muc.localhost'];
|
||||
view.onChatRoomPresence(presence, {'nick': 'dummy'});
|
||||
var $chat_content = view.$el.find('.chat-content');
|
||||
expect($chat_content.text()).toBe('You are not allowed to create new rooms');
|
||||
}, converse));
|
||||
|
||||
it("will show an error message if the user's nickname doesn't conform to room policy", $.proxy(function () {
|
||||
var presence = $pres().attrs({
|
||||
from:'coven@chat.shakespeare.lit/thirdwitch',
|
||||
id:'n13mt3l',
|
||||
to:'hag66@shakespeare.lit/pda',
|
||||
type:'error'})
|
||||
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
||||
.c('error').attrs({by:'coven@chat.shakespeare.lit', type:'cancel'})
|
||||
.c('not-acceptable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||
var view = this.chatboxesview.views['problematic@muc.localhost'];
|
||||
view.onChatRoomPresence(presence, {'nick': 'dummy'});
|
||||
var $chat_content = view.$el.find('.chat-content');
|
||||
expect($chat_content.text()).toBe("Your nickname doesn't conform to the room's policies");
|
||||
}, converse));
|
||||
|
||||
it("will show an error message if the user's nickname is already taken", $.proxy(function () {
|
||||
var presence = $pres().attrs({
|
||||
from:'coven@chat.shakespeare.lit/thirdwitch',
|
||||
id:'n13mt3l',
|
||||
to:'hag66@shakespeare.lit/pda',
|
||||
type:'error'})
|
||||
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
||||
.c('error').attrs({by:'coven@chat.shakespeare.lit', type:'cancel'})
|
||||
.c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||
var view = this.chatboxesview.views['problematic@muc.localhost'];
|
||||
view.onChatRoomPresence(presence, {'nick': 'dummy'});
|
||||
var $chat_content = view.$el.find('.chat-content');
|
||||
expect($chat_content.text()).toBe("Your nickname is already taken");
|
||||
}, converse));
|
||||
|
||||
it("will show an error message if the room doesn't yet exist", $.proxy(function () {
|
||||
var presence = $pres().attrs({
|
||||
from:'coven@chat.shakespeare.lit/thirdwitch',
|
||||
id:'n13mt3l',
|
||||
to:'hag66@shakespeare.lit/pda',
|
||||
type:'error'})
|
||||
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
||||
.c('error').attrs({by:'coven@chat.shakespeare.lit', type:'cancel'})
|
||||
.c('item-not-found').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||
var view = this.chatboxesview.views['problematic@muc.localhost'];
|
||||
view.onChatRoomPresence(presence, {'nick': 'dummy'});
|
||||
var $chat_content = view.$el.find('.chat-content');
|
||||
expect($chat_content.text()).toBe("This room does not (yet) exist");
|
||||
}, converse));
|
||||
|
||||
it("will show an error message if the room has reached it's maximum number of occupants", $.proxy(function () {
|
||||
var presence = $pres().attrs({
|
||||
from:'coven@chat.shakespeare.lit/thirdwitch',
|
||||
id:'n13mt3l',
|
||||
to:'hag66@shakespeare.lit/pda',
|
||||
type:'error'})
|
||||
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
||||
.c('error').attrs({by:'coven@chat.shakespeare.lit', type:'cancel'})
|
||||
.c('service-unavailable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||
var view = this.chatboxesview.views['problematic@muc.localhost'];
|
||||
view.onChatRoomPresence(presence, {'nick': 'dummy'});
|
||||
var $chat_content = view.$el.find('.chat-content');
|
||||
expect($chat_content.text()).toBe("This room has reached it's maximum number of occupants");
|
||||
}, converse));
|
||||
}, converse));
|
||||
}, converse));
|
||||
}));
|
116
spec/MainSpec.js
116
spec/MainSpec.js
@ -1,12 +1,12 @@
|
||||
(function (root, factory) {
|
||||
define([
|
||||
"converse"
|
||||
], function (converse) {
|
||||
return factory(converse);
|
||||
"converse",
|
||||
"mock"
|
||||
], function (converse, mock_connection) {
|
||||
return factory(converse, mock_connection);
|
||||
}
|
||||
);
|
||||
} (this, function (converse) {
|
||||
|
||||
} (this, function (converse, mock_connection) {
|
||||
return describe("Converse.js", $.proxy(function() {
|
||||
// Names from http://www.fakenamegenerator.com/
|
||||
var req_names = [
|
||||
@ -20,56 +20,7 @@
|
||||
'Robin Schook', 'Marcel Eberhardt', 'Simone Brauer', 'Asmaa Haakman', 'Felix Amsel',
|
||||
'Lena Grunewald', 'Laura Grunewald', 'Mandy Seiler', 'Sven Bosch', 'Nuriye Cuypers'
|
||||
];
|
||||
var chatroom_names = [
|
||||
'Dyon van de Wege', 'Thomas Kalb', 'Dirk Theissen', 'Felix Hofmann', 'Ka Lek', 'Anne Ebersbacher'
|
||||
];
|
||||
var num_contacts = req_names.length + pend_names.length + cur_names.length;
|
||||
mock_connection = {
|
||||
'muc': {
|
||||
'listRooms': function () {},
|
||||
'join': function () {},
|
||||
'leave': function () {}
|
||||
},
|
||||
'jid': 'dummy@localhost',
|
||||
'addHandler': function (handler, ns, name, type, id, from, options) {
|
||||
return function () {};
|
||||
},
|
||||
'send': function () {},
|
||||
'roster': {
|
||||
'add': function () {},
|
||||
'authorize': function () {},
|
||||
'unauthorize': function () {},
|
||||
'get': function () {},
|
||||
'subscribe': function () {},
|
||||
'registerCallback': function () {}
|
||||
},
|
||||
'vcard': {
|
||||
'get': function (callback, jid) {
|
||||
var name = jid.split('@')[0].replace('.', ' ').split(' ');
|
||||
var firstname = name[0].charAt(0).toUpperCase()+name[0].slice(1);
|
||||
var lastname = name[1].charAt(0).toUpperCase()+name[1].slice(1);
|
||||
var fullname = firstname+' '+lastname;
|
||||
var vcard = $iq().c('vCard').c('FN').t(fullname);
|
||||
callback(vcard.tree());
|
||||
}
|
||||
},
|
||||
'disco': {
|
||||
'info': function () {},
|
||||
'items': function () {}
|
||||
}
|
||||
};
|
||||
|
||||
// Clear localStorage
|
||||
window.localStorage.clear();
|
||||
this.initialize({
|
||||
prebind: false,
|
||||
xhr_user_search: false,
|
||||
auto_subscribe: false,
|
||||
animate: false
|
||||
});
|
||||
this.onConnected(mock_connection);
|
||||
|
||||
// Variable declarations for specs
|
||||
var open_controlbox;
|
||||
|
||||
describe("The Control Box", $.proxy(function () {
|
||||
@ -82,8 +33,10 @@
|
||||
// open yet.
|
||||
expect($("div#controlbox").is(':visible')).toBe(false);
|
||||
spyOn(this, 'toggleControlBox').andCallThrough();
|
||||
spyOn(this, 'showControlBox').andCallThrough();
|
||||
$('.toggle-online-users').click();
|
||||
expect(this.toggleControlBox).toHaveBeenCalled();
|
||||
expect(this.showControlBox).toHaveBeenCalled();
|
||||
expect($("div#controlbox").is(':visible')).toBe(true);
|
||||
}, converse);
|
||||
it("can be opened by clicking a DOM element with class 'toggle-online-users'", open_controlbox);
|
||||
@ -577,8 +530,13 @@
|
||||
|
||||
it("is cleared when the window is focused", $.proxy(function () {
|
||||
spyOn(converse, 'clearMsgCounter').andCallThrough();
|
||||
$(window).trigger('focus');
|
||||
expect(converse.clearMsgCounter).toHaveBeenCalled();
|
||||
runs(function () {
|
||||
$(window).trigger('focus');
|
||||
});
|
||||
waits(50);
|
||||
runs(function () {
|
||||
expect(converse.clearMsgCounter).toHaveBeenCalled();
|
||||
});
|
||||
}, converse));
|
||||
|
||||
it("is not incremented when the message is received and the window is focused", $.proxy(function () {
|
||||
@ -667,51 +625,5 @@
|
||||
}, converse));
|
||||
}, converse));
|
||||
}, converse));
|
||||
|
||||
describe("A Chat Room", $.proxy(function () {
|
||||
it("shows users currently present in the room", $.proxy(function () {
|
||||
var chatroomview = this.chatboxesview.views['lounge@muc.localhost'];
|
||||
var $participant_list = chatroomview.$el.find('.participant-list');
|
||||
var roster = {}, room = {}, i;
|
||||
for (i=0; i<chatroom_names.length; i++) {
|
||||
roster[chatroom_names[i]] = {};
|
||||
chatroomview.onChatRoomRoster(roster, room);
|
||||
expect($participant_list.find('li').length).toBe(1+i);
|
||||
expect($($participant_list.find('li')[i]).text()).toBe(chatroom_names[i]);
|
||||
}
|
||||
roster[converse.bare_jid] = {};
|
||||
chatroomview.onChatRoomRoster(roster, room);
|
||||
}, converse));
|
||||
|
||||
it("can be saved to, and retrieved from, localStorage", $.proxy(function () {
|
||||
// We instantiate a new ChatBoxes collection, which by default
|
||||
// will be empty.
|
||||
var newchatboxes = new this.ChatBoxes();
|
||||
expect(newchatboxes.length).toEqual(0);
|
||||
// The chatboxes will then be fetched from localStorage inside the
|
||||
// onConnected method
|
||||
newchatboxes.onConnected();
|
||||
expect(newchatboxes.length).toEqual(2); // controlbox is also included
|
||||
// Check that the chatrooms retrieved from localStorage
|
||||
// have the same attributes values as the original ones.
|
||||
attrs = ['id', 'box_id', 'visible'];
|
||||
for (i=0; i<attrs.length; i++) {
|
||||
new_attrs = _.pluck(_.pluck(newchatboxes.models, 'attributes'), attrs[i]);
|
||||
old_attrs = _.pluck(_.pluck(this.chatboxes.models, 'attributes'), attrs[i]);
|
||||
expect(_.isEqual(new_attrs, old_attrs)).toEqual(true);
|
||||
}
|
||||
this.rosterview.render();
|
||||
}, converse));
|
||||
|
||||
it("can be closed again by clicking a DOM element with class 'close-chatbox-button'", $.proxy(function () {
|
||||
var view = this.chatboxesview.views['lounge@muc.localhost'], chatroom = view.model, $el;
|
||||
spyOn(view, 'closeChat').andCallThrough();
|
||||
spyOn(converse.connection.muc, 'leave');
|
||||
view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
|
||||
view.$el.find('.close-chatbox-button').click();
|
||||
expect(view.closeChat).toHaveBeenCalled();
|
||||
expect(converse.connection.muc.leave).toHaveBeenCalled();
|
||||
}, converse));
|
||||
}, converse));
|
||||
}, converse));
|
||||
}));
|
||||
|
@ -1,16 +1,21 @@
|
||||
require(["jquery", "spec/MainSpec"], function($) {
|
||||
|
||||
$(function($) {
|
||||
var jasmineEnv = jasmine.getEnv();
|
||||
jasmineEnv.updateInterval = 500;
|
||||
|
||||
var htmlReporter = new jasmine.HtmlReporter();
|
||||
|
||||
jasmineEnv.addReporter(htmlReporter);
|
||||
|
||||
jasmineEnv.specFilter = function(spec) {
|
||||
return htmlReporter.specFilter(spec);
|
||||
};
|
||||
jasmineEnv.execute();
|
||||
require(["jquery", "converse", "mock", "spec/MainSpec", "spec/ChatRoomSpec"], function($, converse, mock_connection) {
|
||||
// Set up converse.js
|
||||
window.localStorage.clear();
|
||||
converse.initialize({
|
||||
prebind: false,
|
||||
xhr_user_search: false,
|
||||
auto_subscribe: false,
|
||||
animate: false
|
||||
});
|
||||
converse.onConnected(mock_connection);
|
||||
|
||||
// Jasmine stuff
|
||||
var jasmineEnv = jasmine.getEnv();
|
||||
jasmineEnv.updateInterval = 50;
|
||||
var htmlReporter = new jasmine.HtmlReporter();
|
||||
jasmineEnv.addReporter(htmlReporter);
|
||||
jasmineEnv.specFilter = function(spec) {
|
||||
return htmlReporter.specFilter(spec);
|
||||
};
|
||||
jasmineEnv.execute();
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user