chatroom.js 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956
  1. (function (root, factory) {
  2. define(["mock", "test_utils", "utils" ], factory);
  3. } (this, function (mock, test_utils, utils) {
  4. var _ = converse_api.env._;
  5. var $ = converse_api.env.jQuery;
  6. var $pres = converse_api.env.$pres;
  7. var $iq = converse_api.env.$iq;
  8. var $msg = converse_api.env.$msg;
  9. var Strophe = converse_api.env.Strophe;
  10. return describe("ChatRooms", function () {
  11. describe("The \"rooms\" API", function () {
  12. afterEach(function () {
  13. converse_api.user.logout();
  14. converse_api.listen.not();
  15. test_utils.clearBrowserStorage();
  16. });
  17. it("has a method 'close' which closes rooms by JID or all rooms when called with no arguments", mock.initConverse(function (converse) {
  18. test_utils.createContacts(converse, 'current');
  19. runs(function () {
  20. test_utils.openChatRoom(converse, 'lounge', 'localhost', 'dummy');
  21. test_utils.openChatRoom(converse, 'leisure', 'localhost', 'dummy');
  22. test_utils.openChatRoom(converse, 'news', 'localhost', 'dummy');
  23. expect(converse.chatboxviews.get('lounge@localhost').$el.is(':visible')).toBeTruthy();
  24. expect(converse.chatboxviews.get('leisure@localhost').$el.is(':visible')).toBeTruthy();
  25. expect(converse.chatboxviews.get('news@localhost').$el.is(':visible')).toBeTruthy();
  26. });
  27. waits('100');
  28. runs(function () {
  29. converse_api.rooms.close('lounge@localhost');
  30. expect(converse.chatboxviews.get('lounge@localhost')).toBeUndefined();
  31. expect(converse.chatboxviews.get('leisure@localhost').$el.is(':visible')).toBeTruthy();
  32. expect(converse.chatboxviews.get('news@localhost').$el.is(':visible')).toBeTruthy();
  33. converse_api.rooms.close(['leisure@localhost', 'news@localhost']);
  34. expect(converse.chatboxviews.get('lounge@localhost')).toBeUndefined();
  35. expect(converse.chatboxviews.get('leisure@localhost')).toBeUndefined();
  36. expect(converse.chatboxviews.get('news@localhost')).toBeUndefined();
  37. test_utils.openChatRoom(converse, 'lounge', 'localhost', 'dummy');
  38. test_utils.openChatRoom(converse, 'leisure', 'localhost', 'dummy');
  39. expect(converse.chatboxviews.get('lounge@localhost').$el.is(':visible')).toBeTruthy();
  40. expect(converse.chatboxviews.get('leisure@localhost').$el.is(':visible')).toBeTruthy();
  41. });
  42. waits('100');
  43. runs(function () {
  44. converse_api.rooms.close();
  45. expect(converse.chatboxviews.get('lounge@localhost')).toBeUndefined();
  46. expect(converse.chatboxviews.get('leisure@localhost')).toBeUndefined();
  47. });
  48. }));
  49. it("has a method 'get' which returns a wrapped chat room (if it exists)", mock.initConverse(function (converse) {
  50. test_utils.createContacts(converse, 'current');
  51. waits('300'); // ChatBox.show() is debounced for 250ms
  52. runs(function () {
  53. test_utils.openChatRoom(converse, 'lounge', 'localhost', 'dummy');
  54. var jid = 'lounge@localhost';
  55. var room = converse_api.rooms.get(jid);
  56. expect(room instanceof Object).toBeTruthy();
  57. expect(room.is_chatroom).toBeTruthy();
  58. var chatroomview = converse.chatboxviews.get(jid);
  59. expect(chatroomview.$el.is(':visible')).toBeTruthy();
  60. chatroomview.close();
  61. });
  62. waits('300'); // ChatBox.show() is debounced for 250ms
  63. runs(function () {
  64. // Test with mixed case
  65. test_utils.openChatRoom(converse, 'Leisure', 'localhost', 'dummy');
  66. var jid = 'Leisure@localhost';
  67. var room = converse_api.rooms.get(jid);
  68. expect(room instanceof Object).toBeTruthy();
  69. var chatroomview = converse.chatboxviews.get(jid.toLowerCase());
  70. expect(chatroomview.$el.is(':visible')).toBeTruthy();
  71. });
  72. waits('300'); // ChatBox.show() is debounced for 250ms
  73. runs(function () {
  74. var jid = 'leisure@localhost';
  75. var room = converse_api.rooms.get(jid);
  76. expect(room instanceof Object).toBeTruthy();
  77. var chatroomview = converse.chatboxviews.get(jid.toLowerCase());
  78. expect(chatroomview.$el.is(':visible')).toBeTruthy();
  79. jid = 'leiSure@localhost';
  80. room = converse_api.rooms.get(jid);
  81. expect(room instanceof Object).toBeTruthy();
  82. chatroomview = converse.chatboxviews.get(jid.toLowerCase());
  83. expect(chatroomview.$el.is(':visible')).toBeTruthy();
  84. chatroomview.close();
  85. // Non-existing room
  86. jid = 'lounge2@localhost';
  87. room = converse_api.rooms.get(jid);
  88. expect(typeof room === 'undefined').toBeTruthy();
  89. });
  90. }));
  91. it("has a method 'open' which opens and returns a wrapped chat box", mock.initConverse(function (converse) {
  92. test_utils.createContacts(converse, 'current');
  93. var chatroomview;
  94. var jid = 'lounge@localhost';
  95. var room = converse_api.rooms.open(jid);
  96. runs(function () {
  97. // Test on chat room that doesn't exist.
  98. expect(room instanceof Object).toBeTruthy();
  99. expect(room.is_chatroom).toBeTruthy();
  100. chatroomview = converse.chatboxviews.get(jid);
  101. expect(chatroomview.$el.is(':visible')).toBeTruthy();
  102. });
  103. waits('300'); // ChatBox.show() is debounced for 250ms
  104. runs(function () {
  105. // Test again, now that the room exists.
  106. room = converse_api.rooms.open(jid);
  107. expect(room instanceof Object).toBeTruthy();
  108. expect(room.is_chatroom).toBeTruthy();
  109. chatroomview = converse.chatboxviews.get(jid);
  110. expect(chatroomview.$el.is(':visible')).toBeTruthy();
  111. });
  112. waits('300'); // ChatBox.show() is debounced for 250ms
  113. runs(function () {
  114. // Test with mixed case in JID
  115. jid = 'Leisure@localhost';
  116. room = converse_api.rooms.open(jid);
  117. expect(room instanceof Object).toBeTruthy();
  118. chatroomview = converse.chatboxviews.get(jid.toLowerCase());
  119. expect(chatroomview.$el.is(':visible')).toBeTruthy();
  120. jid = 'leisure@localhost';
  121. room = converse_api.rooms.open(jid);
  122. expect(room instanceof Object).toBeTruthy();
  123. chatroomview = converse.chatboxviews.get(jid.toLowerCase());
  124. expect(chatroomview.$el.is(':visible')).toBeTruthy();
  125. jid = 'leiSure@localhost';
  126. room = converse_api.rooms.open(jid);
  127. expect(room instanceof Object).toBeTruthy();
  128. chatroomview = converse.chatboxviews.get(jid.toLowerCase());
  129. expect(chatroomview.$el.is(':visible')).toBeTruthy();
  130. chatroomview.close();
  131. });
  132. }));
  133. });
  134. describe("A Chat Room", function () {
  135. afterEach(function () {
  136. converse_api.user.logout();
  137. converse_api.listen.not();
  138. test_utils.clearBrowserStorage();
  139. });
  140. it("can have spaces and special characters in its name", mock.initConverse(function (converse) {
  141. test_utils.openChatRoom(converse, 'lounge & leisure', 'localhost', 'dummy');
  142. var view = converse.chatboxviews.get(
  143. Strophe.escapeNode('lounge & leisure')+'@localhost');
  144. expect(view instanceof converse.ChatRoomView).toBe(true);
  145. }));
  146. it("shows users currently present in the room", mock.initConverse(function (converse) {
  147. test_utils.openAndEnterChatRoom(converse, 'lounge', 'localhost', 'dummy');
  148. var name;
  149. var view = converse.chatboxviews.get('lounge@localhost'),
  150. $occupants = view.$('.occupant-list');
  151. var presence, role;
  152. for (var i=0; i<mock.chatroom_names.length; i++) {
  153. name = mock.chatroom_names[i];
  154. role = mock.chatroom_roles[name].role;
  155. // See example 21 http://xmpp.org/extensions/xep-0045.html#enter-pres
  156. presence = $pres({
  157. to:'dummy@localhost/pda',
  158. from:'lounge@localhost/'+name
  159. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  160. .c('item').attrs({
  161. affiliation: mock.chatroom_roles[name].affiliation,
  162. jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
  163. role: role
  164. }).up()
  165. .c('status').attrs({code:'110'}).nodeTree;
  166. converse.connection._dataRecv(test_utils.createRequest(presence));
  167. expect($occupants.find('li').length).toBe(2+i);
  168. expect($($occupants.find('li')[i+1]).text()).toBe(mock.chatroom_names[i]);
  169. expect($($occupants.find('li')[i+1]).hasClass('moderator')).toBe(role === "moderator");
  170. }
  171. // Test users leaving the room
  172. // http://xmpp.org/extensions/xep-0045.html#exit
  173. for (i=mock.chatroom_names.length-1; i>-1; i--) {
  174. name = mock.chatroom_names[i];
  175. role = mock.chatroom_roles[name].role;
  176. // See example 21 http://xmpp.org/extensions/xep-0045.html#enter-pres
  177. presence = $pres({
  178. to:'dummy@localhost/pda',
  179. from:'lounge@localhost/'+name,
  180. type: 'unavailable'
  181. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  182. .c('item').attrs({
  183. affiliation: mock.chatroom_roles[name].affiliation,
  184. jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
  185. role: 'none'
  186. }).nodeTree;
  187. converse.connection._dataRecv(test_utils.createRequest(presence));
  188. expect($occupants.find('li').length).toBe(i+1);
  189. }
  190. }));
  191. it("indicates moderators by means of a special css class and tooltip", mock.initConverse(function (converse) {
  192. test_utils.openAndEnterChatRoom(converse, 'lounge', 'localhost', 'dummy');
  193. var view = converse.chatboxviews.get('lounge@localhost');
  194. var presence = $pres({
  195. to:'dummy@localhost/pda',
  196. from:'lounge@localhost/moderatorman'
  197. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  198. .c('item').attrs({
  199. affiliation: 'admin',
  200. jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
  201. role: 'moderator',
  202. }).up()
  203. .c('status').attrs({code:'110'}).nodeTree;
  204. converse.connection._dataRecv(test_utils.createRequest(presence));
  205. var occupant = view.$el.find('.occupant-list').find('li');
  206. expect(occupant.length).toBe(2);
  207. expect($(occupant).first().text()).toBe("dummy");
  208. expect($(occupant).last().text()).toBe("moderatorman");
  209. expect($(occupant).last().attr('class').indexOf('moderator')).not.toBe(-1);
  210. expect($(occupant).last().attr('title')).toBe('This user is a moderator. Click to mention this user in your message.');
  211. }));
  212. it("will use the user's reserved nickname, if it exists", mock.initConverse(function (converse) {
  213. var sent_IQ, IQ_id;
  214. var sendIQ = converse.connection.sendIQ;
  215. spyOn(converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
  216. sent_IQ = iq;
  217. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  218. });
  219. test_utils.openChatRoom(converse, 'lounge', 'localhost', 'dummy');
  220. var view = converse.chatboxviews.get('lounge@localhost');
  221. spyOn(view, 'join').andCallThrough();
  222. /* <iq from='hag66@shakespeare.lit/pda'
  223. * id='getnick1'
  224. * to='coven@chat.shakespeare.lit'
  225. * type='get'>
  226. * <query xmlns='http://jabber.org/protocol/disco#info'
  227. * node='x-roomuser-item'/>
  228. * </iq>
  229. */
  230. expect(sent_IQ.toLocaleString()).toBe(
  231. "<iq to='lounge@localhost' from='dummy@localhost/resource' "+
  232. "type='get' xmlns='jabber:client' id='"+IQ_id+"'>"+
  233. "<query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item'/></iq>"
  234. );
  235. /* <iq from='coven@chat.shakespeare.lit'
  236. * id='getnick1'
  237. * to='hag66@shakespeare.lit/pda'
  238. * type='result'>
  239. * <query xmlns='http://jabber.org/protocol/disco#info'
  240. * node='x-roomuser-item'>
  241. * <identity
  242. * category='conference'
  243. * name='thirdwitch'
  244. * type='text'/>
  245. * </query>
  246. * </iq>
  247. */
  248. var stanza = $iq({
  249. 'type': 'result',
  250. 'id': IQ_id,
  251. 'from': view.model.get('jid'),
  252. 'to': converse.connection.jid
  253. }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info', 'node': 'x-roomuser-item'})
  254. .c('identity', {'category': 'conference', 'name': 'thirdwitch', 'type': 'text'});
  255. converse.connection._dataRecv(test_utils.createRequest(stanza));
  256. expect(view.join).toHaveBeenCalled();
  257. // The user has just entered the room (because join was called)
  258. // and receives their own presence from the server.
  259. // See example 24:
  260. // http://xmpp.org/extensions/xep-0045.html#enter-pres
  261. var presence = $pres({
  262. to:'dummy@localhost/resource',
  263. from:'lounge@localhost/thirdwitch',
  264. id:'DC352437-C019-40EC-B590-AF29E879AF97'
  265. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  266. .c('item').attrs({
  267. affiliation: 'member',
  268. jid: 'dummy@localhost/resource',
  269. role: 'occupant'
  270. }).up()
  271. .c('status').attrs({code:'110'}).up()
  272. .c('status').attrs({code:'210'}).nodeTree;
  273. converse.connection._dataRecv(test_utils.createRequest(presence));
  274. var info_text = view.$el.find('.chat-content .chat-info').text();
  275. expect(info_text).toBe('Your nickname has been automatically set to: thirdwitch');
  276. }));
  277. it("allows the user to invite their roster contacts to enter the chat room", mock.initConverse(function (converse) {
  278. test_utils.openChatRoom(converse, 'lounge', 'localhost', 'dummy');
  279. spyOn(converse, 'emit');
  280. spyOn(window, 'prompt').andCallFake(function () {
  281. return null;
  282. });
  283. var $input;
  284. var view = converse.chatboxviews.get('lounge@localhost');
  285. view.$el.find('.chat-area').remove();
  286. view.renderChatArea(); // Will init the widget
  287. test_utils.createContacts(converse, 'current'); // We need roster contacts, so that we have someone to invite
  288. $input = view.$el.find('input.invited-contact.tt-input');
  289. var $hint = view.$el.find('input.invited-contact.tt-hint');
  290. runs (function () {
  291. expect($input.length).toBe(1);
  292. expect($input.attr('placeholder')).toBe('Invite');
  293. $input.val("Felix");
  294. $input.trigger('input');
  295. });
  296. waits(350); // Needed, due to debounce
  297. runs (function () {
  298. expect($input.val()).toBe('Felix');
  299. expect($hint.val()).toBe('Felix Amsel');
  300. var $sugg = view.$el.find('[data-jid="felix.amsel@localhost"]');
  301. expect($sugg.length).toBe(1);
  302. $sugg.trigger('click');
  303. expect(window.prompt).toHaveBeenCalled();
  304. });
  305. }));
  306. it("can be joined automatically, based upon a received invite", mock.initConverse(function (converse) {
  307. test_utils.openChatRoom(converse, 'lounge', 'localhost', 'dummy');
  308. spyOn(window, 'confirm').andCallFake(function () {
  309. return true;
  310. });
  311. test_utils.createContacts(converse, 'current'); // We need roster contacts, who can invite us
  312. var view = converse.chatboxviews.get('lounge@localhost');
  313. view.close();
  314. var name = mock.cur_names[0];
  315. var from_jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
  316. var room_jid = 'lounge@localhost';
  317. var reason = "Please join this chat room";
  318. var message = $(
  319. "<message from='"+from_jid+"' to='"+converse.bare_jid+"'>" +
  320. "<x xmlns='jabber:x:conference'" +
  321. "jid='"+room_jid+"'" +
  322. "reason='"+reason+"'/>"+
  323. "</message>"
  324. )[0];
  325. expect(converse.chatboxes.models.length).toBe(1);
  326. expect(converse.chatboxes.models[0].id).toBe("controlbox");
  327. converse.onDirectMUCInvitation(message);
  328. expect(window.confirm).toHaveBeenCalledWith(
  329. name + ' has invited you to join a chat room: '+ room_jid +
  330. ', and left the following reason: "'+reason+'"');
  331. expect(converse.chatboxes.models.length).toBe(2);
  332. expect(converse.chatboxes.models[0].id).toBe('controlbox');
  333. expect(converse.chatboxes.models[1].id).toBe(room_jid);
  334. }));
  335. it("shows received groupchat messages", mock.initConverse(function (converse) {
  336. test_utils.openChatRoom(converse, 'lounge', 'localhost', 'dummy');
  337. spyOn(converse, 'emit');
  338. var view = converse.chatboxviews.get('lounge@localhost');
  339. if (!view.$el.find('.chat-area').length) { view.renderChatArea(); }
  340. var nick = mock.chatroom_names[0];
  341. var text = 'This is a received message';
  342. var message = $msg({
  343. from: 'lounge@localhost/'+nick,
  344. id: '1',
  345. to: 'dummy@localhost',
  346. type: 'groupchat'
  347. }).c('body').t(text);
  348. view.onChatRoomMessage(message.nodeTree);
  349. var $chat_content = view.$el.find('.chat-content');
  350. expect($chat_content.find('.chat-message').length).toBe(1);
  351. expect($chat_content.find('.chat-msg-content').text()).toBe(text);
  352. expect(converse.emit).toHaveBeenCalledWith('message', message.nodeTree);
  353. }));
  354. it("shows sent groupchat messages", mock.initConverse(function (converse) {
  355. test_utils.openAndEnterChatRoom(converse, 'lounge', 'localhost', 'dummy');
  356. spyOn(converse, 'emit');
  357. var view = converse.chatboxviews.get('lounge@localhost');
  358. if (!view.$el.find('.chat-area').length) { view.renderChatArea(); }
  359. var text = 'This is a sent message';
  360. view.$el.find('.chat-textarea').text(text);
  361. view.$el.find('textarea.chat-textarea').trigger($.Event('keypress', {keyCode: 13}));
  362. expect(converse.emit).toHaveBeenCalledWith('messageSend', text);
  363. var $chat_content = view.$el.find('.chat-content');
  364. expect($chat_content.find('.chat-message').length).toBe(1);
  365. // Let's check that if we receive the same message again, it's
  366. // not shown.
  367. var message = $msg({
  368. from: 'lounge@localhost/dummy',
  369. to: 'dummy@localhost.com',
  370. type: 'groupchat',
  371. id: view.model.messages.at(0).get('msgid')
  372. }).c('body').t(text);
  373. view.onChatRoomMessage(message.nodeTree);
  374. expect($chat_content.find('.chat-message').length).toBe(1);
  375. expect($chat_content.find('.chat-msg-content').last().text()).toBe(text);
  376. // We don't emit an event if it's our own message
  377. expect(converse.emit.callCount, 1);
  378. }));
  379. it("will cause the chat area to be scrolled down only if it was at the bottom already", mock.initConverse(function (converse) {
  380. var message = 'This message is received while the chat area is scrolled up';
  381. test_utils.openAndEnterChatRoom(converse, 'lounge', 'localhost', 'dummy');
  382. var view = converse.chatboxviews.get('lounge@localhost');
  383. spyOn(view, 'scrollDown').andCallThrough();
  384. runs(function () {
  385. /* Create enough messages so that there's a
  386. * scrollbar.
  387. */
  388. for (var i=0; i<20; i++) {
  389. converse.chatboxes.onMessage(
  390. $msg({
  391. from: 'lounge@localhost/someone',
  392. to: 'dummy@localhost.com',
  393. type: 'groupchat',
  394. id: (new Date()).getTime(),
  395. }).c('body').t('Message: '+i).tree());
  396. }
  397. });
  398. waits(50);
  399. runs(function () {
  400. view.$content.scrollTop(0);
  401. });
  402. waits(250);
  403. runs(function () {
  404. expect(view.model.get('scrolled')).toBeTruthy();
  405. converse.chatboxes.onMessage(
  406. $msg({
  407. from: 'lounge@localhost/someone',
  408. to: 'dummy@localhost.com',
  409. type: 'groupchat',
  410. id: (new Date()).getTime(),
  411. }).c('body').t(message).tree());
  412. });
  413. waits(150);
  414. runs(function () {
  415. // Now check that the message appears inside the chatbox in the DOM
  416. var $chat_content = view.$el.find('.chat-content');
  417. var msg_txt = $chat_content.find('.chat-message:last').find('.chat-msg-content').text();
  418. expect(msg_txt).toEqual(message);
  419. expect(view.$content.scrollTop()).toBe(0);
  420. });
  421. }));
  422. it("shows received chatroom subject messages", mock.initConverse(function (converse) {
  423. test_utils.openAndEnterChatRoom(converse, 'jdev', 'conference.jabber.org', 'jc');
  424. var text = 'Jabber/XMPP Development | RFCs and Extensions: http://xmpp.org/ | Protocol and XSF discussions: xsf@muc.xmpp.org';
  425. var stanza = Strophe.xmlHtmlNode(
  426. '<message xmlns="jabber:client" to="jc@opkode.com/converse.js-60429116" type="groupchat" from="jdev@conference.jabber.org/ralphm">'+
  427. ' <subject>'+text+'</subject>'+
  428. ' <delay xmlns="urn:xmpp:delay" stamp="2014-02-04T09:35:39Z" from="jdev@conference.jabber.org"/>'+
  429. ' <x xmlns="jabber:x:delay" stamp="20140204T09:35:39" from="jdev@conference.jabber.org"/>'+
  430. '</message>').firstChild;
  431. converse.connection._dataRecv(test_utils.createRequest(stanza));
  432. var view = converse.chatboxviews.get('jdev@conference.jabber.org');
  433. var $chat_content = view.$el.find('.chat-content');
  434. expect($chat_content.find('.chat-info').length).toBe(1);
  435. expect($chat_content.find('.chat-info').text()).toBe('Topic set by ralphm to: '+text);
  436. }));
  437. it("informs users if their nicknames has been changed.", mock.initConverse(function (converse) {
  438. /* The service then sends two presence stanzas to the full JID
  439. * of each occupant (including the occupant who is changing his
  440. * or her room nickname), one of type "unavailable" for the old
  441. * nickname and one indicating availability for the new
  442. * nickname.
  443. *
  444. * See: http://xmpp.org/extensions/xep-0045.html#changenick
  445. *
  446. * <presence
  447. * from='coven@localhost/thirdwitch'
  448. * id='DC352437-C019-40EC-B590-AF29E879AF98'
  449. * to='hag66@shakespeare.lit/pda'
  450. * type='unavailable'>
  451. * <x xmlns='http://jabber.org/protocol/muc#user'>
  452. * <item affiliation='member'
  453. * jid='hag66@shakespeare.lit/pda'
  454. * nick='oldhag'
  455. * role='occupant'/>
  456. * <status code='303'/>
  457. * <status code='110'/>
  458. * </x>
  459. * </presence>
  460. *
  461. * <presence
  462. * from='coven@localhost/oldhag'
  463. * id='5B4F27A4-25ED-43F7-A699-382C6B4AFC67'
  464. * to='hag66@shakespeare.lit/pda'>
  465. * <x xmlns='http://jabber.org/protocol/muc#user'>
  466. * <item affiliation='member'
  467. * jid='hag66@shakespeare.lit/pda'
  468. * role='occupant'/>
  469. * <status code='110'/>
  470. * </x>
  471. * </presence>
  472. */
  473. var __ = utils.__.bind(converse);
  474. test_utils.openAndEnterChatRoom(converse, 'lounge', 'localhost', 'oldnick');
  475. var view = converse.chatboxviews.get('lounge@localhost');
  476. var $chat_content = view.$el.find('.chat-content');
  477. // The user has just entered the room and receives their own
  478. // presence from the server.
  479. // See example 24:
  480. // http://xmpp.org/extensions/xep-0045.html#enter-pres
  481. var presence = $pres({
  482. to:'dummy@localhost/pda',
  483. from:'lounge@localhost/oldnick',
  484. id:'DC352437-C019-40EC-B590-AF29E879AF97'
  485. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  486. .c('item').attrs({
  487. affiliation: 'member',
  488. jid: 'dummy@localhost/pda',
  489. role: 'occupant'
  490. }).up()
  491. .c('status').attrs({code:'110'}).up()
  492. .c('status').attrs({code:'210'}).nodeTree;
  493. converse.connection._dataRecv(test_utils.createRequest(presence));
  494. var $occupants = view.$('.occupant-list');
  495. expect($occupants.children().length).toBe(1);
  496. expect($occupants.children().first(0).text()).toBe("oldnick");
  497. expect($chat_content.find('div.chat-info').length).toBe(1);
  498. expect($chat_content.find('div.chat-info').html()).toBe(__(converse.muc.new_nickname_messages["210"], "oldnick"));
  499. presence = $pres().attrs({
  500. from:'lounge@localhost/oldnick',
  501. id:'DC352437-C019-40EC-B590-AF29E879AF98',
  502. to:'dummy@localhost/pda',
  503. type:'unavailable'
  504. })
  505. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  506. .c('item').attrs({
  507. affiliation: 'member',
  508. jid: 'dummy@localhost/pda',
  509. nick: 'newnick',
  510. role: 'occupant'
  511. }).up()
  512. .c('status').attrs({code:'303'}).up()
  513. .c('status').attrs({code:'110'}).nodeTree;
  514. converse.connection._dataRecv(test_utils.createRequest(presence));
  515. expect($chat_content.find('div.chat-info').length).toBe(2);
  516. expect($chat_content.find('div.chat-info').last().html()).toBe(__(converse.muc.new_nickname_messages["303"], "newnick"));
  517. $occupants = view.$('.occupant-list');
  518. expect($occupants.children().length).toBe(0);
  519. presence = $pres().attrs({
  520. from:'lounge@localhost/newnick',
  521. id:'5B4F27A4-25ED-43F7-A699-382C6B4AFC67',
  522. to:'dummy@localhost/pda'
  523. })
  524. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  525. .c('item').attrs({
  526. affiliation: 'member',
  527. jid: 'dummy@localhost/pda',
  528. role: 'occupant'
  529. }).up()
  530. .c('status').attrs({code:'110'}).nodeTree;
  531. converse.connection._dataRecv(test_utils.createRequest(presence));
  532. expect($chat_content.find('div.chat-info').length).toBe(2);
  533. expect($chat_content.find('div.chat-info').last().html()).toBe(__(converse.muc.new_nickname_messages["303"], "newnick"));
  534. $occupants = view.$('.occupant-list');
  535. expect($occupants.children().length).toBe(1);
  536. expect($occupants.children().first(0).text()).toBe("newnick");
  537. }));
  538. it("informs users if they have been kicked out of the chat room", mock.initConverse(function (converse) {
  539. /* <presence
  540. * from='harfleur@chat.shakespeare.lit/pistol'
  541. * to='pistol@shakespeare.lit/harfleur'
  542. * type='unavailable'>
  543. * <x xmlns='http://jabber.org/protocol/muc#user'>
  544. * <item affiliation='none' role='none'>
  545. * <actor nick='Fluellen'/>
  546. * <reason>Avaunt, you cullion!</reason>
  547. * </item>
  548. * <status code='307'/>
  549. * </x>
  550. * </presence>
  551. */
  552. test_utils.openAndEnterChatRoom(converse, 'lounge', 'localhost', 'dummy');
  553. var presence = $pres().attrs({
  554. from:'lounge@localhost/dummy',
  555. to:'dummy@localhost/pda',
  556. type:'unavailable'
  557. })
  558. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  559. .c('item').attrs({
  560. affiliation: 'none',
  561. jid: 'dummy@localhost/pda',
  562. role: 'none'
  563. })
  564. .c('actor').attrs({nick: 'Fluellen'}).up()
  565. .c('reason').t('Avaunt, you cullion!').up()
  566. .up()
  567. .c('status').attrs({code:'307'}).nodeTree;
  568. var view = converse.chatboxviews.get('lounge@localhost');
  569. view.onChatRoomPresence(presence);
  570. expect(view.$('.chat-area').is(':visible')).toBeFalsy();
  571. expect(view.$('.occupants').is(':visible')).toBeFalsy();
  572. var $chat_body = view.$('.chatroom-body');
  573. expect($chat_body.html().trim().indexOf(
  574. '<p>You have been kicked from this room</p>'+
  575. '<p>This action was done by <strong>Fluellen</strong>.</p>'+
  576. '<p>The reason given is: <em>"Avaunt, you cullion!"</em>.</p>'
  577. )).not.toBe(-1);
  578. }));
  579. it("can be saved to, and retrieved from, browserStorage", mock.initConverse(function (converse) {
  580. test_utils.openChatRoom(converse, 'lounge', 'localhost', 'dummy');
  581. // We instantiate a new ChatBoxes collection, which by default
  582. // will be empty.
  583. test_utils.openControlBox();
  584. var newchatboxes = new converse.ChatBoxes();
  585. expect(newchatboxes.length).toEqual(0);
  586. // The chatboxes will then be fetched from browserStorage inside the
  587. // onConnected method
  588. newchatboxes.onConnected();
  589. expect(newchatboxes.length).toEqual(2);
  590. // Check that the chatrooms retrieved from browserStorage
  591. // have the same attributes values as the original ones.
  592. var attrs = ['id', 'box_id', 'visible'];
  593. var new_attrs, old_attrs;
  594. for (var i=0; i<attrs.length; i++) {
  595. new_attrs = _.pluck(_.pluck(newchatboxes.models, 'attributes'), attrs[i]);
  596. old_attrs = _.pluck(_.pluck(converse.chatboxes.models, 'attributes'), attrs[i]);
  597. // FIXME: should have have to sort here? Order must
  598. // probably be the same...
  599. // This should be fixed once the controlbox always opens
  600. // only on the right.
  601. expect(_.isEqual(new_attrs.sort(), old_attrs.sort())).toEqual(true);
  602. }
  603. converse.rosterview.render();
  604. }));
  605. it("can be minimized by clicking a DOM element with class 'toggle-chatbox-button'", mock.initConverse(function (converse) {
  606. test_utils.openChatRoom(converse, 'lounge', 'localhost', 'dummy');
  607. var view = converse.chatboxviews.get('lounge@localhost'),
  608. trimmed_chatboxes = converse.minimized_chats;
  609. spyOn(view, 'minimize').andCallThrough();
  610. spyOn(view, 'maximize').andCallThrough();
  611. spyOn(converse, 'emit');
  612. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  613. runs(function () {
  614. view.$el.find('.toggle-chatbox-button').click();
  615. });
  616. waits(350);
  617. runs(function () {
  618. expect(view.minimize).toHaveBeenCalled();
  619. expect(converse.emit).toHaveBeenCalledWith('chatBoxMinimized', jasmine.any(Object));
  620. expect(view.$el.is(':visible')).toBeFalsy();
  621. expect(view.model.get('minimized')).toBeTruthy();
  622. expect(view.minimize).toHaveBeenCalled();
  623. var trimmedview = trimmed_chatboxes.get(view.model.get('id'));
  624. trimmedview.$("a.restore-chat").click();
  625. });
  626. waits(350);
  627. runs(function () {
  628. expect(view.maximize).toHaveBeenCalled();
  629. expect(converse.emit).toHaveBeenCalledWith('chatBoxMaximized', jasmine.any(Object));
  630. expect(view.$el.is(':visible')).toBeTruthy();
  631. expect(view.model.get('minimized')).toBeFalsy();
  632. expect(converse.emit.callCount, 3);
  633. });
  634. }));
  635. it("can be closed again by clicking a DOM element with class 'close-chatbox-button'", mock.initConverse(function (converse) {
  636. test_utils.openChatRoom(converse, 'lounge', 'localhost', 'dummy');
  637. var view = converse.chatboxviews.get('lounge@localhost');
  638. spyOn(view, 'close').andCallThrough();
  639. spyOn(converse, 'emit');
  640. spyOn(view, 'leave');
  641. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  642. runs(function () {
  643. view.$el.find('.close-chatbox-button').click();
  644. });
  645. waits(50);
  646. runs(function () {
  647. expect(view.close).toHaveBeenCalled();
  648. expect(view.leave).toHaveBeenCalled();
  649. expect(converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
  650. });
  651. }));
  652. });
  653. describe("Each chat room can take special commands", function () {
  654. afterEach(function () {
  655. converse_api.user.logout();
  656. converse_api.listen.not();
  657. test_utils.clearBrowserStorage();
  658. });
  659. it("to clear messages", mock.initConverse(function (converse) {
  660. test_utils.openChatRoom(converse, 'lounge', 'localhost', 'dummy');
  661. var view = converse.chatboxviews.get('lounge@localhost');
  662. spyOn(view, 'onMessageSubmitted').andCallThrough();
  663. spyOn(view, 'clearChatRoomMessages');
  664. view.$el.find('.chat-textarea').text('/clear');
  665. view.$el.find('textarea.chat-textarea').trigger($.Event('keypress', {keyCode: 13}));
  666. expect(view.onMessageSubmitted).toHaveBeenCalled();
  667. expect(view.clearChatRoomMessages).toHaveBeenCalled();
  668. }));
  669. it("to ban a user", mock.initConverse(function (converse) {
  670. test_utils.openChatRoom(converse, 'lounge', 'localhost', 'dummy');
  671. var view = converse.chatboxviews.get('lounge@localhost');
  672. spyOn(view, 'onMessageSubmitted').andCallThrough();
  673. spyOn(view, 'setAffiliation').andCallThrough();
  674. spyOn(view, 'showStatusNotification').andCallThrough();
  675. spyOn(view, 'validateRoleChangeCommand').andCallThrough();
  676. view.$el.find('.chat-textarea').text('/ban');
  677. view.$el.find('textarea.chat-textarea').trigger($.Event('keypress', {keyCode: 13}));
  678. expect(view.onMessageSubmitted).toHaveBeenCalled();
  679. expect(view.validateRoleChangeCommand).toHaveBeenCalled();
  680. expect(view.showStatusNotification).toHaveBeenCalledWith(
  681. "Error: the \"ban\" command takes two arguments, the user's nickname and optionally a reason.",
  682. true
  683. );
  684. expect(view.setAffiliation).not.toHaveBeenCalled();
  685. // Call now with the correct amount of arguments.
  686. // XXX: Calling onMessageSubmitted directly, trying
  687. // again via triggering Event doesn't work for some weird
  688. // reason.
  689. view.onMessageSubmitted('/ban jid This is the reason');
  690. expect(view.validateRoleChangeCommand.callCount).toBe(2);
  691. expect(view.showStatusNotification.callCount).toBe(1);
  692. expect(view.setAffiliation).toHaveBeenCalled();
  693. }));
  694. });
  695. describe("When attempting to enter a chatroom", function () {
  696. afterEach(function () {
  697. converse_api.user.logout();
  698. converse_api.listen.not();
  699. test_utils.clearBrowserStorage();
  700. });
  701. var submitRoomForm = function (converse) {
  702. var roomspanel = converse.chatboxviews.get('controlbox').roomspanel;
  703. var $input = roomspanel.$el.find('input.new-chatroom-name');
  704. var $nick = roomspanel.$el.find('input.new-chatroom-nick');
  705. var $server = roomspanel.$el.find('input.new-chatroom-server');
  706. $input.val('problematic');
  707. $nick.val('dummy');
  708. $server.val('muc.localhost');
  709. roomspanel.$el.find('form').submit();
  710. };
  711. it("will show an error message if the room requires a password", mock.initConverse(function (converse) {
  712. submitRoomForm(converse);
  713. var presence = $pres().attrs({
  714. from:'lounge@localhost/thirdwitch',
  715. id:'n13mt3l',
  716. to:'dummy@localhost/pda',
  717. type:'error'})
  718. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  719. .c('error').attrs({by:'lounge@localhost', type:'auth'})
  720. .c('not-authorized').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  721. var view = converse.chatboxviews.get('problematic@muc.localhost');
  722. spyOn(view, 'renderPasswordForm').andCallThrough();
  723. runs(function () {
  724. view.onChatRoomPresence(presence);
  725. });
  726. waits(250);
  727. runs(function () {
  728. var $chat_body = view.$el.find('.chatroom-body');
  729. expect(view.renderPasswordForm).toHaveBeenCalled();
  730. expect($chat_body.find('form.chatroom-form').length).toBe(1);
  731. expect($chat_body.find('legend').text()).toBe('This chatroom requires a password');
  732. });
  733. }));
  734. it("will show an error message if the room is members-only and the user not included", mock.initConverse(function (converse) {
  735. submitRoomForm(converse);
  736. var presence = $pres().attrs({
  737. from:'lounge@localhost/thirdwitch',
  738. id:'n13mt3l',
  739. to:'dummy@localhost/pda',
  740. type:'error'})
  741. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  742. .c('error').attrs({by:'lounge@localhost', type:'auth'})
  743. .c('registration-required').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  744. var view = converse.chatboxviews.get('problematic@muc.localhost');
  745. spyOn(view, 'showErrorMessage').andCallThrough();
  746. view.onChatRoomPresence(presence);
  747. expect(view.$el.find('.chatroom-body p:last').text()).toBe('You are not on the member list of this room');
  748. }));
  749. it("will show an error message if the user has been banned", mock.initConverse(function (converse) {
  750. submitRoomForm(converse);
  751. var presence = $pres().attrs({
  752. from:'lounge@localhost/thirdwitch',
  753. id:'n13mt3l',
  754. to:'dummy@localhost/pda',
  755. type:'error'})
  756. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  757. .c('error').attrs({by:'lounge@localhost', type:'auth'})
  758. .c('forbidden').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  759. var view = converse.chatboxviews.get('problematic@muc.localhost');
  760. spyOn(view, 'showErrorMessage').andCallThrough();
  761. view.onChatRoomPresence(presence);
  762. expect(view.$el.find('.chatroom-body p:last').text()).toBe('You have been banned from this room');
  763. }));
  764. it("will render a nickname form if a nickname conflict happens and muc_nickname_from_jid=false", mock.initConverse(function (converse) {
  765. submitRoomForm(converse);
  766. var presence = $pres().attrs({
  767. from:'lounge@localhost/thirdwitch',
  768. id:'n13mt3l',
  769. to:'dummy@localhost/pda',
  770. type:'error'})
  771. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  772. .c('error').attrs({by:'lounge@localhost', type:'cancel'})
  773. .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  774. var view = converse.chatboxviews.get('problematic@muc.localhost');
  775. spyOn(view, 'showErrorMessage').andCallThrough();
  776. view.onChatRoomPresence(presence);
  777. expect(view.$el.find('.chatroom-body form.chatroom-form label:first').text()).toBe('Please choose your nickname');
  778. }));
  779. it("will automatically choose a new nickname if a nickname conflict happens and muc_nickname_from_jid=true", mock.initConverse(function (converse) {
  780. /*
  781. <presence
  782. from='coven@chat.shakespeare.lit/thirdwitch'
  783. id='n13mt3l'
  784. to='hag66@shakespeare.lit/pda'
  785. type='error'>
  786. <x xmlns='http://jabber.org/protocol/muc'/>
  787. <error by='coven@chat.shakespeare.lit' type='cancel'>
  788. <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
  789. </error>
  790. </presence>
  791. */
  792. submitRoomForm(converse);
  793. converse.muc_nickname_from_jid = true;
  794. var attrs = {
  795. from:'lounge@localhost/dummy',
  796. id:'n13mt3l',
  797. to:'dummy@localhost/pda',
  798. type:'error'
  799. };
  800. var presence = $pres().attrs(attrs)
  801. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  802. .c('error').attrs({by:'lounge@localhost', type:'cancel'})
  803. .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  804. var view = converse.chatboxviews.get('problematic@muc.localhost');
  805. spyOn(view, 'showErrorMessage').andCallThrough();
  806. spyOn(view, 'join').andCallThrough();
  807. // Simulate repeatedly that there's already someone in the room
  808. // with that nickname
  809. view.onChatRoomPresence(presence);
  810. expect(view.join).toHaveBeenCalledWith('dummy-2');
  811. attrs.from = 'lounge@localhost/dummy-2';
  812. presence = $pres().attrs(attrs)
  813. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  814. .c('error').attrs({by:'lounge@localhost', type:'cancel'})
  815. .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  816. view.onChatRoomPresence(presence);
  817. expect(view.join).toHaveBeenCalledWith('dummy-3');
  818. attrs.from = 'lounge@localhost/dummy-3';
  819. presence = $pres().attrs(attrs)
  820. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  821. .c('error').attrs({by:'lounge@localhost', type:'cancel'})
  822. .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  823. view.onChatRoomPresence(presence);
  824. expect(view.join).toHaveBeenCalledWith('dummy-4');
  825. }));
  826. it("will show an error message if the user is not allowed to have created the room", mock.initConverse(function (converse) {
  827. submitRoomForm(converse);
  828. var presence = $pres().attrs({
  829. from:'lounge@localhost/thirdwitch',
  830. id:'n13mt3l',
  831. to:'dummy@localhost/pda',
  832. type:'error'})
  833. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  834. .c('error').attrs({by:'lounge@localhost', type:'cancel'})
  835. .c('not-allowed').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  836. var view = converse.chatboxviews.get('problematic@muc.localhost');
  837. spyOn(view, 'showErrorMessage').andCallThrough();
  838. view.onChatRoomPresence(presence);
  839. expect(view.$el.find('.chatroom-body p:last').text()).toBe('You are not allowed to create new rooms');
  840. }));
  841. it("will show an error message if the user's nickname doesn't conform to room policy", mock.initConverse(function (converse) {
  842. submitRoomForm(converse);
  843. var presence = $pres().attrs({
  844. from:'lounge@localhost/thirdwitch',
  845. id:'n13mt3l',
  846. to:'dummy@localhost/pda',
  847. type:'error'})
  848. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  849. .c('error').attrs({by:'lounge@localhost', type:'cancel'})
  850. .c('not-acceptable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  851. var view = converse.chatboxviews.get('problematic@muc.localhost');
  852. spyOn(view, 'showErrorMessage').andCallThrough();
  853. view.onChatRoomPresence(presence);
  854. expect(view.$el.find('.chatroom-body p:last').text()).toBe("Your nickname doesn't conform to this room's policies");
  855. }));
  856. it("will show an error message if the room doesn't yet exist", mock.initConverse(function (converse) {
  857. submitRoomForm(converse);
  858. var presence = $pres().attrs({
  859. from:'lounge@localhost/thirdwitch',
  860. id:'n13mt3l',
  861. to:'dummy@localhost/pda',
  862. type:'error'})
  863. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  864. .c('error').attrs({by:'lounge@localhost', type:'cancel'})
  865. .c('item-not-found').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  866. var view = converse.chatboxviews.get('problematic@muc.localhost');
  867. spyOn(view, 'showErrorMessage').andCallThrough();
  868. view.onChatRoomPresence(presence);
  869. expect(view.$el.find('.chatroom-body p:last').text()).toBe("This room does not (yet) exist");
  870. }));
  871. it("will show an error message if the room has reached its maximum number of occupants", mock.initConverse(function (converse) {
  872. submitRoomForm(converse);
  873. var presence = $pres().attrs({
  874. from:'lounge@localhost/thirdwitch',
  875. id:'n13mt3l',
  876. to:'dummy@localhost/pda',
  877. type:'error'})
  878. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  879. .c('error').attrs({by:'lounge@localhost', type:'cancel'})
  880. .c('service-unavailable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  881. var view = converse.chatboxviews.get('problematic@muc.localhost');
  882. spyOn(view, 'showErrorMessage').andCallThrough();
  883. view.onChatRoomPresence(presence);
  884. expect(view.$el.find('.chatroom-body p:last').text()).toBe("This room has reached its maximum number of occupants");
  885. }));
  886. });
  887. });
  888. }));