chatroom.js 117 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095
  1. (function (root, factory) {
  2. define(["mock", "converse-core", "test_utils", "utils" ], factory);
  3. } (this, function (mock, converse, test_utils, utils) {
  4. var _ = converse.env._;
  5. var $ = converse.env.jQuery;
  6. var $pres = converse.env.$pres;
  7. var $iq = converse.env.$iq;
  8. var $msg = converse.env.$msg;
  9. var Strophe = converse.env.Strophe;
  10. return describe("ChatRooms", function () {
  11. describe("The \"rooms\" API", function () {
  12. it("has a method 'close' which closes rooms by JID or all rooms when called with no arguments", mock.initConverse(function (_converse) {
  13. test_utils.createContacts(_converse, 'current');
  14. runs(function () {
  15. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  16. test_utils.openAndEnterChatRoom(_converse, 'leisure', 'localhost', 'dummy');
  17. test_utils.openAndEnterChatRoom(_converse, 'news', 'localhost', 'dummy');
  18. expect(_converse.chatboxviews.get('lounge@localhost').$el.is(':visible')).toBeTruthy();
  19. expect(_converse.chatboxviews.get('leisure@localhost').$el.is(':visible')).toBeTruthy();
  20. expect(_converse.chatboxviews.get('news@localhost').$el.is(':visible')).toBeTruthy();
  21. });
  22. waits('100');
  23. runs(function () {
  24. // XXX: bit of a cheat here. We want `cleanup()` to be
  25. // called on the room. Either it's this or faking
  26. // `sendPresence`.
  27. _converse.connection.connected = false;
  28. _converse.api.rooms.close('lounge@localhost');
  29. expect(_converse.chatboxviews.get('lounge@localhost')).toBeUndefined();
  30. expect(_converse.chatboxviews.get('leisure@localhost').$el.is(':visible')).toBeTruthy();
  31. expect(_converse.chatboxviews.get('news@localhost').$el.is(':visible')).toBeTruthy();
  32. _converse.api.rooms.close(['leisure@localhost', 'news@localhost']);
  33. expect(_converse.chatboxviews.get('lounge@localhost')).toBeUndefined();
  34. expect(_converse.chatboxviews.get('leisure@localhost')).toBeUndefined();
  35. expect(_converse.chatboxviews.get('news@localhost')).toBeUndefined();
  36. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  37. test_utils.openAndEnterChatRoom(_converse, 'leisure', 'localhost', 'dummy');
  38. expect(_converse.chatboxviews.get('lounge@localhost').$el.is(':visible')).toBeTruthy();
  39. expect(_converse.chatboxviews.get('leisure@localhost').$el.is(':visible')).toBeTruthy();
  40. });
  41. waits('100');
  42. runs(function () {
  43. _converse.api.rooms.close();
  44. expect(_converse.chatboxviews.get('lounge@localhost')).toBeUndefined();
  45. expect(_converse.chatboxviews.get('leisure@localhost')).toBeUndefined();
  46. });
  47. }));
  48. it("has a method 'get' which returns a wrapped chat room (if it exists)", mock.initConverse(function (_converse) {
  49. test_utils.createContacts(_converse, 'current');
  50. waits('300'); // ChatBox.show() is debounced for 250ms
  51. runs(function () {
  52. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  53. var jid = 'lounge@localhost';
  54. var room = _converse.api.rooms.get(jid);
  55. expect(room instanceof Object).toBeTruthy();
  56. expect(room.is_chatroom).toBeTruthy();
  57. var chatroomview = _converse.chatboxviews.get(jid);
  58. expect(chatroomview.$el.is(':visible')).toBeTruthy();
  59. chatroomview.close();
  60. });
  61. waits('300'); // ChatBox.show() is debounced for 250ms
  62. runs(function () {
  63. // Test with mixed case
  64. test_utils.openAndEnterChatRoom(_converse, 'Leisure', 'localhost', 'dummy');
  65. var jid = 'Leisure@localhost';
  66. var room = _converse.api.rooms.get(jid);
  67. expect(room instanceof Object).toBeTruthy();
  68. var chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
  69. expect(chatroomview.$el.is(':visible')).toBeTruthy();
  70. });
  71. waits('300'); // ChatBox.show() is debounced for 250ms
  72. runs(function () {
  73. var jid = 'leisure@localhost';
  74. var room = _converse.api.rooms.get(jid);
  75. expect(room instanceof Object).toBeTruthy();
  76. var chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
  77. expect(chatroomview.$el.is(':visible')).toBeTruthy();
  78. jid = 'leiSure@localhost';
  79. room = _converse.api.rooms.get(jid);
  80. expect(room instanceof Object).toBeTruthy();
  81. chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
  82. expect(chatroomview.$el.is(':visible')).toBeTruthy();
  83. chatroomview.close();
  84. // Non-existing room
  85. jid = 'lounge2@localhost';
  86. room = _converse.api.rooms.get(jid);
  87. expect(typeof room === 'undefined').toBeTruthy();
  88. });
  89. }));
  90. it("has a method 'open' which opens (optionally configures) and returns a wrapped chat box", mock.initConverse(function (_converse) {
  91. // Mock 'getRoomFeatures', otherwise the room won't be
  92. // displayed as it waits first for the features to be returned
  93. // (when it's a new room being created).
  94. spyOn(_converse.ChatRoomView.prototype, 'getRoomFeatures').andCallFake(function () {
  95. var deferred = new $.Deferred();
  96. deferred.resolve();
  97. return deferred.promise();
  98. });
  99. test_utils.createContacts(_converse, 'current');
  100. var chatroomview;
  101. var jid = 'lounge@localhost';
  102. var room = _converse.api.rooms.open(jid);
  103. runs(function () {
  104. // Test on chat room that doesn't exist.
  105. expect(room instanceof Object).toBeTruthy();
  106. expect(room.is_chatroom).toBeTruthy();
  107. chatroomview = _converse.chatboxviews.get(jid);
  108. expect(chatroomview.$el.is(':visible')).toBeTruthy();
  109. });
  110. waits('300'); // ChatBox.show() is debounced for 250ms
  111. runs(function () {
  112. // Test again, now that the room exists.
  113. room = _converse.api.rooms.open(jid);
  114. expect(room instanceof Object).toBeTruthy();
  115. expect(room.is_chatroom).toBeTruthy();
  116. chatroomview = _converse.chatboxviews.get(jid);
  117. expect(chatroomview.$el.is(':visible')).toBeTruthy();
  118. });
  119. waits('300'); // ChatBox.show() is debounced for 250ms
  120. runs(function () {
  121. // Test with mixed case in JID
  122. jid = 'Leisure@localhost';
  123. room = _converse.api.rooms.open(jid);
  124. expect(room instanceof Object).toBeTruthy();
  125. chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
  126. expect(chatroomview.$el.is(':visible')).toBeTruthy();
  127. jid = 'leisure@localhost';
  128. room = _converse.api.rooms.open(jid);
  129. expect(room instanceof Object).toBeTruthy();
  130. chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
  131. expect(chatroomview.$el.is(':visible')).toBeTruthy();
  132. jid = 'leiSure@localhost';
  133. room = _converse.api.rooms.open(jid);
  134. expect(room instanceof Object).toBeTruthy();
  135. chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
  136. expect(chatroomview.$el.is(':visible')).toBeTruthy();
  137. chatroomview.close();
  138. });
  139. waits('300'); // ChatBox.show() is debounced for 250ms
  140. runs(function () {
  141. _converse.muc_instant_rooms = false;
  142. var sent_IQ, IQ_id;
  143. var sendIQ = _converse.connection.sendIQ;
  144. spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
  145. sent_IQ = iq;
  146. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  147. });
  148. // Test with configuration
  149. _converse.api.rooms.open('room@conference.example.org', {
  150. 'nick': 'some1',
  151. 'auto_configure': true,
  152. 'roomconfig': {
  153. 'changesubject': false,
  154. 'membersonly': true,
  155. 'persistentroom': true,
  156. 'publicroom': true,
  157. 'roomdesc': 'Welcome to this room',
  158. 'whois': 'anyone'
  159. }
  160. });
  161. // We pretend this is a new room, so no disco info is returned.
  162. var features_stanza = $iq({
  163. from: 'room@conference.example.org',
  164. 'id': IQ_id,
  165. 'to': 'dummy@localhost/desktop',
  166. 'type': 'error'
  167. }).c('error', {'type': 'cancel'})
  168. .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
  169. _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
  170. /* <presence xmlns="jabber:client" to="dummy@localhost/pda" from="room@conference.example.org/yo">
  171. * <x xmlns="http://jabber.org/protocol/muc#user">
  172. * <item affiliation="owner" jid="dummy@localhost/pda" role="moderator"/>
  173. * <status code="110"/>
  174. * <status code="201"/>
  175. * </x>
  176. * </presence>
  177. */
  178. var presence = $pres({
  179. from:'room@conference.example.org/some1',
  180. to:'dummy@localhost/pda'
  181. })
  182. .c('x', {xmlns:'http://jabber.org/protocol/muc#user'})
  183. .c('item', {
  184. affiliation: 'owner',
  185. jid: 'dummy@localhost/pda',
  186. role: 'moderator'
  187. }).up()
  188. .c('status', {code:'110'}).up()
  189. .c('status', {code:'201'});
  190. _converse.connection._dataRecv(test_utils.createRequest(presence));
  191. expect(_converse.connection.sendIQ).toHaveBeenCalled();
  192. expect(sent_IQ.toLocaleString()).toBe(
  193. "<iq to='room@conference.example.org' type='get' xmlns='jabber:client' id='"+IQ_id+
  194. "'><query xmlns='http://jabber.org/protocol/muc#owner'/></iq>"
  195. );
  196. _converse.connection._dataRecv(test_utils.createRequest($(
  197. '<iq xmlns="jabber:client"'+
  198. ' type="result"'+
  199. ' to="dummy@localhost/pda"'+
  200. ' from="room@conference.example.org" id="'+IQ_id+'">'+
  201. ' <query xmlns="http://jabber.org/protocol/muc#owner">'+
  202. ' <x xmlns="jabber:x:data" type="form">'+
  203. ' <title>Configuration for room@conference.example.org</title>'+
  204. ' <instructions>Complete and submit this form to configure the room.</instructions>'+
  205. ' <field var="FORM_TYPE" type="hidden">'+
  206. ' <value>http://jabber.org/protocol/muc#roomconfig</value>'+
  207. ' </field>'+
  208. ' <field type="text-single" var="muc#roomconfig_roomname" label="Name">'+
  209. ' <value>Room</value>'+
  210. ' </field>'+
  211. ' <field type="text-single" var="muc#roomconfig_roomdesc" label="Description"><value/></field>'+
  212. ' <field type="boolean" var="muc#roomconfig_persistentroom" label="Make Room Persistent?"/>'+
  213. ' <field type="boolean" var="muc#roomconfig_publicroom" label="Make Room Publicly Searchable?"><value>1</value></field>'+
  214. ' <field type="boolean" var="muc#roomconfig_changesubject" label="Allow Occupants to Change Subject?"/>'+
  215. ' <field type="list-single" var="muc#roomconfig_whois" label="Who May Discover Real JIDs?"><option label="Moderators Only">'+
  216. ' <value>moderators</value></option><option label="Anyone"><value>anyone</value></option>'+
  217. ' </field>'+
  218. ' <field type="text-private" var="muc#roomconfig_roomsecret" label="Password"><value/></field>'+
  219. ' <field type="boolean" var="muc#roomconfig_moderatedroom" label="Make Room Moderated?"/>'+
  220. ' <field type="boolean" var="muc#roomconfig_membersonly" label="Make Room Members-Only?"/>'+
  221. ' <field type="text-single" var="muc#roomconfig_historylength" label="Maximum Number of History Messages Returned by Room">'+
  222. ' <value>20</value></field>'+
  223. ' </x>'+
  224. ' </query>'+
  225. ' </iq>')[0]));
  226. var $sent_stanza = $(sent_IQ.toLocaleString());
  227. expect($sent_stanza.find('field[var="muc#roomconfig_roomname"] value').text()).toBe('Room');
  228. expect($sent_stanza.find('field[var="muc#roomconfig_roomdesc"] value').text()).toBe('Welcome to this room');
  229. expect($sent_stanza.find('field[var="muc#roomconfig_persistentroom"] value').text()).toBe('1');
  230. expect($sent_stanza.find('field[var="muc#roomconfig_publicroom"] value ').text()).toBe('1');
  231. expect($sent_stanza.find('field[var="muc#roomconfig_changesubject"] value').text()).toBe('0');
  232. expect($sent_stanza.find('field[var="muc#roomconfig_whois"] value ').text()).toBe('anyone');
  233. expect($sent_stanza.find('field[var="muc#roomconfig_membersonly"] value').text()).toBe('1');
  234. expect($sent_stanza.find('field[var="muc#roomconfig_historylength"] value').text()).toBe('20');
  235. });
  236. }));
  237. });
  238. describe("An instant chat room", function () {
  239. it("will be created when muc_instant_rooms is set to true", mock.initConverse(function (_converse) {
  240. var sent_IQ, IQ_id;
  241. var sendIQ = _converse.connection.sendIQ;
  242. spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
  243. sent_IQ = iq;
  244. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  245. });
  246. /* <iq from="jordie.langen@chat.example.org/converse.js-11659299" to="myroom@conference.chat.example.org" type="get">
  247. * <query xmlns="http://jabber.org/protocol/disco#info"/>
  248. * </iq>
  249. * <iq xmlns="jabber:client" type="error" to="jordie.langen@chat.example.org/converse.js-11659299" from="myroom@conference.chat.example.org">
  250. * <error type="cancel">
  251. * <item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
  252. * </error>
  253. * </iq>
  254. */
  255. test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  256. // We pretend this is a new room, so no disco info is returned.
  257. var features_stanza = $iq({
  258. from: 'lounge@localhost',
  259. 'id': IQ_id,
  260. 'to': 'dummy@localhost/desktop',
  261. 'type': 'error'
  262. }).c('error', {'type': 'cancel'})
  263. .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
  264. _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
  265. var view = _converse.chatboxviews.get('lounge@localhost');
  266. spyOn(view, 'join').andCallThrough();
  267. /* <iq to="myroom@conference.chat.example.org"
  268. * from="jordie.langen@chat.example.org/converse.js-11659299"
  269. * type="get">
  270. * <query xmlns="http://jabber.org/protocol/disco#info"
  271. * node="x-roomuser-item"/>
  272. * </iq>
  273. */
  274. expect(sent_IQ.toLocaleString()).toBe(
  275. "<iq to='lounge@localhost' from='dummy@localhost/resource' "+
  276. "type='get' xmlns='jabber:client' id='"+IQ_id+"'>"+
  277. "<query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item'/></iq>"
  278. );
  279. /* * <iq xmlns="jabber:client" type="error" to="jordie.langen@chat.example.org/converse.js-11659299" from="myroom@conference.chat.example.org">
  280. * <error type="cancel">
  281. * <item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
  282. * </error>
  283. * </iq>
  284. */
  285. var stanza = $iq({
  286. 'type': 'error',
  287. 'id': IQ_id,
  288. 'from': view.model.get('jid'),
  289. 'to': _converse.connection.jid
  290. }).c('error', {'type': 'cancel'})
  291. .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
  292. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  293. // TODO: enter nickname
  294. var $input = view.$el.find('input.new-chatroom-nick');
  295. $input.val('nicky').parents('form').submit();
  296. expect(view.join).toHaveBeenCalled();
  297. // The user has just entered the room (because join was called)
  298. // and receives their own presence from the server.
  299. // See example 24:
  300. // http://xmpp.org/extensions/xep-0045.html#enter-pres
  301. //
  302. /* <presence xmlns="jabber:client" to="jordie.langen@chat.example.org/converse.js-11659299" from="myroom@conference.chat.example.org/jc">
  303. * <x xmlns="http://jabber.org/protocol/muc#user">
  304. * <item jid="jordie.langen@chat.example.org/converse.js-11659299" affiliation="owner" role="moderator"/>
  305. * <status code="110"/>
  306. * <status code="201"/>
  307. * </x>
  308. * </presence>
  309. */
  310. var presence = $pres({
  311. to:'dummy@localhost/resource',
  312. from:'lounge@localhost/thirdwitch',
  313. id:'5025e055-036c-4bc5-a227-706e7e352053'
  314. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  315. .c('item').attrs({
  316. affiliation: 'owner',
  317. jid: 'dummy@localhost/resource',
  318. role: 'moderator'
  319. }).up()
  320. .c('status').attrs({code:'110'}).up()
  321. .c('status').attrs({code:'201'}).nodeTree;
  322. _converse.connection._dataRecv(test_utils.createRequest(presence));
  323. var info_text = view.$el.find('.chat-content .chat-info').text();
  324. expect(info_text).toBe('A new room has been created');
  325. // An instant room is created by saving the default configuratoin.
  326. //
  327. /* <iq to="myroom@conference.chat.example.org" type="set" xmlns="jabber:client" id="5025e055-036c-4bc5-a227-706e7e352053:sendIQ">
  328. * <query xmlns="http://jabber.org/protocol/muc#owner"><x xmlns="jabber:x:data" type="submit"/></query>
  329. * </iq>
  330. */
  331. expect(sent_IQ.toLocaleString()).toBe(
  332. "<iq to='lounge@localhost' type='set' xmlns='jabber:client' id='"+IQ_id+"'>"+
  333. "<query xmlns='http://jabber.org/protocol/muc#owner'><x xmlns='jabber:x:data' type='submit'/>"+
  334. "</query></iq>");
  335. }));
  336. });
  337. describe("A Chat Room", function () {
  338. it("shows join/leave messages when users enter or exit a room", mock.initConverse(function (_converse) {
  339. test_utils.openChatRoom(_converse, "coven", 'chat.shakespeare.lit', 'some1');
  340. var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
  341. var $chat_content = view.$el.find('.chat-content');
  342. /* We don't show join/leave messages for existing occupants. We
  343. * know about them because we receive their presences before we
  344. * receive our own.
  345. */
  346. presence = $pres({
  347. to: 'dummy@localhost/_converse.js-29092160',
  348. from: 'coven@chat.shakespeare.lit/oldguy'
  349. }).c('x', {xmlns: Strophe.NS.MUC_USER})
  350. .c('item', {
  351. 'affiliation': 'none',
  352. 'jid': 'oldguy@localhost/_converse.js-290929789',
  353. 'role': 'participant'
  354. });
  355. _converse.connection._dataRecv(test_utils.createRequest(presence));
  356. expect($chat_content.find('div.chat-info').length).toBe(0);
  357. /* <presence to="dummy@localhost/_converse.js-29092160"
  358. * from="coven@chat.shakespeare.lit/some1">
  359. * <x xmlns="http://jabber.org/protocol/muc#user">
  360. * <item affiliation="owner" jid="dummy@localhost/_converse.js-29092160" role="moderator"/>
  361. * <status code="110"/>
  362. * </x>
  363. * </presence></body>
  364. */
  365. var presence = $pres({
  366. to: 'dummy@localhost/_converse.js-29092160',
  367. from: 'coven@chat.shakespeare.lit/some1'
  368. }).c('x', {xmlns: Strophe.NS.MUC_USER})
  369. .c('item', {
  370. 'affiliation': 'owner',
  371. 'jid': 'dummy@localhost/_converse.js-29092160',
  372. 'role': 'moderator'
  373. }).up()
  374. .c('status', {code: '110'});
  375. _converse.connection._dataRecv(test_utils.createRequest(presence));
  376. expect($chat_content.find('div.chat-info:first').html()).toBe("some1 has joined the room.");
  377. presence = $pres({
  378. to: 'dummy@localhost/_converse.js-29092160',
  379. from: 'coven@chat.shakespeare.lit/newguy'
  380. }).c('x', {xmlns: Strophe.NS.MUC_USER})
  381. .c('item', {
  382. 'affiliation': 'none',
  383. 'jid': 'newguy@localhost/_converse.js-290929789',
  384. 'role': 'participant'
  385. });
  386. _converse.connection._dataRecv(test_utils.createRequest(presence));
  387. expect($chat_content.find('div.chat-info').length).toBe(2);
  388. expect($chat_content.find('div.chat-info:last').html()).toBe("newguy has joined the room.");
  389. // Don't show duplicate join messages
  390. presence = $pres({
  391. to: 'dummy@localhost/_converse.js-290918392',
  392. from: 'coven@chat.shakespeare.lit/newguy'
  393. }).c('x', {xmlns: Strophe.NS.MUC_USER})
  394. .c('item', {
  395. 'affiliation': 'none',
  396. 'jid': 'newguy@localhost/_converse.js-290929789',
  397. 'role': 'participant'
  398. });
  399. _converse.connection._dataRecv(test_utils.createRequest(presence));
  400. expect($chat_content.find('div.chat-info').length).toBe(2);
  401. presence = $pres({
  402. to: 'dummy@localhost/_converse.js-29092160',
  403. type: 'unavailable',
  404. from: 'coven@chat.shakespeare.lit/newguy'
  405. }).c('x', {xmlns: Strophe.NS.MUC_USER})
  406. .c('item', {
  407. 'affiliation': 'none',
  408. 'jid': 'newguy@localhost/_converse.js-290929789',
  409. 'role': 'participant'
  410. });
  411. _converse.connection._dataRecv(test_utils.createRequest(presence));
  412. expect($chat_content.find('div.chat-info').length).toBe(3);
  413. expect($chat_content.find('div.chat-info:last').html()).toBe("newguy has left the room");
  414. }));
  415. it("shows its description in the chat heading", mock.initConverse(function (_converse) {
  416. var sent_IQ, IQ_id;
  417. var sendIQ = _converse.connection.sendIQ;
  418. spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
  419. sent_IQ = iq;
  420. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  421. });
  422. var view = _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'});
  423. spyOn(view, 'generateHeadingHTML').andCallThrough();
  424. var features_stanza = $iq({
  425. from: 'coven@chat.shakespeare.lit',
  426. 'id': IQ_id,
  427. 'to': 'dummy@localhost/desktop',
  428. 'type': 'result'
  429. })
  430. .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'})
  431. .c('identity', {
  432. 'category': 'conference',
  433. 'name': 'A Dark Cave',
  434. 'type': 'text'
  435. }).up()
  436. .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up()
  437. .c('feature', {'var': 'muc_passwordprotected'}).up()
  438. .c('feature', {'var': 'muc_hidden'}).up()
  439. .c('feature', {'var': 'muc_temporary'}).up()
  440. .c('feature', {'var': 'muc_open'}).up()
  441. .c('feature', {'var': 'muc_unmoderated'}).up()
  442. .c('feature', {'var': 'muc_nonanonymous'}).up()
  443. .c('feature', {'var': 'urn:xmpp:mam:0'}).up()
  444. .c('x', { 'xmlns':'jabber:x:data', 'type':'result'})
  445. .c('field', {'var':'FORM_TYPE', 'type':'hidden'})
  446. .c('value').t('http://jabber.org/protocol/muc#roominfo').up().up()
  447. .c('field', {'type':'text-single', 'var':'muc#roominfo_description', 'label':'Description'})
  448. .c('value').t('This is the description').up().up()
  449. .c('field', {'type':'text-single', 'var':'muc#roominfo_occupants', 'label':'Number of occupants'})
  450. .c('value').t(0);
  451. _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
  452. expect(view.generateHeadingHTML).toHaveBeenCalled();
  453. expect(view.$('.chatroom-description').text()).toBe('This is the description');
  454. }));
  455. it("will specially mark messages in which you are mentioned", mock.initConverse(function (_converse) {
  456. test_utils.createContacts(_converse, 'current');
  457. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  458. var view = _converse.chatboxviews.get('lounge@localhost');
  459. if (!view.$el.find('.chat-area').length) { view.renderChatArea(); }
  460. var message = 'dummy: Your attention is required';
  461. var nick = mock.chatroom_names[0],
  462. msg = $msg({
  463. from: 'lounge@localhost/'+nick,
  464. id: (new Date()).getTime(),
  465. to: 'dummy@localhost',
  466. type: 'groupchat'
  467. }).c('body').t(message).tree();
  468. _converse.chatboxes.onMessage(msg);
  469. expect(view.$el.find('.chat-message').hasClass('mentioned')).toBeTruthy();
  470. }));
  471. it("supports the /me command", mock.initConverse(function (_converse) {
  472. test_utils.createContacts(_converse, 'current');
  473. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  474. var view = _converse.chatboxviews.get('lounge@localhost');
  475. if (!view.$el.find('.chat-area').length) { view.renderChatArea(); }
  476. var message = '/me is tired';
  477. var nick = mock.chatroom_names[0],
  478. msg = $msg({
  479. from: 'lounge@localhost/'+nick,
  480. id: (new Date()).getTime(),
  481. to: 'dummy@localhost',
  482. type: 'groupchat'
  483. }).c('body').t(message).tree();
  484. _converse.chatboxes.onMessage(msg);
  485. expect(_.includes(view.$el.find('.chat-msg-author').text(), '**Dyon van de Wege')).toBeTruthy();
  486. expect(view.$el.find('.chat-msg-content').text()).toBe(' is tired');
  487. message = '/me is as well';
  488. msg = $msg({
  489. from: 'lounge@localhost/dummy',
  490. id: (new Date()).getTime(),
  491. to: 'dummy@localhost',
  492. type: 'groupchat'
  493. }).c('body').t(message).tree();
  494. _converse.chatboxes.onMessage(msg);
  495. expect(_.includes(view.$el.find('.chat-msg-author:last').text(), '**Max Mustermann')).toBeTruthy();
  496. expect(view.$el.find('.chat-msg-content:last').text()).toBe(' is as well');
  497. }));
  498. it("can have spaces and special characters in its name", mock.initConverse(function (_converse) {
  499. test_utils.openChatRoom(_converse, 'lounge & leisure', 'localhost', 'dummy');
  500. var view = _converse.chatboxviews.get(
  501. Strophe.escapeNode('lounge & leisure')+'@localhost');
  502. expect(view instanceof _converse.ChatRoomView).toBe(true);
  503. }));
  504. it("can be configured if you're its owner", mock.initConverse(function (_converse) {
  505. var view;
  506. var sent_IQ, IQ_id;
  507. var sendIQ = _converse.connection.sendIQ;
  508. spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
  509. sent_IQ = iq;
  510. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  511. });
  512. runs(function () {
  513. _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'});
  514. view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
  515. spyOn(view, 'saveAffiliationAndRole').andCallThrough();
  516. // We pretend this is a new room, so no disco info is returned.
  517. var features_stanza = $iq({
  518. from: 'coven@chat.shakespeare.lit',
  519. 'id': IQ_id,
  520. 'to': 'dummy@localhost/desktop',
  521. 'type': 'error'
  522. }).c('error', {'type': 'cancel'})
  523. .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
  524. _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
  525. /* <presence to="dummy@localhost/_converse.js-29092160"
  526. * from="coven@chat.shakespeare.lit/some1">
  527. * <x xmlns="http://jabber.org/protocol/muc#user">
  528. * <item affiliation="owner" jid="dummy@localhost/_converse.js-29092160" role="moderator"/>
  529. * <status code="110"/>
  530. * </x>
  531. * </presence></body>
  532. */
  533. var presence = $pres({
  534. to: 'dummy@localhost/_converse.js-29092160',
  535. from: 'coven@chat.shakespeare.lit/some1'
  536. }).c('x', {xmlns: Strophe.NS.MUC_USER})
  537. .c('item', {
  538. 'affiliation': 'owner',
  539. 'jid': 'dummy@localhost/_converse.js-29092160',
  540. 'role': 'moderator'
  541. }).up()
  542. .c('status', {code: '110'});
  543. _converse.connection._dataRecv(test_utils.createRequest(presence));
  544. expect(view.saveAffiliationAndRole).toHaveBeenCalled();
  545. expect(view.$('.configure-chatroom-button').is(':visible')).toBeTruthy();
  546. expect(view.$('.toggle-chatbox-button').is(':visible')).toBeTruthy();
  547. expect(view.$('.toggle-bookmark').is(':visible')).toBeTruthy();
  548. view.$('.configure-chatroom-button').click();
  549. });
  550. waits(50);
  551. runs (function () {
  552. /* Check that an IQ is sent out, asking for the
  553. * configuration form.
  554. * See: // http://xmpp.org/extensions/xep-0045.html#example-163
  555. *
  556. * <iq from='crone1@shakespeare.lit/desktop'
  557. * id='config1'
  558. * to='coven@chat.shakespeare.lit'
  559. * type='get'>
  560. * <query xmlns='http://jabber.org/protocol/muc#owner'/>
  561. * </iq>
  562. */
  563. expect(sent_IQ.toLocaleString()).toBe(
  564. "<iq to='coven@chat.shakespeare.lit' type='get' xmlns='jabber:client' id='"+IQ_id+"'>"+
  565. "<query xmlns='http://jabber.org/protocol/muc#owner'/>"+
  566. "</iq>");
  567. /* Server responds with the configuration form.
  568. * See: // http://xmpp.org/extensions/xep-0045.html#example-165
  569. */
  570. var config_stanza = $iq({from: 'coven@chat.shakespeare.lit',
  571. 'id': IQ_id,
  572. 'to': 'dummy@localhost/desktop',
  573. 'type': 'result'})
  574. .c('query', { 'xmlns': 'http://jabber.org/protocol/muc#owner'})
  575. .c('x', { 'xmlns': 'jabber:x:data', 'type': 'form'})
  576. .c('title').t('Configuration for "coven" Room').up()
  577. .c('instructions').t('Complete this form to modify the configuration of your room.').up()
  578. .c('field', {'type': 'hidden', 'var': 'FORM_TYPE'})
  579. .c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up()
  580. .c('field', {
  581. 'label': 'Natural-Language Room Name',
  582. 'type': 'text-single',
  583. 'var': 'muc#roomconfig_roomname'})
  584. .c('value').t('A Dark Cave').up().up()
  585. .c('field', {
  586. 'label': 'Short Description of Room',
  587. 'type': 'text-single',
  588. 'var': 'muc#roomconfig_roomdesc'})
  589. .c('value').t('The place for all good witches!').up().up()
  590. .c('field', {
  591. 'label': 'Enable Public Logging?',
  592. 'type': 'boolean',
  593. 'var': 'muc#roomconfig_enablelogging'})
  594. .c('value').t(0).up().up()
  595. .c('field', {
  596. 'label': 'Allow Occupants to Change Subject?',
  597. 'type': 'boolean',
  598. 'var': 'muc#roomconfig_changesubject'})
  599. .c('value').t(0).up().up()
  600. .c('field', {
  601. 'label': 'Allow Occupants to Invite Others?',
  602. 'type': 'boolean',
  603. 'var': 'muc#roomconfig_allowinvites'})
  604. .c('value').t(0).up().up()
  605. .c('field', {
  606. 'label': 'Who Can Send Private Messages?',
  607. 'type': 'list-single',
  608. 'var': 'muc#roomconfig_allowpm'})
  609. .c('value').t('anyone').up()
  610. .c('option', {'label': 'Anyone'})
  611. .c('value').t('anyone').up().up()
  612. .c('option', {'label': 'Anyone with Voice'})
  613. .c('value').t('participants').up().up()
  614. .c('option', {'label': 'Moderators Only'})
  615. .c('value').t('moderators').up().up()
  616. .c('option', {'label': 'Nobody'})
  617. .c('value').t('none').up().up().up()
  618. .c('field', {
  619. 'label': 'Roles for which Presence is Broadcasted',
  620. 'type': 'list-multi',
  621. 'var': 'muc#roomconfig_presencebroadcast'})
  622. .c('value').t('moderator').up()
  623. .c('value').t('participant').up()
  624. .c('value').t('visitor').up()
  625. .c('option', {'label': 'Moderator'})
  626. .c('value').t('moderator').up().up()
  627. .c('option', {'label': 'Participant'})
  628. .c('value').t('participant').up().up()
  629. .c('option', {'label': 'Visitor'})
  630. .c('value').t('visitor').up().up().up()
  631. .c('field', {
  632. 'label': 'Roles and Affiliations that May Retrieve Member List',
  633. 'type': 'list-multi',
  634. 'var': 'muc#roomconfig_getmemberlist'})
  635. .c('value').t('moderator').up()
  636. .c('value').t('participant').up()
  637. .c('value').t('visitor').up()
  638. .c('option', {'label': 'Moderator'})
  639. .c('value').t('moderator').up().up()
  640. .c('option', {'label': 'Participant'})
  641. .c('value').t('participant').up().up()
  642. .c('option', {'label': 'Visitor'})
  643. .c('value').t('visitor').up().up().up()
  644. .c('field', {
  645. 'label': 'Make Room Publicly Searchable?',
  646. 'type': 'boolean',
  647. 'var': 'muc#roomconfig_publicroom'})
  648. .c('value').t(0).up().up()
  649. .c('field', {
  650. 'label': 'Make Room Publicly Searchable?',
  651. 'type': 'boolean',
  652. 'var': 'muc#roomconfig_publicroom'})
  653. .c('value').t(0).up().up()
  654. .c('field', {
  655. 'label': 'Make Room Persistent?',
  656. 'type': 'boolean',
  657. 'var': 'muc#roomconfig_persistentroom'})
  658. .c('value').t(0).up().up()
  659. .c('field', {
  660. 'label': 'Make Room Moderated?',
  661. 'type': 'boolean',
  662. 'var': 'muc#roomconfig_moderatedroom'})
  663. .c('value').t(0).up().up()
  664. .c('field', {
  665. 'label': 'Make Room Members Only?',
  666. 'type': 'boolean',
  667. 'var': 'muc#roomconfig_membersonly'})
  668. .c('value').t(0).up().up()
  669. .c('field', {
  670. 'label': 'Password Required for Entry?',
  671. 'type': 'boolean',
  672. 'var': 'muc#roomconfig_passwordprotectedroom'})
  673. .c('value').t(1).up().up()
  674. .c('field', {'type': 'fixed'})
  675. .c('value').t('If a password is required to enter this room,'+
  676. 'you must specify the password below.').up().up()
  677. .c('field', {
  678. 'label': 'Password',
  679. 'type': 'text-private',
  680. 'var': 'muc#roomconfig_roomsecret'})
  681. .c('value').t('cauldronburn');
  682. _converse.connection._dataRecv(test_utils.createRequest(config_stanza));
  683. });
  684. waits(50);
  685. runs (function () {
  686. expect(view.$('form.chatroom-form').length).toBe(1);
  687. expect(view.$('form.chatroom-form fieldset').length).toBe(2);
  688. var $membersonly = view.$('input[name="muc#roomconfig_membersonly"]');
  689. expect($membersonly.length).toBe(1);
  690. expect($membersonly.attr('type')).toBe('checkbox');
  691. $membersonly.prop('checked', true);
  692. var $moderated = view.$('input[name="muc#roomconfig_moderatedroom"]');
  693. expect($moderated.length).toBe(1);
  694. expect($moderated.attr('type')).toBe('checkbox');
  695. $moderated.prop('checked', true);
  696. var $password = view.$('input[name="muc#roomconfig_roomsecret"]');
  697. expect($password.length).toBe(1);
  698. expect($password.attr('type')).toBe('password');
  699. var $allowpm = view.$('select[name="muc#roomconfig_allowpm"]');
  700. expect($allowpm.length).toBe(1);
  701. $allowpm.val('moderators');
  702. var $presencebroadcast = view.$('select[name="muc#roomconfig_presencebroadcast"]');
  703. expect($presencebroadcast.length).toBe(1);
  704. $presencebroadcast.val(['moderator']);
  705. view.$('input[type="submit"]').click();
  706. });
  707. waits(50);
  708. runs (function () {
  709. var $sent_stanza = $(sent_IQ.toLocaleString());
  710. expect($sent_stanza.find('field[var="muc#roomconfig_membersonly"] value').text()).toBe('1');
  711. expect($sent_stanza.find('field[var="muc#roomconfig_moderatedroom"] value').text()).toBe('1');
  712. expect($sent_stanza.find('field[var="muc#roomconfig_allowpm"] value').text()).toBe('moderators');
  713. expect($sent_stanza.find('field[var="muc#roomconfig_presencebroadcast"] value').text()).toBe('moderator');
  714. });
  715. }));
  716. it("shows users currently present in the room", mock.initConverse(function (_converse) {
  717. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  718. var name;
  719. var view = _converse.chatboxviews.get('lounge@localhost'),
  720. $occupants = view.$('.occupant-list');
  721. var presence, role;
  722. for (var i=0; i<mock.chatroom_names.length; i++) {
  723. name = mock.chatroom_names[i];
  724. role = mock.chatroom_roles[name].role;
  725. // See example 21 http://xmpp.org/extensions/xep-0045.html#enter-pres
  726. presence = $pres({
  727. to:'dummy@localhost/pda',
  728. from:'lounge@localhost/'+name
  729. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  730. .c('item').attrs({
  731. affiliation: mock.chatroom_roles[name].affiliation,
  732. jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
  733. role: role
  734. }).up()
  735. .c('status').attrs({code:'110'}).nodeTree;
  736. _converse.connection._dataRecv(test_utils.createRequest(presence));
  737. expect($occupants.find('li').length).toBe(2+i);
  738. expect($($occupants.find('li')[i+1]).text()).toBe(mock.chatroom_names[i]);
  739. expect($($occupants.find('li')[i+1]).hasClass('moderator')).toBe(role === "moderator");
  740. }
  741. // Test users leaving the room
  742. // http://xmpp.org/extensions/xep-0045.html#exit
  743. for (i=mock.chatroom_names.length-1; i>-1; i--) {
  744. name = mock.chatroom_names[i];
  745. role = mock.chatroom_roles[name].role;
  746. // See example 21 http://xmpp.org/extensions/xep-0045.html#enter-pres
  747. presence = $pres({
  748. to:'dummy@localhost/pda',
  749. from:'lounge@localhost/'+name,
  750. type: 'unavailable'
  751. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  752. .c('item').attrs({
  753. affiliation: mock.chatroom_roles[name].affiliation,
  754. jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
  755. role: 'none'
  756. }).nodeTree;
  757. _converse.connection._dataRecv(test_utils.createRequest(presence));
  758. expect($occupants.find('li').length).toBe(i+1);
  759. }
  760. }));
  761. it("escapes occupant nicknames when rendering them, to avoid JS-injection attacks", mock.initConverse(function (_converse) {
  762. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  763. /* <presence xmlns="jabber:client" to="jc@chat.example.org/converse.js-17184538"
  764. * from="oo@conference.chat.example.org/&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;">
  765. * <x xmlns="http://jabber.org/protocol/muc#user">
  766. * <item jid="jc@chat.example.org/converse.js-17184538" affiliation="owner" role="moderator"/>
  767. * <status code="110"/>
  768. * </x>
  769. * </presence>"
  770. */
  771. var presence = $pres({
  772. to:'dummy@localhost/pda',
  773. from:"lounge@localhost/&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;"
  774. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  775. .c('item').attrs({
  776. jid: 'someone@localhost',
  777. role: 'moderator',
  778. }).up()
  779. .c('status').attrs({code:'110'}).nodeTree;
  780. _converse.connection._dataRecv(test_utils.createRequest(presence));
  781. var view = _converse.chatboxviews.get('lounge@localhost');
  782. var occupant = view.$el.find('.occupant-list').find('li');
  783. expect(occupant.length).toBe(2);
  784. expect($(occupant).last().text()).toBe("&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;");
  785. }));
  786. it("indicates moderators by means of a special css class and tooltip", mock.initConverse(function (_converse) {
  787. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  788. var view = _converse.chatboxviews.get('lounge@localhost');
  789. var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  790. var presence = $pres({
  791. to:'dummy@localhost/pda',
  792. from:'lounge@localhost/moderatorman'
  793. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  794. .c('item').attrs({
  795. affiliation: 'admin',
  796. jid: contact_jid,
  797. role: 'moderator',
  798. }).up()
  799. .c('status').attrs({code:'110'}).nodeTree;
  800. _converse.connection._dataRecv(test_utils.createRequest(presence));
  801. var occupant = view.$el.find('.occupant-list').find('li');
  802. expect(occupant.length).toBe(2);
  803. expect($(occupant).first().text()).toBe("dummy");
  804. expect($(occupant).last().text()).toBe("moderatorman");
  805. expect($(occupant).last().attr('class').indexOf('moderator')).not.toBe(-1);
  806. expect($(occupant).last().attr('title')).toBe(contact_jid + ' This user is a moderator. Click to mention moderatorman in your message.');
  807. }));
  808. it("will use the user's reserved nickname, if it exists", mock.initConverse(function (_converse) {
  809. var sent_IQ, IQ_id;
  810. var sendIQ = _converse.connection.sendIQ;
  811. spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
  812. sent_IQ = iq;
  813. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  814. });
  815. test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  816. // We pretend this is a new room, so no disco info is returned.
  817. var features_stanza = $iq({
  818. from: 'lounge@localhost',
  819. 'id': IQ_id,
  820. 'to': 'dummy@localhost/desktop',
  821. 'type': 'error'
  822. }).c('error', {'type': 'cancel'})
  823. .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
  824. _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
  825. var view = _converse.chatboxviews.get('lounge@localhost');
  826. spyOn(view, 'join').andCallThrough();
  827. /* <iq from='hag66@shakespeare.lit/pda'
  828. * id='getnick1'
  829. * to='coven@chat.shakespeare.lit'
  830. * type='get'>
  831. * <query xmlns='http://jabber.org/protocol/disco#info'
  832. * node='x-roomuser-item'/>
  833. * </iq>
  834. */
  835. expect(sent_IQ.toLocaleString()).toBe(
  836. "<iq to='lounge@localhost' from='dummy@localhost/resource' "+
  837. "type='get' xmlns='jabber:client' id='"+IQ_id+"'>"+
  838. "<query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item'/></iq>"
  839. );
  840. /* <iq from='coven@chat.shakespeare.lit'
  841. * id='getnick1'
  842. * to='hag66@shakespeare.lit/pda'
  843. * type='result'>
  844. * <query xmlns='http://jabber.org/protocol/disco#info'
  845. * node='x-roomuser-item'>
  846. * <identity
  847. * category='conference'
  848. * name='thirdwitch'
  849. * type='text'/>
  850. * </query>
  851. * </iq>
  852. */
  853. var stanza = $iq({
  854. 'type': 'result',
  855. 'id': IQ_id,
  856. 'from': view.model.get('jid'),
  857. 'to': _converse.connection.jid
  858. }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info', 'node': 'x-roomuser-item'})
  859. .c('identity', {'category': 'conference', 'name': 'thirdwitch', 'type': 'text'});
  860. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  861. expect(view.join).toHaveBeenCalled();
  862. // The user has just entered the room (because join was called)
  863. // and receives their own presence from the server.
  864. // See example 24:
  865. // http://xmpp.org/extensions/xep-0045.html#enter-pres
  866. var presence = $pres({
  867. to:'dummy@localhost/resource',
  868. from:'lounge@localhost/thirdwitch',
  869. id:'DC352437-C019-40EC-B590-AF29E879AF97'
  870. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  871. .c('item').attrs({
  872. affiliation: 'member',
  873. jid: 'dummy@localhost/resource',
  874. role: 'occupant'
  875. }).up()
  876. .c('status').attrs({code:'110'}).up()
  877. .c('status').attrs({code:'210'}).nodeTree;
  878. _converse.connection._dataRecv(test_utils.createRequest(presence));
  879. var info_text = view.$el.find('.chat-content .chat-info').text();
  880. expect(info_text).toBe('Your nickname has been automatically set to: thirdwitch');
  881. }));
  882. it("allows the user to invite their roster contacts to enter the chat room", mock.initConverse(function (_converse) {
  883. test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  884. test_utils.createContacts(_converse, 'current'); // We need roster contacts, so that we have someone to invite
  885. // Since we don't actually fetch roster contacts, we need to
  886. // cheat here and emit the event.
  887. _converse.emit('rosterContactsFetched');
  888. spyOn(_converse, 'emit');
  889. spyOn(window, 'prompt').andCallFake(function () {
  890. return "Please join!";
  891. });
  892. var view = _converse.chatboxviews.get('lounge@localhost');
  893. // XXX: cheating a lttle bit, normally this'll be set after
  894. // receiving the features for the room.
  895. view.model.set('open', 'true');
  896. spyOn(view, 'directInvite').andCallThrough();
  897. var $input;
  898. view.$el.find('.chat-area').remove();
  899. $input = view.$el.find('input.invited-contact');
  900. runs (function () {
  901. expect($input.length).toBe(1);
  902. expect($input.attr('placeholder')).toBe('Invite');
  903. $input.val("Felix");
  904. var evt;
  905. // check if Event() is a constructor function
  906. // usage as per the spec, if true
  907. if (typeof(Event) === 'function') {
  908. evt = new Event('input');
  909. } else { // the deprecated way for PhantomJS
  910. evt = document.createEvent('CustomEvent');
  911. evt.initCustomEvent('input', false, false, null);
  912. }
  913. $input[0].dispatchEvent(evt);
  914. });
  915. waits(350); // Needed, due to debounce
  916. runs (function () {
  917. var sent_stanza;
  918. spyOn(_converse.connection, 'send').andCallFake(function (stanza) {
  919. sent_stanza = stanza;
  920. });
  921. var $hint = $input.siblings('ul').children('li');
  922. expect($input.val()).toBe('Felix');
  923. expect($hint[0].textContent).toBe('Felix Amsel');
  924. expect($hint.length).toBe(1);
  925. var evt;
  926. if (typeof(Event) === 'function') {
  927. // Not working on PhantomJS
  928. evt = new Event('mousedown', {'bubbles': true});
  929. evt.button = 0; // For some reason awesomplete wants this
  930. $hint[0].dispatchEvent(evt);
  931. expect(window.prompt).toHaveBeenCalled();
  932. expect(view.directInvite).toHaveBeenCalled();
  933. expect(sent_stanza.toLocaleString()).toBe(
  934. "<message from='dummy@localhost/resource' to='felix.amsel@localhost' id='" +
  935. sent_stanza.nodeTree.getAttribute('id') +
  936. "' xmlns='jabber:client'>"+
  937. "<x xmlns='jabber:x:conference' jid='lounge@localhost' reason='Please join!'/>"+
  938. "</message>"
  939. );
  940. }
  941. });
  942. }));
  943. it("can be joined automatically, based upon a received invite", mock.initConverse(function (_converse) {
  944. test_utils.createContacts(_converse, 'current'); // We need roster contacts, who can invite us
  945. spyOn(window, 'confirm').andCallFake(function () {
  946. return true;
  947. });
  948. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  949. var view = _converse.chatboxviews.get('lounge@localhost');
  950. view.close();
  951. view.model.destroy(); // Manually calling this, otherwise we have to mock stanzas.
  952. var name = mock.cur_names[0];
  953. var from_jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
  954. var room_jid = 'lounge@localhost';
  955. var reason = "Please join this chat room";
  956. var message = $(
  957. "<message from='"+from_jid+"' to='"+_converse.bare_jid+"'>" +
  958. "<x xmlns='jabber:x:conference'" +
  959. "jid='"+room_jid+"'" +
  960. "reason='"+reason+"'/>"+
  961. "</message>"
  962. )[0];
  963. expect(_converse.chatboxes.models.length).toBe(1);
  964. expect(_converse.chatboxes.models[0].id).toBe("controlbox");
  965. _converse.onDirectMUCInvitation(message);
  966. expect(window.confirm).toHaveBeenCalledWith(
  967. name + ' has invited you to join a chat room: '+ room_jid +
  968. ', and left the following reason: "'+reason+'"');
  969. expect(_converse.chatboxes.models.length).toBe(2);
  970. expect(_converse.chatboxes.models[0].id).toBe('controlbox');
  971. expect(_converse.chatboxes.models[1].id).toBe(room_jid);
  972. }));
  973. it("shows received groupchat messages", mock.initConverse(function (_converse) {
  974. test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  975. spyOn(_converse, 'emit');
  976. var view = _converse.chatboxviews.get('lounge@localhost');
  977. if (!view.$el.find('.chat-area').length) { view.renderChatArea(); }
  978. var nick = mock.chatroom_names[0];
  979. var text = 'This is a received message';
  980. var message = $msg({
  981. from: 'lounge@localhost/'+nick,
  982. id: '1',
  983. to: 'dummy@localhost',
  984. type: 'groupchat'
  985. }).c('body').t(text);
  986. view.onChatRoomMessage(message.nodeTree);
  987. var $chat_content = view.$el.find('.chat-content');
  988. expect($chat_content.find('.chat-message').length).toBe(1);
  989. expect($chat_content.find('.chat-msg-content').text()).toBe(text);
  990. expect(_converse.emit).toHaveBeenCalledWith('message', message.nodeTree);
  991. }));
  992. it("shows sent groupchat messages", mock.initConverse(function (_converse) {
  993. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  994. spyOn(_converse, 'emit');
  995. var view = _converse.chatboxviews.get('lounge@localhost');
  996. if (!view.$el.find('.chat-area').length) { view.renderChatArea(); }
  997. var text = 'This is a sent message';
  998. view.$el.find('.chat-textarea').text(text);
  999. view.$el.find('textarea.chat-textarea').trigger($.Event('keypress', {keyCode: 13}));
  1000. expect(_converse.emit).toHaveBeenCalledWith('messageSend', text);
  1001. var $chat_content = view.$el.find('.chat-content');
  1002. expect($chat_content.find('.chat-message').length).toBe(1);
  1003. // Let's check that if we receive the same message again, it's
  1004. // not shown.
  1005. var message = $msg({
  1006. from: 'lounge@localhost/dummy',
  1007. to: 'dummy@localhost.com',
  1008. type: 'groupchat',
  1009. id: view.model.messages.at(0).get('msgid')
  1010. }).c('body').t(text);
  1011. view.onChatRoomMessage(message.nodeTree);
  1012. expect($chat_content.find('.chat-message').length).toBe(1);
  1013. expect($chat_content.find('.chat-msg-content').last().text()).toBe(text);
  1014. // We don't emit an event if it's our own message
  1015. expect(_converse.emit.callCount, 1);
  1016. }));
  1017. it("will cause the chat area to be scrolled down only if it was at the bottom already", mock.initConverse(function (_converse) {
  1018. var message = 'This message is received while the chat area is scrolled up';
  1019. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  1020. var view = _converse.chatboxviews.get('lounge@localhost');
  1021. spyOn(view, 'scrollDown').andCallThrough();
  1022. runs(function () {
  1023. /* Create enough messages so that there's a
  1024. * scrollbar.
  1025. */
  1026. for (var i=0; i<20; i++) {
  1027. _converse.chatboxes.onMessage(
  1028. $msg({
  1029. from: 'lounge@localhost/someone',
  1030. to: 'dummy@localhost.com',
  1031. type: 'groupchat',
  1032. id: (new Date()).getTime(),
  1033. }).c('body').t('Message: '+i).tree());
  1034. }
  1035. });
  1036. waits(500); // Give enough time for `markScrolled` to have been called
  1037. runs(function () {
  1038. view.$content.scrollTop(0);
  1039. });
  1040. waits(250);
  1041. runs(function () {
  1042. expect(view.model.get('scrolled')).toBeTruthy();
  1043. _converse.chatboxes.onMessage(
  1044. $msg({
  1045. from: 'lounge@localhost/someone',
  1046. to: 'dummy@localhost.com',
  1047. type: 'groupchat',
  1048. id: (new Date()).getTime(),
  1049. }).c('body').t(message).tree());
  1050. });
  1051. waits(150);
  1052. runs(function () {
  1053. // Now check that the message appears inside the chatbox in the DOM
  1054. var $chat_content = view.$el.find('.chat-content');
  1055. var msg_txt = $chat_content.find('.chat-message:last').find('.chat-msg-content').text();
  1056. expect(msg_txt).toEqual(message);
  1057. expect(view.$content.scrollTop()).toBe(0);
  1058. });
  1059. }));
  1060. it("shows received chatroom subject messages", mock.initConverse(function (_converse) {
  1061. test_utils.openAndEnterChatRoom(_converse, 'jdev', 'conference.jabber.org', 'jc');
  1062. var text = 'Jabber/XMPP Development | RFCs and Extensions: http://xmpp.org/ | Protocol and XSF discussions: xsf@muc.xmpp.org';
  1063. var stanza = Strophe.xmlHtmlNode(
  1064. '<message xmlns="jabber:client" to="jc@opkode.com/_converse.js-60429116" type="groupchat" from="jdev@conference.jabber.org/ralphm">'+
  1065. ' <subject>'+text+'</subject>'+
  1066. ' <delay xmlns="urn:xmpp:delay" stamp="2014-02-04T09:35:39Z" from="jdev@conference.jabber.org"/>'+
  1067. ' <x xmlns="jabber:x:delay" stamp="20140204T09:35:39" from="jdev@conference.jabber.org"/>'+
  1068. '</message>').firstChild;
  1069. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1070. var view = _converse.chatboxviews.get('jdev@conference.jabber.org');
  1071. var $chat_content = view.$el.find('.chat-content');
  1072. expect($chat_content.find('.chat-info:last').text()).toBe('Topic set by ralphm to: '+text);
  1073. }));
  1074. it("escapes the subject before rendering it, to avoid JS-injection attacks", mock.initConverse(function (_converse) {
  1075. test_utils.openAndEnterChatRoom(_converse, 'jdev', 'conference.jabber.org', 'jc');
  1076. spyOn(window, 'alert');
  1077. var subject = '<img src="x" onerror="alert(\'XSS\');"/>';
  1078. var view = _converse.chatboxviews.get('jdev@conference.jabber.org');
  1079. view.setChatRoomSubject('ralphm', subject);
  1080. var $chat_content = view.$el.find('.chat-content');
  1081. expect($chat_content.find('.chat-info:last').text()).toBe('Topic set by ralphm to: '+subject);
  1082. }));
  1083. it("informs users if their nicknames has been changed.", mock.initConverse(function (_converse) {
  1084. /* The service then sends two presence stanzas to the full JID
  1085. * of each occupant (including the occupant who is changing his
  1086. * or her room nickname), one of type "unavailable" for the old
  1087. * nickname and one indicating availability for the new
  1088. * nickname.
  1089. *
  1090. * See: http://xmpp.org/extensions/xep-0045.html#changenick
  1091. *
  1092. * <presence
  1093. * from='coven@localhost/thirdwitch'
  1094. * id='DC352437-C019-40EC-B590-AF29E879AF98'
  1095. * to='hag66@shakespeare.lit/pda'
  1096. * type='unavailable'>
  1097. * <x xmlns='http://jabber.org/protocol/muc#user'>
  1098. * <item affiliation='member'
  1099. * jid='hag66@shakespeare.lit/pda'
  1100. * nick='oldhag'
  1101. * role='occupant'/>
  1102. * <status code='303'/>
  1103. * <status code='110'/>
  1104. * </x>
  1105. * </presence>
  1106. *
  1107. * <presence
  1108. * from='coven@localhost/oldhag'
  1109. * id='5B4F27A4-25ED-43F7-A699-382C6B4AFC67'
  1110. * to='hag66@shakespeare.lit/pda'>
  1111. * <x xmlns='http://jabber.org/protocol/muc#user'>
  1112. * <item affiliation='member'
  1113. * jid='hag66@shakespeare.lit/pda'
  1114. * role='occupant'/>
  1115. * <status code='110'/>
  1116. * </x>
  1117. * </presence>
  1118. */
  1119. var __ = utils.__.bind(_converse);
  1120. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'oldnick');
  1121. var view = _converse.chatboxviews.get('lounge@localhost');
  1122. var $chat_content = view.$el.find('.chat-content');
  1123. // The user has just entered the room and receives their own
  1124. // presence from the server.
  1125. // See example 24:
  1126. // http://xmpp.org/extensions/xep-0045.html#enter-pres
  1127. var presence = $pres({
  1128. to:'dummy@localhost/pda',
  1129. from:'lounge@localhost/oldnick',
  1130. id:'DC352437-C019-40EC-B590-AF29E879AF97'
  1131. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  1132. .c('item').attrs({
  1133. affiliation: 'member',
  1134. jid: 'dummy@localhost/pda',
  1135. role: 'occupant'
  1136. }).up()
  1137. .c('status').attrs({code:'110'}).up()
  1138. .c('status').attrs({code:'210'}).nodeTree;
  1139. _converse.connection._dataRecv(test_utils.createRequest(presence));
  1140. var $occupants = view.$('.occupant-list');
  1141. expect($occupants.children().length).toBe(1);
  1142. expect($occupants.children().first(0).text()).toBe("oldnick");
  1143. expect($chat_content.find('div.chat-info').length).toBe(2);
  1144. expect($chat_content.find('div.chat-info:first').html()).toBe("oldnick has joined the room.");
  1145. expect($chat_content.find('div.chat-info:last').html()).toBe(__(_converse.muc.new_nickname_messages["210"], "oldnick"));
  1146. presence = $pres().attrs({
  1147. from:'lounge@localhost/oldnick',
  1148. id:'DC352437-C019-40EC-B590-AF29E879AF98',
  1149. to:'dummy@localhost/pda',
  1150. type:'unavailable'
  1151. })
  1152. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  1153. .c('item').attrs({
  1154. affiliation: 'member',
  1155. jid: 'dummy@localhost/pda',
  1156. nick: 'newnick',
  1157. role: 'occupant'
  1158. }).up()
  1159. .c('status').attrs({code:'303'}).up()
  1160. .c('status').attrs({code:'110'}).nodeTree;
  1161. _converse.connection._dataRecv(test_utils.createRequest(presence));
  1162. expect($chat_content.find('div.chat-info').length).toBe(3);
  1163. expect($chat_content.find('div.chat-info').last().html()).toBe(__(_converse.muc.new_nickname_messages["303"], "newnick"));
  1164. $occupants = view.$('.occupant-list');
  1165. expect($occupants.children().length).toBe(0);
  1166. presence = $pres().attrs({
  1167. from:'lounge@localhost/newnick',
  1168. id:'5B4F27A4-25ED-43F7-A699-382C6B4AFC67',
  1169. to:'dummy@localhost/pda'
  1170. })
  1171. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  1172. .c('item').attrs({
  1173. affiliation: 'member',
  1174. jid: 'dummy@localhost/pda',
  1175. role: 'occupant'
  1176. }).up()
  1177. .c('status').attrs({code:'110'}).nodeTree;
  1178. _converse.connection._dataRecv(test_utils.createRequest(presence));
  1179. expect($chat_content.find('div.chat-info').length).toBe(4);
  1180. expect($chat_content.find('div.chat-info').get(2).textContent).toBe(__(_converse.muc.new_nickname_messages["303"], "newnick"));
  1181. expect($chat_content.find('div.chat-info').last().html()).toBe("newnick has joined the room.");
  1182. $occupants = view.$('.occupant-list');
  1183. expect($occupants.children().length).toBe(1);
  1184. expect($occupants.children().first(0).text()).toBe("newnick");
  1185. }));
  1186. it("queries for the room information before attempting to join the user", mock.initConverse(function (_converse) {
  1187. var sent_IQ, IQ_id;
  1188. var sendIQ = _converse.connection.sendIQ;
  1189. spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
  1190. sent_IQ = iq;
  1191. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  1192. });
  1193. _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'});
  1194. // Check that the room queried for the feautures.
  1195. expect(sent_IQ.toLocaleString()).toBe(
  1196. "<iq from='dummy@localhost/resource' to='coven@chat.shakespeare.lit' type='get' xmlns='jabber:client' id='"+IQ_id+"'>"+
  1197. "<query xmlns='http://jabber.org/protocol/disco#info'/>"+
  1198. "</iq>");
  1199. /* <iq from='coven@chat.shakespeare.lit'
  1200. * id='ik3vs715'
  1201. * to='hag66@shakespeare.lit/pda'
  1202. * type='result'>
  1203. * <query xmlns='http://jabber.org/protocol/disco#info'>
  1204. * <identity
  1205. * category='conference'
  1206. * name='A Dark Cave'
  1207. * type='text'/>
  1208. * <feature var='http://jabber.org/protocol/muc'/>
  1209. * <feature var='muc_passwordprotected'/>
  1210. * <feature var='muc_hidden'/>
  1211. * <feature var='muc_temporary'/>
  1212. * <feature var='muc_open'/>
  1213. * <feature var='muc_unmoderated'/>
  1214. * <feature var='muc_nonanonymous'/>
  1215. * </query>
  1216. * </iq>
  1217. */
  1218. var features_stanza = $iq({
  1219. from: 'coven@chat.shakespeare.lit',
  1220. 'id': IQ_id,
  1221. 'to': 'dummy@localhost/desktop',
  1222. 'type': 'result'
  1223. })
  1224. .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'})
  1225. .c('identity', {
  1226. 'category': 'conference',
  1227. 'name': 'A Dark Cave',
  1228. 'type': 'text'
  1229. }).up()
  1230. .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up()
  1231. .c('feature', {'var': 'muc_passwordprotected'}).up()
  1232. .c('feature', {'var': 'muc_hidden'}).up()
  1233. .c('feature', {'var': 'muc_temporary'}).up()
  1234. .c('feature', {'var': 'muc_open'}).up()
  1235. .c('feature', {'var': 'muc_unmoderated'}).up()
  1236. .c('feature', {'var': 'muc_nonanonymous'});
  1237. _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
  1238. var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
  1239. expect(view.model.get('features_fetched')).toBe(true);
  1240. expect(view.model.get('passwordprotected')).toBe(true);
  1241. expect(view.model.get('hidden')).toBe(true);
  1242. expect(view.model.get('temporary')).toBe(true);
  1243. expect(view.model.get('open')).toBe(true);
  1244. expect(view.model.get('unmoderated')).toBe(true);
  1245. expect(view.model.get('nonanonymous')).toBe(true);
  1246. }));
  1247. it("updates the shown features when the room configuration has changed", mock.initConverse(function (_converse) {
  1248. var sent_IQ, IQ_id;
  1249. var sendIQ = _converse.connection.sendIQ;
  1250. test_utils.openAndEnterChatRoom(_converse, 'room', 'conference.example.org', 'dummy');
  1251. var view = _converse.chatboxviews.get('room@conference.example.org');
  1252. view.model.set({
  1253. 'passwordprotected': false,
  1254. 'unsecured': true,
  1255. 'hidden': false,
  1256. 'public': true,
  1257. 'membersonly': false,
  1258. 'open': true,
  1259. 'persistent': false,
  1260. 'temporary': true,
  1261. 'nonanonymous': true,
  1262. 'semianonymous': false,
  1263. 'moderated': false,
  1264. 'unmoderated': true
  1265. });
  1266. expect(view.model.get('persistent')).toBe(false);
  1267. expect(view.model.get('temporary')).toBe(true);
  1268. view.model.set({'persistent': true});
  1269. expect(view.model.get('persistent')).toBe(true);
  1270. expect(view.model.get('temporary')).toBe(false);
  1271. expect(view.model.get('unsecured')).toBe(true);
  1272. expect(view.model.get('passwordprotected')).toBe(false);
  1273. view.model.set({'passwordprotected': true});
  1274. expect(view.model.get('unsecured')).toBe(false);
  1275. expect(view.model.get('passwordprotected')).toBe(true);
  1276. expect(view.model.get('unmoderated')).toBe(true);
  1277. expect(view.model.get('moderated')).toBe(false);
  1278. view.model.set({'moderated': true});
  1279. expect(view.model.get('unmoderated')).toBe(false);
  1280. expect(view.model.get('moderated')).toBe(true);
  1281. expect(view.model.get('nonanonymous')).toBe(true);
  1282. expect(view.model.get('semianonymous')).toBe(false);
  1283. view.model.set({'nonanonymous': false});
  1284. expect(view.model.get('nonanonymous')).toBe(false);
  1285. expect(view.model.get('semianonymous')).toBe(true);
  1286. expect(view.model.get('open')).toBe(true);
  1287. expect(view.model.get('membersonly')).toBe(false);
  1288. view.model.set({'membersonly': true});
  1289. expect(view.model.get('open')).toBe(false);
  1290. expect(view.model.get('membersonly')).toBe(true);
  1291. }));
  1292. it("indicates when a room is no longer anonymous", mock.initConverse(function (_converse) {
  1293. var sent_IQ, IQ_id;
  1294. var sendIQ = _converse.connection.sendIQ;
  1295. spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
  1296. sent_IQ = iq;
  1297. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  1298. });
  1299. _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'});
  1300. // We pretend this is a new room, so no disco info is returned.
  1301. var features_stanza = $iq({
  1302. from: 'coven@chat.shakespeare.lit',
  1303. 'id': IQ_id,
  1304. 'to': 'dummy@localhost/desktop',
  1305. 'type': 'error'
  1306. }).c('error', {'type': 'cancel'})
  1307. .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
  1308. _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
  1309. var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
  1310. /* <message xmlns="jabber:client"
  1311. * type="groupchat"
  1312. * to="dummy@localhost/_converse.js-27854181"
  1313. * from="coven@chat.shakespeare.lit">
  1314. * <x xmlns="http://jabber.org/protocol/muc#user">
  1315. * <status code="104"/>
  1316. * <status code="172"/>
  1317. * </x>
  1318. * </message>
  1319. */
  1320. var message = $msg({
  1321. type:'groupchat',
  1322. to: 'dummy@localhost/_converse.js-27854181',
  1323. from: 'coven@chat.shakespeare.lit'
  1324. }).c('x', {xmlns: Strophe.NS.MUC_USER})
  1325. .c('status', {code: '104'}).up()
  1326. .c('status', {code: '172'});
  1327. _converse.connection._dataRecv(test_utils.createRequest(message));
  1328. var $chat_body = view.$('.chatroom-body');
  1329. expect($chat_body.html().trim().indexOf(
  1330. '<div class="chat-info">This room is now no longer anonymous</div>'
  1331. )).not.toBe(-1);
  1332. }));
  1333. it("informs users if they have been kicked out of the chat room", mock.initConverse(function (_converse) {
  1334. /* <presence
  1335. * from='harfleur@chat.shakespeare.lit/pistol'
  1336. * to='pistol@shakespeare.lit/harfleur'
  1337. * type='unavailable'>
  1338. * <x xmlns='http://jabber.org/protocol/muc#user'>
  1339. * <item affiliation='none' role='none'>
  1340. * <actor nick='Fluellen'/>
  1341. * <reason>Avaunt, you cullion!</reason>
  1342. * </item>
  1343. * <status code='110'/>
  1344. * <status code='307'/>
  1345. * </x>
  1346. * </presence>
  1347. */
  1348. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  1349. var presence = $pres().attrs({
  1350. from:'lounge@localhost/dummy',
  1351. to:'dummy@localhost/pda',
  1352. type:'unavailable'
  1353. })
  1354. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  1355. .c('item').attrs({
  1356. affiliation: 'none',
  1357. jid: 'dummy@localhost/pda',
  1358. role: 'none'
  1359. })
  1360. .c('actor').attrs({nick: 'Fluellen'}).up()
  1361. .c('reason').t('Avaunt, you cullion!').up()
  1362. .up()
  1363. .c('status').attrs({code:'110'}).up()
  1364. .c('status').attrs({code:'307'}).nodeTree;
  1365. var view = _converse.chatboxviews.get('lounge@localhost');
  1366. view.onChatRoomPresence(presence);
  1367. expect(view.$('.chat-area').is(':visible')).toBeFalsy();
  1368. expect(view.$('.occupants').is(':visible')).toBeFalsy();
  1369. var $chat_body = view.$('.chatroom-body');
  1370. expect($chat_body.html().trim().indexOf(
  1371. '<p>You have been kicked from this room</p>'+
  1372. '<p>This action was done by Fluellen.</p>'+
  1373. '<p>The reason given is: "Avaunt, you cullion!".</p>'
  1374. )).not.toBe(-1);
  1375. }));
  1376. it("can be saved to, and retrieved from, browserStorage", mock.initConverse(function (_converse) {
  1377. test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  1378. // We instantiate a new ChatBoxes collection, which by default
  1379. // will be empty.
  1380. test_utils.openControlBox();
  1381. var newchatboxes = new _converse.ChatBoxes();
  1382. expect(newchatboxes.length).toEqual(0);
  1383. // The chatboxes will then be fetched from browserStorage inside the
  1384. // onConnected method
  1385. newchatboxes.onConnected();
  1386. expect(newchatboxes.length).toEqual(2);
  1387. // Check that the chatrooms retrieved from browserStorage
  1388. // have the same attributes values as the original ones.
  1389. var attrs = ['id', 'box_id', 'visible'];
  1390. var new_attrs, old_attrs;
  1391. for (var i=0; i<attrs.length; i++) {
  1392. new_attrs = _.map(_.map(newchatboxes.models, 'attributes'), attrs[i]);
  1393. old_attrs = _.map(_.map(_converse.chatboxes.models, 'attributes'), attrs[i]);
  1394. // FIXME: should have have to sort here? Order must
  1395. // probably be the same...
  1396. // This should be fixed once the controlbox always opens
  1397. // only on the right.
  1398. expect(_.isEqual(new_attrs.sort(), old_attrs.sort())).toEqual(true);
  1399. }
  1400. _converse.rosterview.render();
  1401. }));
  1402. it("can be minimized by clicking a DOM element with class 'toggle-chatbox-button'", mock.initConverse(function (_converse) {
  1403. test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  1404. var view = _converse.chatboxviews.get('lounge@localhost'),
  1405. trimmed_chatboxes = _converse.minimized_chats;
  1406. spyOn(view, 'minimize').andCallThrough();
  1407. spyOn(view, 'maximize').andCallThrough();
  1408. spyOn(_converse, 'emit');
  1409. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  1410. runs(function () {
  1411. view.$el.find('.toggle-chatbox-button').click();
  1412. });
  1413. waits(350);
  1414. runs(function () {
  1415. expect(view.minimize).toHaveBeenCalled();
  1416. expect(_converse.emit).toHaveBeenCalledWith('chatBoxMinimized', jasmine.any(Object));
  1417. expect(view.$el.is(':visible')).toBeFalsy();
  1418. expect(view.model.get('minimized')).toBeTruthy();
  1419. expect(view.minimize).toHaveBeenCalled();
  1420. var trimmedview = trimmed_chatboxes.get(view.model.get('id'));
  1421. trimmedview.$("a.restore-chat").click();
  1422. });
  1423. waits(350);
  1424. runs(function () {
  1425. expect(view.maximize).toHaveBeenCalled();
  1426. expect(_converse.emit).toHaveBeenCalledWith('chatBoxMaximized', jasmine.any(Object));
  1427. expect(view.$el.is(':visible')).toBeTruthy();
  1428. expect(view.model.get('minimized')).toBeFalsy();
  1429. expect(_converse.emit.callCount, 3);
  1430. });
  1431. }));
  1432. it("can be closed again by clicking a DOM element with class 'close-chatbox-button'", mock.initConverse(function (_converse) {
  1433. test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  1434. var view = _converse.chatboxviews.get('lounge@localhost');
  1435. spyOn(view, 'close').andCallThrough();
  1436. spyOn(_converse, 'emit');
  1437. spyOn(view, 'leave');
  1438. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  1439. runs(function () {
  1440. view.$el.find('.close-chatbox-button').click();
  1441. });
  1442. waits(50);
  1443. runs(function () {
  1444. expect(view.close).toHaveBeenCalled();
  1445. expect(view.leave).toHaveBeenCalled();
  1446. // XXX: After refactoring, the chat box only gets closed
  1447. // once we have confirmation from the server. To test this,
  1448. // we would have to mock the returned presence stanza.
  1449. // See the "leave" method on the ChatRoomView.
  1450. // expect(_converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
  1451. });
  1452. }));
  1453. });
  1454. describe("Each chat room can take special commands", function () {
  1455. it("to set the room topic", mock.initConverse(function (_converse) {
  1456. var sent_stanza;
  1457. test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  1458. var view = _converse.chatboxviews.get('lounge@localhost');
  1459. spyOn(view, 'onMessageSubmitted').andCallThrough();
  1460. spyOn(view, 'clearChatRoomMessages');
  1461. spyOn(_converse.connection, 'send').andCallFake(function (stanza) {
  1462. sent_stanza = stanza;
  1463. });
  1464. // Check the alias /topic
  1465. var $textarea = view.$el.find('.chat-textarea');
  1466. $textarea.text('/topic This is the room subject');
  1467. $textarea.trigger($.Event('keypress', {keyCode: 13}));
  1468. expect(view.onMessageSubmitted).toHaveBeenCalled();
  1469. expect(_converse.connection.send).toHaveBeenCalled();
  1470. expect(sent_stanza.textContent).toBe('This is the room subject');
  1471. // Check /subject
  1472. $textarea.val('/subject This is a new subject');
  1473. $textarea.trigger($.Event('keypress', {keyCode: 13}));
  1474. expect(sent_stanza.textContent).toBe('This is a new subject');
  1475. // Check case insensitivity
  1476. //
  1477. // XXX: This works in the browser but fails on phantomjs
  1478. // expect(sent_stanza.outerHTML).toBe(
  1479. // '<message to="lounge@localhost" from="dummy@localhost/resource" type="groupchat" xmlns="jabber:client">'+
  1480. // '<subject xmlns="jabber:client">This is yet another subject</subject>'+
  1481. // '</message>');
  1482. $textarea.val('/Subject This is yet another subject');
  1483. $textarea.trigger($.Event('keypress', {keyCode: 13}));
  1484. expect(sent_stanza.textContent).toBe('This is yet another subject');
  1485. }));
  1486. it("to clear messages", mock.initConverse(function (_converse) {
  1487. test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  1488. var view = _converse.chatboxviews.get('lounge@localhost');
  1489. spyOn(view, 'onMessageSubmitted').andCallThrough();
  1490. spyOn(view, 'clearChatRoomMessages');
  1491. view.$el.find('.chat-textarea').text('/clear');
  1492. view.$el.find('textarea.chat-textarea').trigger($.Event('keypress', {keyCode: 13}));
  1493. expect(view.onMessageSubmitted).toHaveBeenCalled();
  1494. expect(view.clearChatRoomMessages).toHaveBeenCalled();
  1495. }));
  1496. it("to make a user an owner", mock.initConverse(function (_converse) {
  1497. var sent_IQ, IQ_id;
  1498. var sendIQ = _converse.connection.sendIQ;
  1499. spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
  1500. sent_IQ = iq;
  1501. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  1502. });
  1503. test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  1504. var view = _converse.chatboxviews.get('lounge@localhost');
  1505. spyOn(view, 'onMessageSubmitted').andCallThrough();
  1506. spyOn(view, 'setAffiliation').andCallThrough();
  1507. spyOn(view, 'showStatusNotification').andCallThrough();
  1508. spyOn(view, 'validateRoleChangeCommand').andCallThrough();
  1509. view.$el.find('.chat-textarea').text('/owner');
  1510. view.$el.find('textarea.chat-textarea').trigger($.Event('keypress', {keyCode: 13}));
  1511. expect(view.onMessageSubmitted).toHaveBeenCalled();
  1512. expect(view.validateRoleChangeCommand).toHaveBeenCalled();
  1513. expect(view.showStatusNotification).toHaveBeenCalledWith(
  1514. "Error: the \"owner\" command takes two arguments, the user's nickname and optionally a reason.",
  1515. true
  1516. );
  1517. expect(view.setAffiliation).not.toHaveBeenCalled();
  1518. // Call now with the correct amount of arguments.
  1519. // XXX: Calling onMessageSubmitted directly, trying
  1520. // again via triggering Event doesn't work for some weird
  1521. // reason.
  1522. view.onMessageSubmitted('/owner annoyingGuy@localhost You\'re annoying');
  1523. expect(view.validateRoleChangeCommand.callCount).toBe(2);
  1524. expect(view.showStatusNotification.callCount).toBe(1);
  1525. expect(view.setAffiliation).toHaveBeenCalled();
  1526. // Check that the member list now gets updated
  1527. expect(sent_IQ.toLocaleString()).toBe(
  1528. "<iq to='lounge@localhost' type='set' xmlns='jabber:client' id='"+IQ_id+"'>"+
  1529. "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
  1530. "<item affiliation='owner' jid='annoyingGuy@localhost'>"+
  1531. "<reason>You&apos;re annoying</reason>"+
  1532. "</item>"+
  1533. "</query>"+
  1534. "</iq>");
  1535. }));
  1536. it("to ban a user", mock.initConverse(function (_converse) {
  1537. var sent_IQ, IQ_id;
  1538. var sendIQ = _converse.connection.sendIQ;
  1539. spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
  1540. sent_IQ = iq;
  1541. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  1542. });
  1543. test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  1544. var view = _converse.chatboxviews.get('lounge@localhost');
  1545. spyOn(view, 'onMessageSubmitted').andCallThrough();
  1546. spyOn(view, 'setAffiliation').andCallThrough();
  1547. spyOn(view, 'showStatusNotification').andCallThrough();
  1548. spyOn(view, 'validateRoleChangeCommand').andCallThrough();
  1549. view.$el.find('.chat-textarea').text('/ban');
  1550. view.$el.find('textarea.chat-textarea').trigger($.Event('keypress', {keyCode: 13}));
  1551. expect(view.onMessageSubmitted).toHaveBeenCalled();
  1552. expect(view.validateRoleChangeCommand).toHaveBeenCalled();
  1553. expect(view.showStatusNotification).toHaveBeenCalledWith(
  1554. "Error: the \"ban\" command takes two arguments, the user's nickname and optionally a reason.",
  1555. true
  1556. );
  1557. expect(view.setAffiliation).not.toHaveBeenCalled();
  1558. // Call now with the correct amount of arguments.
  1559. // XXX: Calling onMessageSubmitted directly, trying
  1560. // again via triggering Event doesn't work for some weird
  1561. // reason.
  1562. view.onMessageSubmitted('/ban annoyingGuy@localhost You\'re annoying');
  1563. expect(view.validateRoleChangeCommand.callCount).toBe(2);
  1564. expect(view.showStatusNotification.callCount).toBe(1);
  1565. expect(view.setAffiliation).toHaveBeenCalled();
  1566. // Check that the member list now gets updated
  1567. expect(sent_IQ.toLocaleString()).toBe(
  1568. "<iq to='lounge@localhost' type='set' xmlns='jabber:client' id='"+IQ_id+"'>"+
  1569. "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
  1570. "<item affiliation='outcast' jid='annoyingGuy@localhost'>"+
  1571. "<reason>You&apos;re annoying</reason>"+
  1572. "</item>"+
  1573. "</query>"+
  1574. "</iq>");
  1575. }));
  1576. });
  1577. describe("When attempting to enter a chatroom", function () {
  1578. var submitRoomForm = function (_converse) {
  1579. var roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
  1580. var $input = roomspanel.$el.find('input.new-chatroom-name');
  1581. var $nick = roomspanel.$el.find('input.new-chatroom-nick');
  1582. var $server = roomspanel.$el.find('input.new-chatroom-server');
  1583. $input.val('problematic');
  1584. $nick.val('dummy');
  1585. $server.val('muc.localhost');
  1586. roomspanel.$el.find('form').submit();
  1587. };
  1588. it("will show an error message if the room requires a password", mock.initConverse(function (_converse) {
  1589. submitRoomForm(_converse);
  1590. var presence = $pres().attrs({
  1591. from:'lounge@localhost/thirdwitch',
  1592. id:'n13mt3l',
  1593. to:'dummy@localhost/pda',
  1594. type:'error'})
  1595. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  1596. .c('error').attrs({by:'lounge@localhost', type:'auth'})
  1597. .c('not-authorized').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  1598. var view = _converse.chatboxviews.get('problematic@muc.localhost');
  1599. spyOn(view, 'renderPasswordForm').andCallThrough();
  1600. runs(function () {
  1601. view.onChatRoomPresence(presence);
  1602. });
  1603. waits(250);
  1604. runs(function () {
  1605. var $chat_body = view.$el.find('.chatroom-body');
  1606. expect(view.renderPasswordForm).toHaveBeenCalled();
  1607. expect($chat_body.find('form.chatroom-form').length).toBe(1);
  1608. expect($chat_body.find('legend').text()).toBe('This chatroom requires a password');
  1609. });
  1610. }));
  1611. it("will show an error message if the room is members-only and the user not included", mock.initConverse(function (_converse) {
  1612. submitRoomForm(_converse);
  1613. var presence = $pres().attrs({
  1614. from:'lounge@localhost/thirdwitch',
  1615. id:'n13mt3l',
  1616. to:'dummy@localhost/pda',
  1617. type:'error'})
  1618. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  1619. .c('error').attrs({by:'lounge@localhost', type:'auth'})
  1620. .c('registration-required').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  1621. var view = _converse.chatboxviews.get('problematic@muc.localhost');
  1622. spyOn(view, 'showErrorMessage').andCallThrough();
  1623. view.onChatRoomPresence(presence);
  1624. expect(view.$el.find('.chatroom-body p:last').text()).toBe('You are not on the member list of this room');
  1625. }));
  1626. it("will show an error message if the user has been banned", mock.initConverse(function (_converse) {
  1627. submitRoomForm(_converse);
  1628. var presence = $pres().attrs({
  1629. from:'lounge@localhost/thirdwitch',
  1630. id:'n13mt3l',
  1631. to:'dummy@localhost/pda',
  1632. type:'error'})
  1633. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  1634. .c('error').attrs({by:'lounge@localhost', type:'auth'})
  1635. .c('forbidden').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  1636. var view = _converse.chatboxviews.get('problematic@muc.localhost');
  1637. spyOn(view, 'showErrorMessage').andCallThrough();
  1638. view.onChatRoomPresence(presence);
  1639. expect(view.$el.find('.chatroom-body p:last').text()).toBe('You have been banned from this room');
  1640. }));
  1641. it("will render a nickname form if a nickname conflict happens and muc_nickname_from_jid=false", mock.initConverse(function (_converse) {
  1642. submitRoomForm(_converse);
  1643. var presence = $pres().attrs({
  1644. from:'lounge@localhost/thirdwitch',
  1645. id:'n13mt3l',
  1646. to:'dummy@localhost/pda',
  1647. type:'error'})
  1648. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  1649. .c('error').attrs({by:'lounge@localhost', type:'cancel'})
  1650. .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  1651. var view = _converse.chatboxviews.get('problematic@muc.localhost');
  1652. spyOn(view, 'showErrorMessage').andCallThrough();
  1653. view.onChatRoomPresence(presence);
  1654. expect(view.$el.find('.chatroom-body form.chatroom-form label:first').text()).toBe('Please choose your nickname');
  1655. }));
  1656. it("will automatically choose a new nickname if a nickname conflict happens and muc_nickname_from_jid=true", mock.initConverse(function (_converse) {
  1657. /* <presence
  1658. * from='coven@chat.shakespeare.lit/thirdwitch'
  1659. * id='n13mt3l'
  1660. * to='hag66@shakespeare.lit/pda'
  1661. * type='error'>
  1662. * <x xmlns='http://jabber.org/protocol/muc'/>
  1663. * <error by='coven@chat.shakespeare.lit' type='cancel'>
  1664. * <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
  1665. * </error>
  1666. * </presence>
  1667. */
  1668. submitRoomForm(_converse);
  1669. _converse.muc_nickname_from_jid = true;
  1670. var attrs = {
  1671. from:'lounge@localhost/dummy',
  1672. id:'n13mt3l',
  1673. to:'dummy@localhost/pda',
  1674. type:'error'
  1675. };
  1676. var presence = $pres().attrs(attrs)
  1677. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  1678. .c('error').attrs({by:'lounge@localhost', type:'cancel'})
  1679. .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  1680. var view = _converse.chatboxviews.get('problematic@muc.localhost');
  1681. spyOn(view, 'showErrorMessage').andCallThrough();
  1682. spyOn(view, 'join').andCallThrough();
  1683. // Simulate repeatedly that there's already someone in the room
  1684. // with that nickname
  1685. view.onChatRoomPresence(presence);
  1686. expect(view.join).toHaveBeenCalledWith('dummy-2');
  1687. attrs.from = 'lounge@localhost/dummy-2';
  1688. presence = $pres().attrs(attrs)
  1689. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  1690. .c('error').attrs({by:'lounge@localhost', type:'cancel'})
  1691. .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  1692. view.onChatRoomPresence(presence);
  1693. expect(view.join).toHaveBeenCalledWith('dummy-3');
  1694. attrs.from = 'lounge@localhost/dummy-3';
  1695. presence = $pres().attrs(attrs)
  1696. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  1697. .c('error').attrs({by:'lounge@localhost', type:'cancel'})
  1698. .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  1699. view.onChatRoomPresence(presence);
  1700. expect(view.join).toHaveBeenCalledWith('dummy-4');
  1701. }));
  1702. it("will show an error message if the user is not allowed to have created the room", mock.initConverse(function (_converse) {
  1703. submitRoomForm(_converse);
  1704. var presence = $pres().attrs({
  1705. from:'lounge@localhost/thirdwitch',
  1706. id:'n13mt3l',
  1707. to:'dummy@localhost/pda',
  1708. type:'error'})
  1709. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  1710. .c('error').attrs({by:'lounge@localhost', type:'cancel'})
  1711. .c('not-allowed').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  1712. var view = _converse.chatboxviews.get('problematic@muc.localhost');
  1713. spyOn(view, 'showErrorMessage').andCallThrough();
  1714. view.onChatRoomPresence(presence);
  1715. expect(view.$el.find('.chatroom-body p:last').text()).toBe('You are not allowed to create new rooms');
  1716. }));
  1717. it("will show an error message if the user's nickname doesn't conform to room policy", mock.initConverse(function (_converse) {
  1718. submitRoomForm(_converse);
  1719. var presence = $pres().attrs({
  1720. from:'lounge@localhost/thirdwitch',
  1721. id:'n13mt3l',
  1722. to:'dummy@localhost/pda',
  1723. type:'error'})
  1724. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  1725. .c('error').attrs({by:'lounge@localhost', type:'cancel'})
  1726. .c('not-acceptable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  1727. var view = _converse.chatboxviews.get('problematic@muc.localhost');
  1728. spyOn(view, 'showErrorMessage').andCallThrough();
  1729. view.onChatRoomPresence(presence);
  1730. expect(view.$el.find('.chatroom-body p:last').text()).toBe("Your nickname doesn't conform to this room's policies");
  1731. }));
  1732. it("will show an error message if the room doesn't yet exist", mock.initConverse(function (_converse) {
  1733. submitRoomForm(_converse);
  1734. var presence = $pres().attrs({
  1735. from:'lounge@localhost/thirdwitch',
  1736. id:'n13mt3l',
  1737. to:'dummy@localhost/pda',
  1738. type:'error'})
  1739. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  1740. .c('error').attrs({by:'lounge@localhost', type:'cancel'})
  1741. .c('item-not-found').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  1742. var view = _converse.chatboxviews.get('problematic@muc.localhost');
  1743. spyOn(view, 'showErrorMessage').andCallThrough();
  1744. view.onChatRoomPresence(presence);
  1745. expect(view.$el.find('.chatroom-body p:last').text()).toBe("This room does not (yet) exist");
  1746. }));
  1747. it("will show an error message if the room has reached its maximum number of occupants", mock.initConverse(function (_converse) {
  1748. submitRoomForm(_converse);
  1749. var presence = $pres().attrs({
  1750. from:'lounge@localhost/thirdwitch',
  1751. id:'n13mt3l',
  1752. to:'dummy@localhost/pda',
  1753. type:'error'})
  1754. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  1755. .c('error').attrs({by:'lounge@localhost', type:'cancel'})
  1756. .c('service-unavailable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  1757. var view = _converse.chatboxviews.get('problematic@muc.localhost');
  1758. spyOn(view, 'showErrorMessage').andCallThrough();
  1759. view.onChatRoomPresence(presence);
  1760. expect(view.$el.find('.chatroom-body p:last').text()).toBe("This room has reached its maximum number of occupants");
  1761. }));
  1762. });
  1763. describe("Someone being invited to a chat room", function () {
  1764. it("will first be added to the member list if the chat room is members only", mock.initConverse(function (_converse) {
  1765. var sent_IQs = [], IQ_ids = [];
  1766. var sendIQ = _converse.connection.sendIQ;
  1767. spyOn(_converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
  1768. sent_IQs.push(iq);
  1769. IQ_ids.push(sendIQ.bind(this)(iq, callback, errback));
  1770. });
  1771. test_utils.openChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'dummy');
  1772. // State that the chat is members-only via the features IQ
  1773. var features_stanza = $iq({
  1774. from: 'coven@chat.shakespeare.lit',
  1775. 'id': IQ_ids.pop(),
  1776. 'to': 'dummy@localhost/desktop',
  1777. 'type': 'result'
  1778. })
  1779. .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'})
  1780. .c('identity', {
  1781. 'category': 'conference',
  1782. 'name': 'A Dark Cave',
  1783. 'type': 'text'
  1784. }).up()
  1785. .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up()
  1786. .c('feature', {'var': 'muc_hidden'}).up()
  1787. .c('feature', {'var': 'muc_temporary'}).up()
  1788. .c('feature', {'var': 'muc_membersonly'}).up();
  1789. _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
  1790. var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
  1791. expect(view.model.get('membersonly')).toBeTruthy();
  1792. test_utils.createContacts(_converse, 'current');
  1793. var sent_stanza,
  1794. sent_id;
  1795. spyOn(_converse.connection, 'send').andCallFake(function (stanza) {
  1796. if (stanza.nodeTree && stanza.nodeTree.nodeName === 'message') {
  1797. sent_id = stanza.nodeTree.getAttribute('id');
  1798. sent_stanza = stanza;
  1799. }
  1800. });
  1801. var name = mock.cur_names[0];
  1802. var invitee_jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
  1803. var reason = "Please join this chat room";
  1804. view.directInvite(invitee_jid, reason);
  1805. var admin_iq_id = IQ_ids.pop();
  1806. var owner_iq_id = IQ_ids.pop();
  1807. var member_iq_id = IQ_ids.pop();
  1808. // Check in reverse order that we requested all three lists
  1809. // (member, owner and admin).
  1810. expect(sent_IQs.pop().toLocaleString()).toBe(
  1811. "<iq to='coven@chat.shakespeare.lit' type='get' xmlns='jabber:client' id='"+admin_iq_id+"'>"+
  1812. "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
  1813. "<item affiliation='admin'/>"+
  1814. "</query>"+
  1815. "</iq>");
  1816. expect(sent_IQs.pop().toLocaleString()).toBe(
  1817. "<iq to='coven@chat.shakespeare.lit' type='get' xmlns='jabber:client' id='"+owner_iq_id+"'>"+
  1818. "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
  1819. "<item affiliation='owner'/>"+
  1820. "</query>"+
  1821. "</iq>");
  1822. expect(sent_IQs.pop().toLocaleString()).toBe(
  1823. "<iq to='coven@chat.shakespeare.lit' type='get' xmlns='jabber:client' id='"+member_iq_id+"'>"+
  1824. "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
  1825. "<item affiliation='member'/>"+
  1826. "</query>"+
  1827. "</iq>");
  1828. /* Now the service sends the member list to the user
  1829. *
  1830. * <iq from='coven@chat.shakespeare.lit'
  1831. * id='member3'
  1832. * to='crone1@shakespeare.lit/desktop'
  1833. * type='result'>
  1834. * <query xmlns='http://jabber.org/protocol/muc#admin'>
  1835. * <item affiliation='member'
  1836. * jid='hag66@shakespeare.lit'
  1837. * nick='thirdwitch'
  1838. * role='participant'/>
  1839. * </query>
  1840. * </iq>
  1841. */
  1842. var member_list_stanza = $iq({
  1843. 'from': 'coven@chat.shakespeare.lit',
  1844. 'id': member_iq_id,
  1845. 'to': 'dummy@localhost/resource',
  1846. 'type': 'result'
  1847. }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN})
  1848. .c('item', {
  1849. 'affiliation': 'member',
  1850. 'jid': 'hag66@shakespeare.lit',
  1851. 'nick': 'thirdwitch',
  1852. 'role': 'participant'
  1853. });
  1854. _converse.connection._dataRecv(test_utils.createRequest(member_list_stanza));
  1855. var admin_list_stanza = $iq({
  1856. 'from': 'coven@chat.shakespeare.lit',
  1857. 'id': admin_iq_id,
  1858. 'to': 'dummy@localhost/resource',
  1859. 'type': 'result'
  1860. }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN})
  1861. .c('item', {
  1862. 'affiliation': 'admin',
  1863. 'jid': 'wiccarocks@shakespeare.lit',
  1864. 'nick': 'secondwitch'
  1865. });
  1866. _converse.connection._dataRecv(test_utils.createRequest(admin_list_stanza));
  1867. var owner_list_stanza = $iq({
  1868. 'from': 'coven@chat.shakespeare.lit',
  1869. 'id': owner_iq_id,
  1870. 'to': 'dummy@localhost/resource',
  1871. 'type': 'result'
  1872. }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN})
  1873. .c('item', {
  1874. 'affiliation': 'owner',
  1875. 'jid': 'crone1@shakespeare.lit',
  1876. });
  1877. _converse.connection._dataRecv(test_utils.createRequest(owner_list_stanza));
  1878. // Check that the member list now gets updated
  1879. expect(sent_IQs.pop().toLocaleString()).toBe(
  1880. "<iq to='coven@chat.shakespeare.lit' type='set' xmlns='jabber:client' id='"+IQ_ids.pop()+"'>"+
  1881. "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
  1882. "<item affiliation='member' jid='"+invitee_jid+"'>"+
  1883. "<reason>Please join this chat room</reason>"+
  1884. "</item>"+
  1885. "</query>"+
  1886. "</iq>");
  1887. // Finally check that the user gets invited.
  1888. expect(sent_stanza.toLocaleString()).toBe( // Strophe adds the xmlns attr (although not in spec)
  1889. "<message from='dummy@localhost/resource' to='"+invitee_jid+"' id='"+sent_id+"' xmlns='jabber:client'>"+
  1890. "<x xmlns='jabber:x:conference' jid='coven@chat.shakespeare.lit' reason='Please join this chat room'/>"+
  1891. "</message>"
  1892. );
  1893. }));
  1894. });
  1895. describe("The affiliations delta", function () {
  1896. it("can be computed in various ways", mock.initConverse(function (_converse) {
  1897. test_utils.openChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'dummy');
  1898. var roomview = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
  1899. var exclude_existing = false;
  1900. var remove_absentees = false;
  1901. var new_list = [];
  1902. var old_list = [];
  1903. var delta = roomview.computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list);
  1904. expect(delta.length).toBe(0);
  1905. new_list = [{'jid': 'wiccarocks@shakespeare.lit', 'affiliation': 'member'}];
  1906. old_list = [{'jid': 'wiccarocks@shakespeare.lit', 'affiliation': 'member'}];
  1907. delta = roomview.computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list);
  1908. expect(delta.length).toBe(0);
  1909. // When remove_absentees is false, then affiliations in the old
  1910. // list which are not in the new one won't be removed.
  1911. old_list = [{'jid': 'oldhag666@shakespeare.lit', 'affiliation': 'owner'},
  1912. {'jid': 'wiccarocks@shakespeare.lit', 'affiliation': 'member'}];
  1913. delta = roomview.computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list);
  1914. expect(delta.length).toBe(0);
  1915. // With exclude_existing set to false, any changed affiliations
  1916. // will be included in the delta (i.e. existing affiliations
  1917. // are included in the comparison).
  1918. old_list = [{'jid': 'wiccarocks@shakespeare.lit', 'affiliation': 'owner'}];
  1919. delta = roomview.computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list);
  1920. expect(delta.length).toBe(1);
  1921. expect(delta[0].jid).toBe('wiccarocks@shakespeare.lit');
  1922. expect(delta[0].affiliation).toBe('member');
  1923. // To also remove affiliations from the old list which are not
  1924. // in the new list, we set remove_absentees to true
  1925. remove_absentees = true;
  1926. old_list = [{'jid': 'oldhag666@shakespeare.lit', 'affiliation': 'owner'},
  1927. {'jid': 'wiccarocks@shakespeare.lit', 'affiliation': 'member'}];
  1928. delta = roomview.computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list);
  1929. expect(delta.length).toBe(1);
  1930. expect(delta[0].jid).toBe('oldhag666@shakespeare.lit');
  1931. expect(delta[0].affiliation).toBe('none');
  1932. delta = roomview.computeAffiliationsDelta(exclude_existing, remove_absentees, [], old_list);
  1933. expect(delta.length).toBe(2);
  1934. expect(delta[0].jid).toBe('oldhag666@shakespeare.lit');
  1935. expect(delta[0].affiliation).toBe('none');
  1936. expect(delta[1].jid).toBe('wiccarocks@shakespeare.lit');
  1937. expect(delta[1].affiliation).toBe('none');
  1938. // To only add a user if they don't already have an
  1939. // affiliation, we set 'exclude_existing' to true
  1940. exclude_existing = true;
  1941. old_list = [{'jid': 'wiccarocks@shakespeare.lit', 'affiliation': 'owner'}];
  1942. delta = roomview.computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list);
  1943. expect(delta.length).toBe(0);
  1944. }));
  1945. });
  1946. });
  1947. }));