chatroom.js 95 KB

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