chatroom.js 196 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549
  1. (function (root, factory) {
  2. define(["jquery", "jasmine", "mock", "converse-core", "test-utils", "utils" ], factory);
  3. } (this, function ($, jasmine, mock, converse, test_utils, utils) {
  4. var _ = converse.env._;
  5. var $pres = converse.env.$pres;
  6. var $iq = converse.env.$iq;
  7. var $msg = converse.env.$msg;
  8. var Strophe = converse.env.Strophe;
  9. var Promise = converse.env.Promise;
  10. var moment = converse.env.moment;
  11. var sizzle = converse.env.sizzle;
  12. var u = converse.env.utils;
  13. return describe("ChatRooms", function () {
  14. describe("The \"rooms\" API", function () {
  15. it("has a method 'close' which closes rooms by JID or all rooms when called with no arguments",
  16. mock.initConverseWithPromises(
  17. null, ['rosterGroupsFetched'], {},
  18. function (done, _converse) {
  19. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy')
  20. .then(() => test_utils.openAndEnterChatRoom(_converse, 'leisure', 'localhost', 'dummy'))
  21. .then(() => test_utils.openAndEnterChatRoom(_converse, 'news', 'localhost', 'dummy'))
  22. .then(() => {
  23. expect(u.isVisible(_converse.chatboxviews.get('lounge@localhost').el)).toBeTruthy();
  24. expect(u.isVisible(_converse.chatboxviews.get('leisure@localhost').el)).toBeTruthy();
  25. expect(u.isVisible(_converse.chatboxviews.get('news@localhost').el)).toBeTruthy();
  26. // XXX: bit of a cheat here. We want `cleanup()` to be
  27. // called on the room. Either it's this or faking
  28. // `sendPresence`.
  29. _converse.connection.connected = false;
  30. _converse.api.rooms.close('lounge@localhost');
  31. expect(_converse.chatboxviews.get('lounge@localhost')).toBeUndefined();
  32. expect(u.isVisible(_converse.chatboxviews.get('leisure@localhost').el)).toBeTruthy();
  33. expect(u.isVisible(_converse.chatboxviews.get('news@localhost').el)).toBeTruthy();
  34. _converse.api.rooms.close(['leisure@localhost', 'news@localhost']);
  35. expect(_converse.chatboxviews.get('lounge@localhost')).toBeUndefined();
  36. expect(_converse.chatboxviews.get('leisure@localhost')).toBeUndefined();
  37. expect(_converse.chatboxviews.get('news@localhost')).toBeUndefined();
  38. return test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  39. })
  40. .then(() => test_utils.openAndEnterChatRoom(_converse, 'leisure', 'localhost', 'dummy'))
  41. .then(() => {
  42. expect(u.isVisible(_converse.chatboxviews.get('lounge@localhost').el)).toBeTruthy();
  43. expect(u.isVisible(_converse.chatboxviews.get('leisure@localhost').el)).toBeTruthy();
  44. _converse.api.rooms.close();
  45. expect(_converse.chatboxviews.get('lounge@localhost')).toBeUndefined();
  46. expect(_converse.chatboxviews.get('leisure@localhost')).toBeUndefined();
  47. done();
  48. }).catch(_.partial(console.error, _));
  49. }));
  50. it("has a method 'get' which returns a wrapped chat room (if it exists)",
  51. mock.initConverseWithPromises(
  52. null, ['rosterGroupsFetched'], {},
  53. function (done, _converse) {
  54. test_utils.createContacts(_converse, 'current');
  55. test_utils.waitUntil(function () {
  56. return $(_converse.rosterview.el).find('.roster-group .group-toggle').length;
  57. }, 300).then(function () {
  58. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
  59. var jid = 'lounge@localhost';
  60. var room = _converse.api.rooms.get(jid);
  61. expect(room instanceof Object).toBeTruthy();
  62. var chatroomview = _converse.chatboxviews.get(jid);
  63. expect(chatroomview.is_chatroom).toBeTruthy();
  64. expect(u.isVisible(chatroomview.el)).toBeTruthy();
  65. chatroomview.close();
  66. // Test with mixed case
  67. test_utils.openAndEnterChatRoom(_converse, 'Leisure', 'localhost', 'dummy').then(function () {
  68. jid = 'Leisure@localhost';
  69. room = _converse.api.rooms.get(jid);
  70. expect(room instanceof Object).toBeTruthy();
  71. chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
  72. expect(u.isVisible(chatroomview.el)).toBeTruthy();
  73. jid = 'leisure@localhost';
  74. room = _converse.api.rooms.get(jid);
  75. expect(room instanceof Object).toBeTruthy();
  76. chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
  77. expect(u.isVisible(chatroomview.el)).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(u.isVisible(chatroomview.el)).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. done();
  89. });
  90. });
  91. });
  92. }));
  93. it("has a method 'open' which opens (optionally configures) and returns a wrapped chat box",
  94. mock.initConverseWithPromises(
  95. null, ['rosterGroupsFetched'], {},
  96. function (done, _converse) {
  97. // Mock 'getRoomFeatures', otherwise the room won't be
  98. // displayed as it waits first for the features to be returned
  99. // (when it's a new room being created).
  100. spyOn(_converse.ChatRoom.prototype, 'getRoomFeatures').and.callFake(function () {
  101. var deferred = new $.Deferred();
  102. deferred.resolve();
  103. return deferred.promise();
  104. });
  105. test_utils.openControlBox();
  106. test_utils.createContacts(_converse, 'current');
  107. test_utils.waitUntil(function () {
  108. return $(_converse.rosterview.el).find('.roster-group .group-toggle').length;
  109. }, 300).then(function () {
  110. var jid = 'lounge@localhost';
  111. var room = _converse.api.rooms.open(jid);
  112. // Test on chat room that's not yet open
  113. expect(room instanceof Object).toBeTruthy();
  114. var chatroomview = _converse.chatboxviews.get(jid);
  115. expect(chatroomview.is_chatroom).toBeTruthy();
  116. expect(u.isVisible(chatroomview.el)).toBeTruthy();
  117. // Test again, now that the room exists.
  118. room = _converse.api.rooms.open(jid);
  119. expect(room instanceof Object).toBeTruthy();
  120. chatroomview = _converse.chatboxviews.get(jid);
  121. expect(chatroomview.is_chatroom).toBeTruthy();
  122. expect(u.isVisible(chatroomview.el)).toBeTruthy();
  123. chatroomview.close();
  124. // Test with mixed case in JID
  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(u.isVisible(chatroomview.el)).toBeTruthy();
  130. jid = 'leisure@localhost';
  131. room = _converse.api.rooms.open(jid);
  132. expect(room instanceof Object).toBeTruthy();
  133. chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
  134. expect(u.isVisible(chatroomview.el)).toBeTruthy();
  135. jid = 'leiSure@localhost';
  136. room = _converse.api.rooms.open(jid);
  137. expect(room instanceof Object).toBeTruthy();
  138. chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
  139. expect(u.isVisible(chatroomview.el)).toBeTruthy();
  140. chatroomview.close();
  141. _converse.muc_instant_rooms = false;
  142. var sent_IQ, IQ_id, sent_IQ_els = [];
  143. var sendIQ = _converse.connection.sendIQ;
  144. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  145. sent_IQ = iq;
  146. sent_IQ_els.push(iq.nodeTree);
  147. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  148. });
  149. // Test with configuration
  150. _converse.api.rooms.open('room@conference.example.org', {
  151. 'nick': 'some1',
  152. 'auto_configure': true,
  153. 'roomconfig': {
  154. 'changesubject': false,
  155. 'membersonly': true,
  156. 'persistentroom': true,
  157. 'publicroom': true,
  158. 'roomdesc': 'Welcome to this room',
  159. 'whois': 'anyone'
  160. }
  161. });
  162. chatroomview = _converse.chatboxviews.get('room@conference.example.org');
  163. // We pretend this is a new room, so no disco info is returned.
  164. var features_stanza = $iq({
  165. from: 'room@conference.example.org',
  166. 'id': IQ_id,
  167. 'to': 'dummy@localhost/desktop',
  168. 'type': 'error'
  169. }).c('error', {'type': 'cancel'})
  170. .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
  171. _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
  172. /* <presence xmlns="jabber:client" to="dummy@localhost/pda" from="room@conference.example.org/yo">
  173. * <x xmlns="http://jabber.org/protocol/muc#user">
  174. * <item affiliation="owner" jid="dummy@localhost/pda" role="moderator"/>
  175. * <status code="110"/>
  176. * <status code="201"/>
  177. * </x>
  178. * </presence>
  179. */
  180. var presence = $pres({
  181. from:'room@conference.example.org/some1',
  182. to:'dummy@localhost/pda'
  183. })
  184. .c('x', {xmlns:'http://jabber.org/protocol/muc#user'})
  185. .c('item', {
  186. affiliation: 'owner',
  187. jid: 'dummy@localhost/pda',
  188. role: 'moderator'
  189. }).up()
  190. .c('status', {code:'110'}).up()
  191. .c('status', {code:'201'});
  192. _converse.connection._dataRecv(test_utils.createRequest(presence));
  193. expect(_converse.connection.sendIQ).toHaveBeenCalled();
  194. expect(sent_IQ.toLocaleString()).toBe(
  195. "<iq to='room@conference.example.org' type='get' xmlns='jabber:client' id='"+IQ_id+
  196. "'><query xmlns='http://jabber.org/protocol/muc#owner'/></iq>"
  197. );
  198. var node = Strophe.xmlHtmlNode(
  199. '<iq xmlns="jabber:client"'+
  200. ' type="result"'+
  201. ' to="dummy@localhost/pda"'+
  202. ' from="room@conference.example.org" id="'+IQ_id+'">'+
  203. ' <query xmlns="http://jabber.org/protocol/muc#owner">'+
  204. ' <x xmlns="jabber:x:data" type="form">'+
  205. ' <title>Configuration for room@conference.example.org</title>'+
  206. ' <instructions>Complete and submit this form to configure the room.</instructions>'+
  207. ' <field var="FORM_TYPE" type="hidden">'+
  208. ' <value>http://jabber.org/protocol/muc#roomconfig</value>'+
  209. ' </field>'+
  210. ' <field type="text-single" var="muc#roomconfig_roomname" label="Name">'+
  211. ' <value>Room</value>'+
  212. ' </field>'+
  213. ' <field type="text-single" var="muc#roomconfig_roomdesc" label="Description"><value/></field>'+
  214. ' <field type="boolean" var="muc#roomconfig_persistentroom" label="Make Room Persistent?"/>'+
  215. ' <field type="boolean" var="muc#roomconfig_publicroom" label="Make Room Publicly Searchable?"><value>1</value></field>'+
  216. ' <field type="boolean" var="muc#roomconfig_changesubject" label="Allow Occupants to Change Subject?"/>'+
  217. ' <field type="list-single" var="muc#roomconfig_whois" label="Who May Discover Real JIDs?"><option label="Moderators Only">'+
  218. ' <value>moderators</value></option><option label="Anyone"><value>anyone</value></option>'+
  219. ' </field>'+
  220. ' <field type="text-private" var="muc#roomconfig_roomsecret" label="Password"><value/></field>'+
  221. ' <field type="boolean" var="muc#roomconfig_moderatedroom" label="Make Room Moderated?"/>'+
  222. ' <field type="boolean" var="muc#roomconfig_membersonly" label="Make Room Members-Only?"/>'+
  223. ' <field type="text-single" var="muc#roomconfig_historylength" label="Maximum Number of History Messages Returned by Room">'+
  224. ' <value>20</value></field>'+
  225. ' </x>'+
  226. ' </query>'+
  227. ' </iq>');
  228. spyOn(chatroomview.model, 'sendConfiguration').and.callThrough();
  229. _converse.connection._dataRecv(test_utils.createRequest(node.firstElementChild));
  230. return test_utils.waitUntil(function () {
  231. return chatroomview.model.sendConfiguration.calls.count() === 1;
  232. }, 300).then(function () {
  233. var sent_stanza = sent_IQ_els.pop();
  234. while (sent_stanza.getAttribute('type') !== 'set') {
  235. sent_stanza = sent_IQ_els.pop();
  236. }
  237. expect(sizzle('field[var="muc#roomconfig_roomname"] value', sent_stanza).pop().textContent).toBe('Room');
  238. expect(sizzle('field[var="muc#roomconfig_roomdesc"] value', sent_stanza).pop().textContent).toBe('Welcome to this room');
  239. expect(sizzle('field[var="muc#roomconfig_persistentroom"] value', sent_stanza).pop().textContent).toBe('1');
  240. expect(sizzle('field[var="muc#roomconfig_publicroom"] value ', sent_stanza).pop().textContent).toBe('1');
  241. expect(sizzle('field[var="muc#roomconfig_changesubject"] value', sent_stanza).pop().textContent).toBe('0');
  242. expect(sizzle('field[var="muc#roomconfig_whois"] value ', sent_stanza).pop().textContent).toBe('anyone');
  243. expect(sizzle('field[var="muc#roomconfig_membersonly"] value', sent_stanza).pop().textContent).toBe('1');
  244. expect(sizzle('field[var="muc#roomconfig_historylength"] value', sent_stanza).pop().textContent).toBe('20');
  245. done();
  246. });
  247. });
  248. }));
  249. });
  250. describe("An instant chat room", function () {
  251. it("will be created when muc_instant_rooms is set to true",
  252. mock.initConverseWithPromises(
  253. null, ['rosterGroupsFetched'], {},
  254. function (done, _converse) {
  255. var sent_IQ, IQ_id;
  256. var sendIQ = _converse.connection.sendIQ;
  257. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  258. if (iq.nodeTree.getAttribute('to') === 'lounge@localhost') {
  259. sent_IQ = iq;
  260. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  261. } else {
  262. sendIQ.bind(this)(iq, callback, errback);
  263. }
  264. });
  265. test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  266. // We pretend this is a new room, so no disco info is returned.
  267. //
  268. /* <iq from="jordie.langen@chat.example.org/converse.js-11659299" to="myroom@conference.chat.example.org" type="get">
  269. * <query xmlns="http://jabber.org/protocol/disco#info"/>
  270. * </iq>
  271. * <iq xmlns="jabber:client" type="error" to="jordie.langen@chat.example.org/converse.js-11659299" from="myroom@conference.chat.example.org">
  272. * <error type="cancel">
  273. * <item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
  274. * </error>
  275. * </iq>
  276. */
  277. var features_stanza = $iq({
  278. 'from': 'lounge@localhost',
  279. 'id': IQ_id,
  280. 'to': 'dummy@localhost/desktop',
  281. 'type': 'error'
  282. }).c('error', {'type': 'cancel'})
  283. .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
  284. _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
  285. var view = _converse.chatboxviews.get('lounge@localhost');
  286. spyOn(view, 'join').and.callThrough();
  287. spyOn(view, 'submitNickname').and.callThrough();
  288. /* <iq to="myroom@conference.chat.example.org"
  289. * from="jordie.langen@chat.example.org/converse.js-11659299"
  290. * type="get">
  291. * <query xmlns="http://jabber.org/protocol/disco#info"
  292. * node="x-roomuser-item"/>
  293. * </iq>
  294. */
  295. test_utils.waitUntil(function () {
  296. return sent_IQ.toLocaleString() ===
  297. "<iq to='lounge@localhost' from='dummy@localhost/resource' "+
  298. "type='get' xmlns='jabber:client' id='"+IQ_id+"'>"+
  299. "<query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item'/></iq>"
  300. }, 300).then(function () {
  301. /* <iq xmlns="jabber:client" type="error" to="jordie.langen@chat.example.org/converse.js-11659299" from="myroom@conference.chat.example.org">
  302. * <error type="cancel">
  303. * <item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
  304. * </error>
  305. * </iq>
  306. */
  307. var stanza = $iq({
  308. 'type': 'error',
  309. 'id': IQ_id,
  310. 'from': view.model.get('jid'),
  311. 'to': _converse.connection.jid
  312. }).c('error', {'type': 'cancel'})
  313. .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
  314. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  315. var input = view.el.querySelector('input[name="nick"]');
  316. input.value = 'nicky';
  317. view.el.querySelector('input[type=submit]').click();
  318. expect(view.submitNickname).toHaveBeenCalled();
  319. expect(view.join).toHaveBeenCalled();
  320. // The user has just entered the room (because join was called)
  321. // and receives their own presence from the server.
  322. // See example 24:
  323. // http://xmpp.org/extensions/xep-0045.html#enter-pres
  324. //
  325. /* <presence xmlns="jabber:client" to="jordie.langen@chat.example.org/converse.js-11659299" from="myroom@conference.chat.example.org/jc">
  326. * <x xmlns="http://jabber.org/protocol/muc#user">
  327. * <item jid="jordie.langen@chat.example.org/converse.js-11659299" affiliation="owner" role="moderator"/>
  328. * <status code="110"/>
  329. * <status code="201"/>
  330. * </x>
  331. * </presence>
  332. */
  333. var presence = $pres({
  334. to:'dummy@localhost/resource',
  335. from:'lounge@localhost/thirdwitch',
  336. id:'5025e055-036c-4bc5-a227-706e7e352053'
  337. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  338. .c('item').attrs({
  339. affiliation: 'owner',
  340. jid: 'dummy@localhost/resource',
  341. role: 'moderator'
  342. }).up()
  343. .c('status').attrs({code:'110'}).up()
  344. .c('status').attrs({code:'201'}).nodeTree;
  345. _converse.connection._dataRecv(test_utils.createRequest(presence));
  346. var info_text = view.el.querySelector('.chat-content .chat-info').textContent;
  347. expect(info_text).toBe('A new room has been created');
  348. // An instant room is created by saving the default configuratoin.
  349. //
  350. /* <iq to="myroom@conference.chat.example.org" type="set" xmlns="jabber:client" id="5025e055-036c-4bc5-a227-706e7e352053:sendIQ">
  351. * <query xmlns="http://jabber.org/protocol/muc#owner"><x xmlns="jabber:x:data" type="submit"/></query>
  352. * </iq>
  353. */
  354. expect(sent_IQ.toLocaleString()).toBe(
  355. "<iq to='lounge@localhost' type='set' xmlns='jabber:client' id='"+IQ_id+"'>"+
  356. "<query xmlns='http://jabber.org/protocol/muc#owner'><x xmlns='jabber:x:data' type='submit'/>"+
  357. "</query></iq>");
  358. done();
  359. });
  360. }));
  361. });
  362. describe("A Chat Room", function () {
  363. it("shows join/leave messages when users enter or exit a room",
  364. mock.initConverseWithPromises(
  365. null, ['rosterGroupsFetched'], {},
  366. function (done, _converse) {
  367. test_utils.openChatRoom(_converse, "coven", 'chat.shakespeare.lit', 'some1');
  368. var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
  369. var $chat_content = $(view.el).find('.chat-content');
  370. /* We don't show join/leave messages for existing occupants. We
  371. * know about them because we receive their presences before we
  372. * receive our own.
  373. */
  374. var presence = $pres({
  375. to: 'dummy@localhost/_converse.js-29092160',
  376. from: 'coven@chat.shakespeare.lit/oldguy'
  377. }).c('x', {xmlns: Strophe.NS.MUC_USER})
  378. .c('item', {
  379. 'affiliation': 'none',
  380. 'jid': 'oldguy@localhost/_converse.js-290929789',
  381. 'role': 'participant'
  382. });
  383. _converse.connection._dataRecv(test_utils.createRequest(presence));
  384. expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(0);
  385. /* <presence to="dummy@localhost/_converse.js-29092160"
  386. * from="coven@chat.shakespeare.lit/some1">
  387. * <x xmlns="http://jabber.org/protocol/muc#user">
  388. * <item affiliation="owner" jid="dummy@localhost/_converse.js-29092160" role="moderator"/>
  389. * <status code="110"/>
  390. * </x>
  391. * </presence></body>
  392. */
  393. presence = $pres({
  394. to: 'dummy@localhost/_converse.js-29092160',
  395. from: 'coven@chat.shakespeare.lit/some1'
  396. }).c('x', {xmlns: Strophe.NS.MUC_USER})
  397. .c('item', {
  398. 'affiliation': 'owner',
  399. 'jid': 'dummy@localhost/_converse.js-29092160',
  400. 'role': 'moderator'
  401. }).up()
  402. .c('status', {code: '110'});
  403. _converse.connection._dataRecv(test_utils.createRequest(presence));
  404. expect($chat_content.find('div.chat-info:first').html()).toBe("some1 has entered the room");
  405. presence = $pres({
  406. to: 'dummy@localhost/_converse.js-29092160',
  407. from: 'coven@chat.shakespeare.lit/newguy'
  408. })
  409. .c('x', {xmlns: Strophe.NS.MUC_USER})
  410. .c('item', {
  411. 'affiliation': 'none',
  412. 'jid': 'newguy@localhost/_converse.js-290929789',
  413. 'role': 'participant'
  414. });
  415. _converse.connection._dataRecv(test_utils.createRequest(presence));
  416. expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(2);
  417. expect($chat_content.find('div.chat-info:last').html()).toBe("newguy has entered the room");
  418. // Add another entrant, otherwise the above message will be
  419. // collapsed if "newguy" leaves immediately again
  420. presence = $pres({
  421. to: 'dummy@localhost/_converse.js-29092160',
  422. from: 'coven@chat.shakespeare.lit/newgirl'
  423. })
  424. .c('x', {xmlns: Strophe.NS.MUC_USER})
  425. .c('item', {
  426. 'affiliation': 'none',
  427. 'jid': 'newgirl@localhost/_converse.js-213098781',
  428. 'role': 'participant'
  429. });
  430. _converse.connection._dataRecv(test_utils.createRequest(presence));
  431. expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(3);
  432. expect($chat_content.find('div.chat-info:last').html()).toBe("newgirl has entered the room");
  433. // Don't show duplicate join messages
  434. presence = $pres({
  435. to: 'dummy@localhost/_converse.js-290918392',
  436. from: 'coven@chat.shakespeare.lit/newguy'
  437. }).c('x', {xmlns: Strophe.NS.MUC_USER})
  438. .c('item', {
  439. 'affiliation': 'none',
  440. 'jid': 'newguy@localhost/_converse.js-290929789',
  441. 'role': 'participant'
  442. });
  443. _converse.connection._dataRecv(test_utils.createRequest(presence));
  444. expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(3);
  445. /* <presence
  446. * from='coven@chat.shakespeare.lit/thirdwitch'
  447. * to='crone1@shakespeare.lit/desktop'
  448. * type='unavailable'>
  449. * <status>Disconnected: Replaced by new connection</status>
  450. * <x xmlns='http://jabber.org/protocol/muc#user'>
  451. * <item affiliation='member'
  452. * jid='hag66@shakespeare.lit/pda'
  453. * role='none'/>
  454. * </x>
  455. * </presence>
  456. */
  457. presence = $pres({
  458. to: 'dummy@localhost/_converse.js-29092160',
  459. type: 'unavailable',
  460. from: 'coven@chat.shakespeare.lit/newguy'
  461. })
  462. .c('status', 'Disconnected: Replaced by new connection').up()
  463. .c('x', {xmlns: Strophe.NS.MUC_USER})
  464. .c('item', {
  465. 'affiliation': 'none',
  466. 'jid': 'newguy@localhost/_converse.js-290929789',
  467. 'role': 'none'
  468. });
  469. _converse.connection._dataRecv(test_utils.createRequest(presence));
  470. expect($chat_content.find('div.chat-info').length).toBe(4);
  471. expect($chat_content.find('div.chat-info:last').html()).toBe(
  472. 'newguy has left the room. '+
  473. '"Disconnected: Replaced by new connection"');
  474. // When the user immediately joins again, we collapse the
  475. // multiple join/leave messages.
  476. presence = $pres({
  477. to: 'dummy@localhost/_converse.js-29092160',
  478. from: 'coven@chat.shakespeare.lit/newguy'
  479. }).c('x', {xmlns: Strophe.NS.MUC_USER})
  480. .c('item', {
  481. 'affiliation': 'none',
  482. 'jid': 'newguy@localhost/_converse.js-290929789',
  483. 'role': 'participant'
  484. });
  485. _converse.connection._dataRecv(test_utils.createRequest(presence));
  486. expect($chat_content.find('div.chat-info').length).toBe(4);
  487. var $msg_el = $chat_content.find('div.chat-info:last');
  488. expect($msg_el.html()).toBe("newguy has left and re-entered the room");
  489. expect($msg_el.data('leavejoin')).toBe('"newguy"');
  490. presence = $pres({
  491. to: 'dummy@localhost/_converse.js-29092160',
  492. type: 'unavailable',
  493. from: 'coven@chat.shakespeare.lit/newguy'
  494. })
  495. .c('x', {xmlns: Strophe.NS.MUC_USER})
  496. .c('item', {
  497. 'affiliation': 'none',
  498. 'jid': 'newguy@localhost/_converse.js-290929789',
  499. 'role': 'none'
  500. });
  501. _converse.connection._dataRecv(test_utils.createRequest(presence));
  502. expect($chat_content.find('div.chat-info').length).toBe(4);
  503. $msg_el = $chat_content.find('div.chat-info:last');
  504. expect($msg_el.html()).toBe('newguy has left the room');
  505. expect($msg_el.data('leave')).toBe('"newguy"');
  506. presence = $pres({
  507. to: 'dummy@localhost/_converse.js-29092160',
  508. from: 'coven@chat.shakespeare.lit/nomorenicks'
  509. })
  510. .c('x', {xmlns: Strophe.NS.MUC_USER})
  511. .c('item', {
  512. 'affiliation': 'none',
  513. 'jid': 'nomorenicks@localhost/_converse.js-290929789',
  514. 'role': 'participant'
  515. });
  516. _converse.connection._dataRecv(test_utils.createRequest(presence));
  517. expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(5);
  518. expect($chat_content.find('div.chat-info:last').html()).toBe("nomorenicks has entered the room");
  519. presence = $pres({
  520. to: 'dummy@localhost/_converse.js-290918392',
  521. type: 'unavailable',
  522. from: 'coven@chat.shakespeare.lit/nomorenicks'
  523. }).c('x', {xmlns: Strophe.NS.MUC_USER})
  524. .c('item', {
  525. 'affiliation': 'none',
  526. 'jid': 'nomorenicks@localhost/_converse.js-290929789',
  527. 'role': 'none'
  528. });
  529. _converse.connection._dataRecv(test_utils.createRequest(presence));
  530. expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(5);
  531. expect($chat_content.find('div.chat-info:last').html()).toBe("nomorenicks has entered and left the room");
  532. presence = $pres({
  533. to: 'dummy@localhost/_converse.js-29092160',
  534. from: 'coven@chat.shakespeare.lit/nomorenicks'
  535. })
  536. .c('x', {xmlns: Strophe.NS.MUC_USER})
  537. .c('item', {
  538. 'affiliation': 'none',
  539. 'jid': 'nomorenicks@localhost/_converse.js-290929789',
  540. 'role': 'participant'
  541. });
  542. _converse.connection._dataRecv(test_utils.createRequest(presence));
  543. expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(5);
  544. expect($chat_content.find('div.chat-info:last').html()).toBe("nomorenicks has entered the room");
  545. done();
  546. }));
  547. it("shows a new day indicator if a join/leave message is received on a new day",
  548. mock.initConverseWithPromises(
  549. null, ['rosterGroupsFetched'], {},
  550. function (done, _converse) {
  551. test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'dummy').then(function () {
  552. var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
  553. var chat_content = view.el.querySelector('.chat-content');
  554. var $chat_content = $(chat_content);
  555. var indicator = chat_content.querySelector('.date-separator');
  556. expect(indicator).not.toBe(null);
  557. expect(indicator.getAttribute('class')).toEqual('message date-separator');
  558. expect(indicator.getAttribute('data-isodate')).toEqual(moment().startOf('day').format());
  559. expect(indicator.querySelector('time').textContent).toEqual(moment().startOf('day').format("dddd MMM Do YYYY"));
  560. expect(chat_content.querySelectorAll('div.chat-info').length).toBe(1);
  561. expect(chat_content.querySelector('div.chat-info').textContent).toBe(
  562. "dummy has entered the room"
  563. );
  564. var baseTime = new Date();
  565. jasmine.clock().install();
  566. jasmine.clock().mockDate(baseTime);
  567. var ONE_DAY_LATER = 86400000;
  568. jasmine.clock().tick(ONE_DAY_LATER);
  569. /* <presence to="dummy@localhost/_converse.js-29092160"
  570. * from="coven@chat.shakespeare.lit/some1">
  571. * <x xmlns="http://jabber.org/protocol/muc#user">
  572. * <item affiliation="owner" jid="dummy@localhost/_converse.js-29092160" role="moderator"/>
  573. * <status code="110"/>
  574. * </x>
  575. * </presence></body>
  576. */
  577. var presence = $pres({
  578. to: 'dummy@localhost/_converse.js-29092160',
  579. from: 'coven@chat.shakespeare.lit/some1'
  580. }).c('x', {xmlns: Strophe.NS.MUC_USER})
  581. .c('item', {
  582. 'affiliation': 'owner',
  583. 'jid': 'some1@localhost/_converse.js-290929789',
  584. 'role': 'moderator'
  585. });
  586. _converse.connection._dataRecv(test_utils.createRequest(presence));
  587. indicator = chat_content.querySelector('.date-separator[data-isodate="'+moment().startOf('day').format()+'"]');
  588. expect(indicator).not.toBe(null);
  589. expect(indicator.getAttribute('class')).toEqual('message date-separator');
  590. expect(indicator.getAttribute('data-isodate')).toEqual(moment().startOf('day').format());
  591. expect(indicator.querySelector('time').getAttribute('class')).toEqual('separator-text');
  592. expect(indicator.querySelector('time').textContent).toEqual(moment().startOf('day').format("dddd MMM Do YYYY"));
  593. expect(chat_content.querySelector('div.chat-info:last-child').textContent).toBe(
  594. "some1 has entered the room"
  595. );
  596. jasmine.clock().tick(ONE_DAY_LATER);
  597. // Test a user leaving a chat room
  598. presence = $pres({
  599. to: 'dummy@localhost/_converse.js-29092160',
  600. type: 'unavailable',
  601. from: 'coven@chat.shakespeare.lit/some1'
  602. })
  603. .c('status', 'Disconnected: Replaced by new connection').up()
  604. .c('x', {xmlns: Strophe.NS.MUC_USER})
  605. .c('item', {
  606. 'affiliation': 'none',
  607. 'jid': 'some1@localhost/_converse.js-290929789',
  608. 'role': 'none'
  609. });
  610. _converse.connection._dataRecv(test_utils.createRequest(presence));
  611. indicator = chat_content.querySelector('.date-separator[data-isodate="'+moment().startOf('day').format()+'"]');
  612. expect(indicator).not.toBe(null);
  613. expect(indicator.getAttribute('class')).toEqual('message date-separator');
  614. expect(indicator.getAttribute('data-isodate')).toEqual(moment().startOf('day').format());
  615. expect(indicator.querySelector('time').textContent).toEqual(moment().startOf('day').format("dddd MMM Do YYYY"));
  616. expect($(chat_content).find('div.chat-info').length).toBe(4);
  617. expect($(chat_content).find('div.chat-info:last').html()).toBe(
  618. 'some1 has left the room. '+
  619. '"Disconnected: Replaced by new connection"');
  620. jasmine.clock().tick(ONE_DAY_LATER);
  621. var stanza = Strophe.xmlHtmlNode(
  622. '<message xmlns="jabber:client"' +
  623. ' to="dummy@localhost/_converse.js-290929789"' +
  624. ' type="groupchat"' +
  625. ' from="coven@chat.shakespeare.lit/some1">'+
  626. ' <body>hello world</body>'+
  627. ' <delay xmlns="urn:xmpp:delay" stamp="'+moment().format()+'" from="some1@localhost"/>'+
  628. '</message>').firstChild;
  629. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  630. presence = $pres({
  631. to: 'dummy@localhost/_converse.js-29092160',
  632. from: 'coven@chat.shakespeare.lit/newguy'
  633. }).c('x', {xmlns: Strophe.NS.MUC_USER})
  634. .c('item', {
  635. 'affiliation': 'none',
  636. 'jid': 'newguy@localhost/_converse.js-290929789',
  637. 'role': 'participant'
  638. });
  639. _converse.connection._dataRecv(test_utils.createRequest(presence));
  640. var $time = $chat_content.find('time');
  641. expect($time.length).toEqual(4);
  642. var $indicator = $chat_content.find('.date-separator:eq(3)');
  643. expect($indicator.attr('class')).toEqual('message date-separator');
  644. expect($indicator.data('isodate')).toEqual(moment().startOf('day').format());
  645. expect($indicator.find('time').text()).toEqual(moment().startOf('day').format("dddd MMM Do YYYY"));
  646. expect($chat_content.find('div.chat-info').length).toBe(5);
  647. expect($chat_content.find('div.chat-info:last').html()).toBe("newguy has entered the room");
  648. jasmine.clock().tick(ONE_DAY_LATER);
  649. stanza = Strophe.xmlHtmlNode(
  650. '<message xmlns="jabber:client"' +
  651. ' to="dummy@localhost/_converse.js-290929789"' +
  652. ' type="groupchat"' +
  653. ' from="coven@chat.shakespeare.lit/some1">'+
  654. ' <body>hello world</body>'+
  655. ' <delay xmlns="urn:xmpp:delay" stamp="'+moment().format()+'" from="some1@localhost"/>'+
  656. '</message>').firstChild;
  657. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  658. jasmine.clock().tick(ONE_DAY_LATER);
  659. // Test a user leaving a chat room
  660. presence = $pres({
  661. to: 'dummy@localhost/_converse.js-29092160',
  662. type: 'unavailable',
  663. from: 'coven@chat.shakespeare.lit/newguy'
  664. })
  665. .c('status', 'Disconnected: Replaced by new connection').up()
  666. .c('x', {xmlns: Strophe.NS.MUC_USER})
  667. .c('item', {
  668. 'affiliation': 'none',
  669. 'jid': 'newguy@localhost/_converse.js-290929789',
  670. 'role': 'none'
  671. });
  672. _converse.connection._dataRecv(test_utils.createRequest(presence));
  673. $time = $chat_content.find('time');
  674. expect($time.length).toEqual(6);
  675. $indicator = $chat_content.find('.date-separator:eq(5)');
  676. expect($indicator.attr('class')).toEqual('message date-separator');
  677. expect($indicator.data('isodate')).toEqual(moment().startOf('day').format());
  678. expect($indicator.find('time').text()).toEqual(moment().startOf('day').format("dddd MMM Do YYYY"));
  679. expect($chat_content.find('div.chat-info').length).toBe(6);
  680. expect($chat_content.find('div.chat-info:last').html()).toBe(
  681. 'newguy has left the room. '+
  682. '"Disconnected: Replaced by new connection"');
  683. jasmine.clock().uninstall();
  684. done();
  685. return;
  686. });
  687. }));
  688. it("shows its description in the chat heading",
  689. mock.initConverseWithPromises(
  690. null, ['rosterGroupsFetched'], {},
  691. function (done, _converse) {
  692. var sent_IQ, IQ_id;
  693. var sendIQ = _converse.connection.sendIQ;
  694. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  695. sent_IQ = iq;
  696. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  697. });
  698. _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'});
  699. var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
  700. var features_stanza = $iq({
  701. from: 'coven@chat.shakespeare.lit',
  702. 'id': IQ_id,
  703. 'to': 'dummy@localhost/desktop',
  704. 'type': 'result'
  705. })
  706. .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'})
  707. .c('identity', {
  708. 'category': 'conference',
  709. 'name': 'A Dark Cave',
  710. 'type': 'text'
  711. }).up()
  712. .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up()
  713. .c('feature', {'var': 'muc_passwordprotected'}).up()
  714. .c('feature', {'var': 'muc_hidden'}).up()
  715. .c('feature', {'var': 'muc_temporary'}).up()
  716. .c('feature', {'var': 'muc_open'}).up()
  717. .c('feature', {'var': 'muc_unmoderated'}).up()
  718. .c('feature', {'var': 'muc_nonanonymous'}).up()
  719. .c('feature', {'var': 'urn:xmpp:mam:0'}).up()
  720. .c('x', { 'xmlns':'jabber:x:data', 'type':'result'})
  721. .c('field', {'var':'FORM_TYPE', 'type':'hidden'})
  722. .c('value').t('http://jabber.org/protocol/muc#roominfo').up().up()
  723. .c('field', {'type':'text-single', 'var':'muc#roominfo_description', 'label':'Description'})
  724. .c('value').t('This is the description').up().up()
  725. .c('field', {'type':'text-single', 'var':'muc#roominfo_occupants', 'label':'Number of occupants'})
  726. .c('value').t(0);
  727. _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
  728. test_utils.waitUntil(function () {
  729. return _.get(view.el.querySelector('.chatroom-description'), 'textContent');
  730. }).then(function () {
  731. expect($(view.el.querySelector('.chatroom-description')).text()).toBe('This is the description');
  732. done();
  733. });
  734. }));
  735. it("will specially mark messages in which you are mentioned",
  736. mock.initConverseWithPromises(
  737. null, ['rosterGroupsFetched'], {},
  738. function (done, _converse) {
  739. test_utils.createContacts(_converse, 'current');
  740. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
  741. var view = _converse.chatboxviews.get('lounge@localhost');
  742. if (!$(view.el).find('.chat-area').length) { view.renderChatArea(); }
  743. var message = 'dummy: Your attention is required';
  744. var nick = mock.chatroom_names[0],
  745. msg = $msg({
  746. from: 'lounge@localhost/'+nick,
  747. id: (new Date()).getTime(),
  748. to: 'dummy@localhost',
  749. type: 'groupchat'
  750. }).c('body').t(message).tree();
  751. view.model.onMessage(msg);
  752. expect($(view.el).find('.chat-msg').hasClass('mentioned')).toBeTruthy();
  753. done();
  754. });
  755. }));
  756. it("supports the /me command",
  757. mock.initConverseWithPromises(
  758. null, ['rosterGroupsFetched'], {},
  759. function (done, _converse) {
  760. test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp'])
  761. .then(function () {
  762. return test_utils.waitUntil(function () {
  763. return _converse.xmppstatus.get('fullname');
  764. }, 300);
  765. }).then(function () {
  766. test_utils.createContacts(_converse, 'current');
  767. return test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  768. }).then(function () {
  769. var view = _converse.chatboxviews.get('lounge@localhost');
  770. if (!$(view.el).find('.chat-area').length) { view.renderChatArea(); }
  771. var message = '/me is tired';
  772. var nick = mock.chatroom_names[0],
  773. msg = $msg({
  774. 'from': 'lounge@localhost/'+nick,
  775. 'id': (new Date()).getTime(),
  776. 'to': 'dummy@localhost',
  777. 'type': 'groupchat'
  778. }).c('body').t(message).tree();
  779. view.model.onMessage(msg);
  780. expect(_.includes($(view.el).find('.chat-msg-author').text(), '**Dyon van de Wege')).toBeTruthy();
  781. expect($(view.el).find('.chat-msg-text').text()).toBe(' is tired');
  782. message = '/me is as well';
  783. msg = $msg({
  784. from: 'lounge@localhost/dummy',
  785. id: (new Date()).getTime(),
  786. to: 'dummy@localhost',
  787. type: 'groupchat'
  788. }).c('body').t(message).tree();
  789. view.model.onMessage(msg);
  790. expect(_.includes($(view.el).find('.chat-msg-author:last').text(), '**Max Mustermann')).toBeTruthy();
  791. expect($(view.el).find('.chat-msg-text:last').text()).toBe(' is as well');
  792. done();
  793. });
  794. }));
  795. it("can be configured if you're its owner",
  796. mock.initConverseWithPromises(
  797. null, ['rosterGroupsFetched'], {},
  798. function (done, _converse) {
  799. var view;
  800. var sent_IQ, IQ_id;
  801. var sendIQ = _converse.connection.sendIQ;
  802. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  803. sent_IQ = iq;
  804. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  805. });
  806. _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'});
  807. view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
  808. spyOn(view.model, 'saveAffiliationAndRole').and.callThrough();
  809. // We pretend this is a new room, so no disco info is returned.
  810. var features_stanza = $iq({
  811. from: 'coven@chat.shakespeare.lit',
  812. 'id': IQ_id,
  813. 'to': 'dummy@localhost/desktop',
  814. 'type': 'error'
  815. }).c('error', {'type': 'cancel'})
  816. .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
  817. _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
  818. /* <presence to="dummy@localhost/_converse.js-29092160"
  819. * from="coven@chat.shakespeare.lit/some1">
  820. * <x xmlns="http://jabber.org/protocol/muc#user">
  821. * <item affiliation="owner" jid="dummy@localhost/_converse.js-29092160" role="moderator"/>
  822. * <status code="110"/>
  823. * </x>
  824. * </presence></body>
  825. */
  826. var presence = $pres({
  827. to: 'dummy@localhost/_converse.js-29092160',
  828. from: 'coven@chat.shakespeare.lit/some1'
  829. }).c('x', {xmlns: Strophe.NS.MUC_USER})
  830. .c('item', {
  831. 'affiliation': 'owner',
  832. 'jid': 'dummy@localhost/_converse.js-29092160',
  833. 'role': 'moderator'
  834. }).up()
  835. .c('status', {code: '110'});
  836. _converse.connection._dataRecv(test_utils.createRequest(presence));
  837. expect(view.model.saveAffiliationAndRole).toHaveBeenCalled();
  838. expect($(view.el.querySelector('.toggle-chatbox-button')).is(':visible')).toBeTruthy();
  839. test_utils.waitUntil(function () {
  840. return !_.isNull(view.el.querySelector('.configure-chatroom-button'));
  841. }, 300).then(function () {
  842. expect($(view.el.querySelector('.configure-chatroom-button')).is(':visible')).toBeTruthy();
  843. view.el.querySelector('.configure-chatroom-button').click();
  844. /* Check that an IQ is sent out, asking for the
  845. * configuration form.
  846. * See: // http://xmpp.org/extensions/xep-0045.html#example-163
  847. *
  848. * <iq from='crone1@shakespeare.lit/desktop'
  849. * id='config1'
  850. * to='coven@chat.shakespeare.lit'
  851. * type='get'>
  852. * <query xmlns='http://jabber.org/protocol/muc#owner'/>
  853. * </iq>
  854. */
  855. expect(sent_IQ.toLocaleString()).toBe(
  856. "<iq to='coven@chat.shakespeare.lit' type='get' xmlns='jabber:client' id='"+IQ_id+"'>"+
  857. "<query xmlns='http://jabber.org/protocol/muc#owner'/>"+
  858. "</iq>");
  859. /* Server responds with the configuration form.
  860. * See: // http://xmpp.org/extensions/xep-0045.html#example-165
  861. */
  862. var config_stanza = $iq({from: 'coven@chat.shakespeare.lit',
  863. 'id': IQ_id,
  864. 'to': 'dummy@localhost/desktop',
  865. 'type': 'result'})
  866. .c('query', { 'xmlns': 'http://jabber.org/protocol/muc#owner'})
  867. .c('x', { 'xmlns': 'jabber:x:data', 'type': 'form'})
  868. .c('title').t('Configuration for "coven" Room').up()
  869. .c('instructions').t('Complete this form to modify the configuration of your room.').up()
  870. .c('field', {'type': 'hidden', 'var': 'FORM_TYPE'})
  871. .c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up()
  872. .c('field', {
  873. 'label': 'Natural-Language Room Name',
  874. 'type': 'text-single',
  875. 'var': 'muc#roomconfig_roomname'})
  876. .c('value').t('A Dark Cave').up().up()
  877. .c('field', {
  878. 'label': 'Short Description of Room',
  879. 'type': 'text-single',
  880. 'var': 'muc#roomconfig_roomdesc'})
  881. .c('value').t('The place for all good witches!').up().up()
  882. .c('field', {
  883. 'label': 'Enable Public Logging?',
  884. 'type': 'boolean',
  885. 'var': 'muc#roomconfig_enablelogging'})
  886. .c('value').t(0).up().up()
  887. .c('field', {
  888. 'label': 'Allow Occupants to Change Subject?',
  889. 'type': 'boolean',
  890. 'var': 'muc#roomconfig_changesubject'})
  891. .c('value').t(0).up().up()
  892. .c('field', {
  893. 'label': 'Allow Occupants to Invite Others?',
  894. 'type': 'boolean',
  895. 'var': 'muc#roomconfig_allowinvites'})
  896. .c('value').t(0).up().up()
  897. .c('field', {
  898. 'label': 'Who Can Send Private Messages?',
  899. 'type': 'list-single',
  900. 'var': 'muc#roomconfig_allowpm'})
  901. .c('value').t('anyone').up()
  902. .c('option', {'label': 'Anyone'})
  903. .c('value').t('anyone').up().up()
  904. .c('option', {'label': 'Anyone with Voice'})
  905. .c('value').t('participants').up().up()
  906. .c('option', {'label': 'Moderators Only'})
  907. .c('value').t('moderators').up().up()
  908. .c('option', {'label': 'Nobody'})
  909. .c('value').t('none').up().up().up()
  910. .c('field', {
  911. 'label': 'Roles for which Presence is Broadcasted',
  912. 'type': 'list-multi',
  913. 'var': 'muc#roomconfig_presencebroadcast'})
  914. .c('value').t('moderator').up()
  915. .c('value').t('participant').up()
  916. .c('value').t('visitor').up()
  917. .c('option', {'label': 'Moderator'})
  918. .c('value').t('moderator').up().up()
  919. .c('option', {'label': 'Participant'})
  920. .c('value').t('participant').up().up()
  921. .c('option', {'label': 'Visitor'})
  922. .c('value').t('visitor').up().up().up()
  923. .c('field', {
  924. 'label': 'Roles and Affiliations that May Retrieve Member List',
  925. 'type': 'list-multi',
  926. 'var': 'muc#roomconfig_getmemberlist'})
  927. .c('value').t('moderator').up()
  928. .c('value').t('participant').up()
  929. .c('value').t('visitor').up()
  930. .c('option', {'label': 'Moderator'})
  931. .c('value').t('moderator').up().up()
  932. .c('option', {'label': 'Participant'})
  933. .c('value').t('participant').up().up()
  934. .c('option', {'label': 'Visitor'})
  935. .c('value').t('visitor').up().up().up()
  936. .c('field', {
  937. 'label': 'Make Room Publicly Searchable?',
  938. 'type': 'boolean',
  939. 'var': 'muc#roomconfig_publicroom'})
  940. .c('value').t(0).up().up()
  941. .c('field', {
  942. 'label': 'Make Room Publicly Searchable?',
  943. 'type': 'boolean',
  944. 'var': 'muc#roomconfig_publicroom'})
  945. .c('value').t(0).up().up()
  946. .c('field', {
  947. 'label': 'Make Room Persistent?',
  948. 'type': 'boolean',
  949. 'var': 'muc#roomconfig_persistentroom'})
  950. .c('value').t(0).up().up()
  951. .c('field', {
  952. 'label': 'Make Room Moderated?',
  953. 'type': 'boolean',
  954. 'var': 'muc#roomconfig_moderatedroom'})
  955. .c('value').t(0).up().up()
  956. .c('field', {
  957. 'label': 'Make Room Members Only?',
  958. 'type': 'boolean',
  959. 'var': 'muc#roomconfig_membersonly'})
  960. .c('value').t(0).up().up()
  961. .c('field', {
  962. 'label': 'Password Required for Entry?',
  963. 'type': 'boolean',
  964. 'var': 'muc#roomconfig_passwordprotectedroom'})
  965. .c('value').t(1).up().up()
  966. .c('field', {'type': 'fixed'})
  967. .c('value').t('If a password is required to enter this room,'+
  968. 'you must specify the password below.').up().up()
  969. .c('field', {
  970. 'label': 'Password',
  971. 'type': 'text-private',
  972. 'var': 'muc#roomconfig_roomsecret'})
  973. .c('value').t('cauldronburn');
  974. _converse.connection._dataRecv(test_utils.createRequest(config_stanza));
  975. test_utils.waitUntil(function () {
  976. return $(view.el.querySelector('form.chatroom-form')).length;
  977. }, 300).then(function () {
  978. expect($(view.el.querySelector('form.chatroom-form')).length).toBe(1);
  979. expect(view.el.querySelectorAll('form.chatroom-form fieldset').length).toBe(2);
  980. var $membersonly = $(view.el.querySelector('input[name="muc#roomconfig_membersonly"]'));
  981. expect($membersonly.length).toBe(1);
  982. expect($membersonly.attr('type')).toBe('checkbox');
  983. $membersonly.prop('checked', true);
  984. var $moderated = $(view.el.querySelector('input[name="muc#roomconfig_moderatedroom"]'));
  985. expect($moderated.length).toBe(1);
  986. expect($moderated.attr('type')).toBe('checkbox');
  987. $moderated.prop('checked', true);
  988. var $password = $(view.el.querySelector('input[name="muc#roomconfig_roomsecret"]'));
  989. expect($password.length).toBe(1);
  990. expect($password.attr('type')).toBe('password');
  991. var $allowpm = $(view.el.querySelector('select[name="muc#roomconfig_allowpm"]'));
  992. expect($allowpm.length).toBe(1);
  993. $allowpm.val('moderators');
  994. var $presencebroadcast = $(view.el.querySelector('select[name="muc#roomconfig_presencebroadcast"]'));
  995. expect($presencebroadcast.length).toBe(1);
  996. $presencebroadcast.val(['moderator']);
  997. view.el.querySelector('input[type="submit"]').click();
  998. var $sent_stanza = $(sent_IQ.toLocaleString());
  999. expect($sent_stanza.find('field[var="muc#roomconfig_membersonly"] value').text()).toBe('1');
  1000. expect($sent_stanza.find('field[var="muc#roomconfig_moderatedroom"] value').text()).toBe('1');
  1001. expect($sent_stanza.find('field[var="muc#roomconfig_allowpm"] value').text()).toBe('moderators');
  1002. expect($sent_stanza.find('field[var="muc#roomconfig_presencebroadcast"] value').text()).toBe('moderator');
  1003. done();
  1004. });
  1005. }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
  1006. }));
  1007. it("shows users currently present in the room",
  1008. mock.initConverseWithPromises(
  1009. null, ['rosterGroupsFetched'], {},
  1010. function (done, _converse) {
  1011. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function() {
  1012. var name;
  1013. var view = _converse.chatboxviews.get('lounge@localhost'),
  1014. occupants = view.el.querySelector('.occupant-list');
  1015. var presence, role, jid, model;
  1016. for (var i=0; i<mock.chatroom_names.length; i++) {
  1017. name = mock.chatroom_names[i];
  1018. role = mock.chatroom_roles[name].role;
  1019. // See example 21 http://xmpp.org/extensions/xep-0045.html#enter-pres
  1020. jid =
  1021. presence = $pres({
  1022. to:'dummy@localhost/pda',
  1023. from:'lounge@localhost/'+name
  1024. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  1025. .c('item').attrs({
  1026. affiliation: mock.chatroom_roles[name].affiliation,
  1027. jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
  1028. role: role
  1029. }).up()
  1030. .c('status').attrs({code:'110'}).nodeTree;
  1031. _converse.connection._dataRecv(test_utils.createRequest(presence));
  1032. expect(occupants.querySelectorAll('li').length).toBe(2+i);
  1033. model = view.occupantsview.model.where({'nick': name})[0];
  1034. var index = view.occupantsview.model.indexOf(model);
  1035. expect(occupants.querySelectorAll('li')[index].textContent).toBe(mock.chatroom_names[i]);
  1036. expect($(occupants.querySelectorAll('li')[index]).hasClass('moderator')).toBe(role === "moderator");
  1037. }
  1038. // Test users leaving the room
  1039. // http://xmpp.org/extensions/xep-0045.html#exit
  1040. for (i=mock.chatroom_names.length-1; i>-1; i--) {
  1041. name = mock.chatroom_names[i];
  1042. role = mock.chatroom_roles[name].role;
  1043. // See example 21 http://xmpp.org/extensions/xep-0045.html#enter-pres
  1044. presence = $pres({
  1045. to:'dummy@localhost/pda',
  1046. from:'lounge@localhost/'+name,
  1047. type: 'unavailable'
  1048. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  1049. .c('item').attrs({
  1050. affiliation: mock.chatroom_roles[name].affiliation,
  1051. jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
  1052. role: 'none'
  1053. }).nodeTree;
  1054. _converse.connection._dataRecv(test_utils.createRequest(presence));
  1055. expect(occupants.querySelectorAll('li').length).toBe(i+1);
  1056. }
  1057. done();
  1058. });
  1059. }));
  1060. it("escapes occupant nicknames when rendering them, to avoid JS-injection attacks",
  1061. mock.initConverseWithPromises(null, ['rosterGroupsFetched'], {}, function (done, _converse) {
  1062. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
  1063. /* <presence xmlns="jabber:client" to="jc@chat.example.org/converse.js-17184538"
  1064. * from="oo@conference.chat.example.org/&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;">
  1065. * <x xmlns="http://jabber.org/protocol/muc#user">
  1066. * <item jid="jc@chat.example.org/converse.js-17184538" affiliation="owner" role="moderator"/>
  1067. * <status code="110"/>
  1068. * </x>
  1069. * </presence>"
  1070. */
  1071. var presence = $pres({
  1072. to:'dummy@localhost/pda',
  1073. from:"lounge@localhost/&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;"
  1074. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  1075. .c('item').attrs({
  1076. jid: 'someone@localhost',
  1077. role: 'moderator',
  1078. }).up()
  1079. .c('status').attrs({code:'110'}).nodeTree;
  1080. _converse.connection._dataRecv(test_utils.createRequest(presence));
  1081. var view = _converse.chatboxviews.get('lounge@localhost');
  1082. var occupants = view.el.querySelector('.occupant-list').querySelectorAll('li');
  1083. expect(occupants.length).toBe(2);
  1084. expect($(occupants).first().text()).toBe("&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;");
  1085. done();
  1086. });
  1087. }));
  1088. it("indicates moderators and visitors by means of a special css class and tooltip",
  1089. mock.initConverseWithPromises(
  1090. null, ['rosterGroupsFetched'], {},
  1091. function (done, _converse) {
  1092. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
  1093. var view = _converse.chatboxviews.get('lounge@localhost');
  1094. var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  1095. var presence = $pres({
  1096. to:'dummy@localhost/pda',
  1097. from:'lounge@localhost/moderatorman'
  1098. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  1099. .c('item').attrs({
  1100. affiliation: 'admin',
  1101. jid: contact_jid,
  1102. role: 'moderator',
  1103. }).up()
  1104. .c('status').attrs({code:'110'}).nodeTree;
  1105. _converse.connection._dataRecv(test_utils.createRequest(presence));
  1106. var occupants = view.el.querySelector('.occupant-list').querySelectorAll('li');
  1107. expect(occupants.length).toBe(2);
  1108. expect($(occupants).first().text()).toBe("moderatorman");
  1109. expect($(occupants).last().text()).toBe("dummy");
  1110. expect($(occupants).first().attr('class').indexOf('moderator')).not.toBe(-1);
  1111. expect($(occupants).first().attr('title')).toBe(
  1112. contact_jid + ' This user is a moderator. Click to mention moderatorman in your message.'
  1113. );
  1114. contact_jid = mock.cur_names[3].replace(/ /g,'.').toLowerCase() + '@localhost';
  1115. presence = $pres({
  1116. to:'dummy@localhost/pda',
  1117. from:'lounge@localhost/visitorwoman'
  1118. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  1119. .c('item').attrs({
  1120. jid: contact_jid,
  1121. role: 'visitor',
  1122. }).up()
  1123. .c('status').attrs({code:'110'}).nodeTree;
  1124. _converse.connection._dataRecv(test_utils.createRequest(presence));
  1125. occupants = view.el.querySelector('.occupant-list').querySelectorAll('li');
  1126. expect($(occupants).last().text()).toBe("visitorwoman");
  1127. expect($(occupants).last().attr('class').indexOf('visitor')).not.toBe(-1);
  1128. expect($(occupants).last().attr('title')).toBe(
  1129. contact_jid + ' This user can NOT send messages in this room. Click to mention visitorwoman in your message.'
  1130. );
  1131. done();
  1132. });
  1133. }));
  1134. it("will use the user's reserved nickname, if it exists",
  1135. mock.initConverseWithPromises(
  1136. null, ['rosterGroupsFetched'], {},
  1137. function (done, _converse) {
  1138. var sent_IQ, IQ_id;
  1139. var sendIQ = _converse.connection.sendIQ;
  1140. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  1141. if (iq.nodeTree.getAttribute('to') === 'lounge@localhost') {
  1142. sent_IQ = iq;
  1143. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  1144. } else {
  1145. sendIQ.bind(this)(iq, callback, errback);
  1146. }
  1147. });
  1148. test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  1149. // We pretend this is a new room, so no disco info is returned.
  1150. var features_stanza = $iq({
  1151. from: 'lounge@localhost',
  1152. 'id': IQ_id,
  1153. 'to': 'dummy@localhost/desktop',
  1154. 'type': 'error'
  1155. }).c('error', {'type': 'cancel'})
  1156. .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
  1157. _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
  1158. var view = _converse.chatboxviews.get('lounge@localhost');
  1159. spyOn(view, 'join').and.callThrough();
  1160. /* <iq from='hag66@shakespeare.lit/pda'
  1161. * id='getnick1'
  1162. * to='coven@chat.shakespeare.lit'
  1163. * type='get'>
  1164. * <query xmlns='http://jabber.org/protocol/disco#info'
  1165. * node='x-roomuser-item'/>
  1166. * </iq>
  1167. */
  1168. test_utils.waitUntil(function () {
  1169. return sent_IQ.toLocaleString() ===
  1170. "<iq to='lounge@localhost' from='dummy@localhost/resource' "+
  1171. "type='get' xmlns='jabber:client' id='"+IQ_id+"'>"+
  1172. "<query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item'/></iq>";
  1173. }, 300).then(function () {
  1174. /* <iq from='coven@chat.shakespeare.lit'
  1175. * id='getnick1'
  1176. * to='hag66@shakespeare.lit/pda'
  1177. * type='result'>
  1178. * <query xmlns='http://jabber.org/protocol/disco#info'
  1179. * node='x-roomuser-item'>
  1180. * <identity
  1181. * category='conference'
  1182. * name='thirdwitch'
  1183. * type='text'/>
  1184. * </query>
  1185. * </iq>
  1186. */
  1187. var stanza = $iq({
  1188. 'type': 'result',
  1189. 'id': IQ_id,
  1190. 'from': view.model.get('jid'),
  1191. 'to': _converse.connection.jid
  1192. }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info', 'node': 'x-roomuser-item'})
  1193. .c('identity', {'category': 'conference', 'name': 'thirdwitch', 'type': 'text'});
  1194. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1195. expect(view.join).toHaveBeenCalled();
  1196. // The user has just entered the room (because join was called)
  1197. // and receives their own presence from the server.
  1198. // See example 24:
  1199. // http://xmpp.org/extensions/xep-0045.html#enter-pres
  1200. var presence = $pres({
  1201. to:'dummy@localhost/resource',
  1202. from:'lounge@localhost/thirdwitch',
  1203. id:'DC352437-C019-40EC-B590-AF29E879AF97'
  1204. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  1205. .c('item').attrs({
  1206. affiliation: 'member',
  1207. jid: 'dummy@localhost/resource',
  1208. role: 'participant'
  1209. }).up()
  1210. .c('status').attrs({code:'110'}).up()
  1211. .c('status').attrs({code:'210'}).nodeTree;
  1212. _converse.connection._dataRecv(test_utils.createRequest(presence));
  1213. var info_text = $(view.el).find('.chat-content .chat-info:first').text();
  1214. expect(info_text).toBe('Your nickname has been automatically set to thirdwitch');
  1215. done();
  1216. });
  1217. }));
  1218. it("allows the user to invite their roster contacts to enter the chat room",
  1219. mock.initConverseWithPromises(
  1220. null, ['rosterGroupsFetched'], {},
  1221. function (done, _converse) {
  1222. test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  1223. test_utils.createContacts(_converse, 'current'); // We need roster contacts, so that we have someone to invite
  1224. // Since we don't actually fetch roster contacts, we need to
  1225. // cheat here and emit the event.
  1226. _converse.emit('rosterContactsFetched');
  1227. spyOn(_converse, 'emit');
  1228. spyOn(window, 'prompt').and.callFake(function () {
  1229. return "Please join!";
  1230. });
  1231. var view = _converse.chatboxviews.get('lounge@localhost');
  1232. // XXX: cheating a lttle bit, normally this'll be set after
  1233. // receiving the features for the room.
  1234. view.model.set('open', 'true');
  1235. spyOn(view.model, 'directInvite').and.callThrough();
  1236. var $input;
  1237. $(view.el).find('.chat-area').remove();
  1238. test_utils.waitUntil(function () {
  1239. return $(view.el).find('input.invited-contact').length;
  1240. }, 300).then(function () {
  1241. var $input = $(view.el).find('input.invited-contact');
  1242. expect($input.attr('placeholder')).toBe('Invite');
  1243. $input.val("Felix");
  1244. var evt = new Event('input');
  1245. $input[0].dispatchEvent(evt);
  1246. var sent_stanza;
  1247. spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
  1248. sent_stanza = stanza;
  1249. });
  1250. var $hint = $input.siblings('ul').children('li');
  1251. expect($input.val()).toBe('Felix');
  1252. expect($hint[0].textContent).toBe('Felix Amsel');
  1253. expect($hint.length).toBe(1);
  1254. if (typeof(Event) === 'function') {
  1255. // Not working on PhantomJS
  1256. evt = new Event('mousedown', {'bubbles': true});
  1257. evt.button = 0; // For some reason awesomplete wants this
  1258. $hint[0].dispatchEvent(evt);
  1259. expect(window.prompt).toHaveBeenCalled();
  1260. expect(view.model.directInvite).toHaveBeenCalled();
  1261. expect(sent_stanza.toLocaleString()).toBe(
  1262. "<message from='dummy@localhost/resource' to='felix.amsel@localhost' id='" +
  1263. sent_stanza.nodeTree.getAttribute('id') +
  1264. "' xmlns='jabber:client'>"+
  1265. "<x xmlns='jabber:x:conference' jid='lounge@localhost' reason='Please join!'/>"+
  1266. "</message>"
  1267. );
  1268. }
  1269. done();
  1270. });
  1271. }));
  1272. it("can be joined automatically, based upon a received invite",
  1273. mock.initConverseWithPromises(
  1274. null, ['rosterGroupsFetched'], {},
  1275. function (done, _converse) {
  1276. test_utils.createContacts(_converse, 'current'); // We need roster contacts, who can invite us
  1277. spyOn(window, 'confirm').and.callFake(function () {
  1278. return true;
  1279. });
  1280. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
  1281. var view = _converse.chatboxviews.get('lounge@localhost');
  1282. view.close(); // Hack, otherwise we have to mock stanzas.
  1283. var name = mock.cur_names[0];
  1284. var from_jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
  1285. var room_jid = 'lounge@localhost';
  1286. var reason = "Please join this chat room";
  1287. expect(_converse.chatboxes.models.length).toBe(1);
  1288. expect(_converse.chatboxes.models[0].id).toBe("controlbox");
  1289. var stanza = Strophe.xmlHtmlNode(
  1290. '<message xmlns="jabber:client" to="'+_converse.bare_jid+'" from="'+from_jid+'" id="9bceb415-f34b-4fa4-80d5-c0d076a24231">'+
  1291. '<x xmlns="jabber:x:conference" jid="'+room_jid+'" reason="'+reason+'"/>'+
  1292. '</message>').firstChild;
  1293. _converse.onDirectMUCInvitation(stanza);
  1294. expect(window.confirm).toHaveBeenCalledWith(
  1295. name + ' has invited you to join a chat room: '+ room_jid +
  1296. ', and left the following reason: "'+reason+'"');
  1297. expect(_converse.chatboxes.models.length).toBe(2);
  1298. expect(_converse.chatboxes.models[0].id).toBe('controlbox');
  1299. expect(_converse.chatboxes.models[1].id).toBe(room_jid);
  1300. done();
  1301. });
  1302. }));
  1303. it("shows received groupchat messages",
  1304. mock.initConverseWithPromises(
  1305. null, ['rosterGroupsFetched'], {},
  1306. function (done, _converse) {
  1307. test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  1308. spyOn(_converse, 'emit');
  1309. var view = _converse.chatboxviews.get('lounge@localhost');
  1310. if (!$(view.el).find('.chat-area').length) { view.renderChatArea(); }
  1311. var nick = mock.chatroom_names[0];
  1312. var text = 'This is a received message';
  1313. var message = $msg({
  1314. from: 'lounge@localhost/'+nick,
  1315. id: '1',
  1316. to: 'dummy@localhost',
  1317. type: 'groupchat'
  1318. }).c('body').t(text);
  1319. view.model.onMessage(message.nodeTree);
  1320. var $chat_content = $(view.el).find('.chat-content');
  1321. expect($chat_content.find('.chat-msg').length).toBe(1);
  1322. expect($chat_content.find('.chat-msg-text').text()).toBe(text);
  1323. expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
  1324. done();
  1325. }));
  1326. it("shows sent groupchat messages",
  1327. mock.initConverseWithPromises(
  1328. null, ['rosterGroupsFetched'], {},
  1329. function (done, _converse) {
  1330. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
  1331. spyOn(_converse, 'emit');
  1332. var view = _converse.chatboxviews.get('lounge@localhost');
  1333. if (!$(view.el).find('.chat-area').length) { view.renderChatArea(); }
  1334. var text = 'This is a sent message';
  1335. var textarea = view.el.querySelector('.chat-textarea');
  1336. textarea.value = text;
  1337. view.keyPressed({
  1338. target: textarea,
  1339. preventDefault: _.noop,
  1340. keyCode: 13
  1341. });
  1342. expect(_converse.emit).toHaveBeenCalledWith('messageSend', text);
  1343. var $chat_content = $(view.el).find('.chat-content');
  1344. expect($chat_content.find('.chat-msg').length).toBe(1);
  1345. // Let's check that if we receive the same message again, it's
  1346. // not shown.
  1347. var message = $msg({
  1348. from: 'lounge@localhost/dummy',
  1349. to: 'dummy@localhost.com',
  1350. type: 'groupchat',
  1351. id: view.model.messages.at(0).get('msgid')
  1352. }).c('body').t(text);
  1353. view.model.onMessage(message.nodeTree);
  1354. expect($chat_content.find('.chat-msg').length).toBe(1);
  1355. expect($chat_content.find('.chat-msg-text').last().text()).toBe(text);
  1356. // We don't emit an event if it's our own message
  1357. expect(_converse.emit.calls.count(), 1);
  1358. done();
  1359. });
  1360. }));
  1361. it("will cause the chat area to be scrolled down only if it was at the bottom already",
  1362. mock.initConverseWithPromises(
  1363. null, ['rosterGroupsFetched'], {},
  1364. function (done, _converse) {
  1365. var message = 'This message is received while the chat area is scrolled up';
  1366. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
  1367. var view = _converse.chatboxviews.get('lounge@localhost');
  1368. spyOn(view, 'scrollDown').and.callThrough();
  1369. /* Create enough messages so that there's a
  1370. * scrollbar.
  1371. */
  1372. for (var i=0; i<20; i++) {
  1373. view.model.onMessage(
  1374. $msg({
  1375. from: 'lounge@localhost/someone',
  1376. to: 'dummy@localhost.com',
  1377. type: 'groupchat',
  1378. id: (new Date()).getTime(),
  1379. }).c('body').t('Message: '+i).tree());
  1380. }
  1381. // Give enough time for `markScrolled` to have been called
  1382. setTimeout(function () {
  1383. view.content.scrollTop = 0;
  1384. view.model.onMessage(
  1385. $msg({
  1386. from: 'lounge@localhost/someone',
  1387. to: 'dummy@localhost.com',
  1388. type: 'groupchat',
  1389. id: (new Date()).getTime(),
  1390. }).c('body').t(message).tree());
  1391. // Now check that the message appears inside the chatbox in the DOM
  1392. var $chat_content = $(view.el).find('.chat-content');
  1393. var msg_txt = $chat_content.find('.chat-msg:last').find('.chat-msg-text').text();
  1394. expect(msg_txt).toEqual(message);
  1395. expect(view.content.scrollTop).toBe(0);
  1396. done();
  1397. }, 500);
  1398. });
  1399. }));
  1400. it("shows received chatroom subject messages",
  1401. mock.initConverseWithPromises(
  1402. null, ['rosterGroupsFetched'], {},
  1403. function (done, _converse) {
  1404. test_utils.openAndEnterChatRoom(_converse, 'jdev', 'conference.jabber.org', 'jc').then(function () {
  1405. var text = 'Jabber/XMPP Development | RFCs and Extensions: http://xmpp.org/ | Protocol and XSF discussions: xsf@muc.xmpp.org';
  1406. var stanza = Strophe.xmlHtmlNode(
  1407. '<message xmlns="jabber:client" to="jc@opkode.com/_converse.js-60429116" type="groupchat" from="jdev@conference.jabber.org/ralphm">'+
  1408. ' <subject>'+text+'</subject>'+
  1409. ' <delay xmlns="urn:xmpp:delay" stamp="2014-02-04T09:35:39Z" from="jdev@conference.jabber.org"/>'+
  1410. ' <x xmlns="jabber:x:delay" stamp="20140204T09:35:39" from="jdev@conference.jabber.org"/>'+
  1411. '</message>').firstChild;
  1412. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1413. var view = _converse.chatboxviews.get('jdev@conference.jabber.org');
  1414. var chat_content = view.el.querySelector('.chat-content');
  1415. expect($(chat_content).find('.chat-event:last').text()).toBe('Topic set by ralphm');
  1416. expect($(chat_content).find('.chat-topic:last').text()).toBe(text);
  1417. done();
  1418. });
  1419. }));
  1420. it("escapes the subject before rendering it, to avoid JS-injection attacks",
  1421. mock.initConverseWithPromises(
  1422. null, ['rosterGroupsFetched'], {},
  1423. function (done, _converse) {
  1424. test_utils.openAndEnterChatRoom(_converse, 'jdev', 'conference.jabber.org', 'jc').then(function () {
  1425. spyOn(window, 'alert');
  1426. var subject = '<img src="x" onerror="alert(\'XSS\');"/>';
  1427. var view = _converse.chatboxviews.get('jdev@conference.jabber.org');
  1428. view.model.set({'subject': {
  1429. 'text': subject,
  1430. 'author': 'ralphm'
  1431. }});
  1432. var chat_content = view.el.querySelector('.chat-content');
  1433. expect($(chat_content).find('.chat-event:last').text()).toBe('Topic set by ralphm');
  1434. expect($(chat_content).find('.chat-topic:last').text()).toBe(subject);
  1435. done();
  1436. });
  1437. }));
  1438. it("informs users if their nicknames has been changed.",
  1439. mock.initConverseWithPromises(
  1440. null, ['rosterGroupsFetched'], {},
  1441. function (done, _converse) {
  1442. /* The service then sends two presence stanzas to the full JID
  1443. * of each occupant (including the occupant who is changing his
  1444. * or her room nickname), one of type "unavailable" for the old
  1445. * nickname and one indicating availability for the new
  1446. * nickname.
  1447. *
  1448. * See: http://xmpp.org/extensions/xep-0045.html#changenick
  1449. *
  1450. * <presence
  1451. * from='coven@localhost/thirdwitch'
  1452. * id='DC352437-C019-40EC-B590-AF29E879AF98'
  1453. * to='hag66@shakespeare.lit/pda'
  1454. * type='unavailable'>
  1455. * <x xmlns='http://jabber.org/protocol/muc#user'>
  1456. * <item affiliation='member'
  1457. * jid='hag66@shakespeare.lit/pda'
  1458. * nick='oldhag'
  1459. * role='participant'/>
  1460. * <status code='303'/>
  1461. * <status code='110'/>
  1462. * </x>
  1463. * </presence>
  1464. *
  1465. * <presence
  1466. * from='coven@localhost/oldhag'
  1467. * id='5B4F27A4-25ED-43F7-A699-382C6B4AFC67'
  1468. * to='hag66@shakespeare.lit/pda'>
  1469. * <x xmlns='http://jabber.org/protocol/muc#user'>
  1470. * <item affiliation='member'
  1471. * jid='hag66@shakespeare.lit/pda'
  1472. * role='participant'/>
  1473. * <status code='110'/>
  1474. * </x>
  1475. * </presence>
  1476. */
  1477. var __ = _converse.__;
  1478. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'oldnick').then(function () {
  1479. var view = _converse.chatboxviews.get('lounge@localhost');
  1480. var $chat_content = $(view.el).find('.chat-content');
  1481. var $occupants = $(view.el.querySelector('.occupant-list'));
  1482. expect($occupants.children().length).toBe(1);
  1483. expect($occupants.children().first(0).text()).toBe("oldnick");
  1484. expect($chat_content.find('div.chat-info').length).toBe(1);
  1485. expect($chat_content.find('div.chat-info:first').html()).toBe("oldnick has entered the room");
  1486. var presence = $pres().attrs({
  1487. from:'lounge@localhost/oldnick',
  1488. id:'DC352437-C019-40EC-B590-AF29E879AF98',
  1489. to:'dummy@localhost/pda',
  1490. type:'unavailable'
  1491. })
  1492. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  1493. .c('item').attrs({
  1494. affiliation: 'member',
  1495. jid: 'dummy@localhost/pda',
  1496. nick: 'newnick',
  1497. role: 'participant'
  1498. }).up()
  1499. .c('status').attrs({code:'303'}).up()
  1500. .c('status').attrs({code:'110'}).nodeTree;
  1501. _converse.connection._dataRecv(test_utils.createRequest(presence));
  1502. expect($chat_content.find('div.chat-info').length).toBe(2);
  1503. expect($chat_content.find('div.chat-info').last().html()).toBe(
  1504. __(_converse.muc.new_nickname_messages["303"], "newnick")
  1505. );
  1506. $occupants = $(view.el.querySelector('.occupant-list'));
  1507. expect($occupants.children().length).toBe(1);
  1508. presence = $pres().attrs({
  1509. from:'lounge@localhost/newnick',
  1510. id:'5B4F27A4-25ED-43F7-A699-382C6B4AFC67',
  1511. to:'dummy@localhost/pda'
  1512. })
  1513. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  1514. .c('item').attrs({
  1515. affiliation: 'member',
  1516. jid: 'dummy@localhost/pda',
  1517. role: 'participant'
  1518. }).up()
  1519. .c('status').attrs({code:'110'}).nodeTree;
  1520. _converse.connection._dataRecv(test_utils.createRequest(presence));
  1521. expect($chat_content.find('div.chat-info').length).toBe(2);
  1522. expect($chat_content.find('div.chat-info').get(1).textContent).toBe(
  1523. __(_converse.muc.new_nickname_messages["303"], "newnick")
  1524. );
  1525. $occupants = $(view.el.querySelector('.occupant-list'));
  1526. expect($occupants.children().length).toBe(1);
  1527. expect($occupants.children().first(0).text()).toBe("newnick");
  1528. done();
  1529. });
  1530. }));
  1531. it("queries for the room information before attempting to join the user",
  1532. mock.initConverseWithPromises(
  1533. null, ['rosterGroupsFetched'], {},
  1534. function (done, _converse) {
  1535. var sent_IQ, IQ_id;
  1536. var sendIQ = _converse.connection.sendIQ;
  1537. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  1538. sent_IQ = iq;
  1539. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  1540. });
  1541. _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'});
  1542. // Check that the room queried for the feautures.
  1543. expect(sent_IQ.toLocaleString()).toBe(
  1544. "<iq from='dummy@localhost/resource' to='coven@chat.shakespeare.lit' type='get' xmlns='jabber:client' id='"+IQ_id+"'>"+
  1545. "<query xmlns='http://jabber.org/protocol/disco#info'/>"+
  1546. "</iq>");
  1547. /* <iq from='coven@chat.shakespeare.lit'
  1548. * id='ik3vs715'
  1549. * to='hag66@shakespeare.lit/pda'
  1550. * type='result'>
  1551. * <query xmlns='http://jabber.org/protocol/disco#info'>
  1552. * <identity
  1553. * category='conference'
  1554. * name='A Dark Cave'
  1555. * type='text'/>
  1556. * <feature var='http://jabber.org/protocol/muc'/>
  1557. * <feature var='muc_passwordprotected'/>
  1558. * <feature var='muc_hidden'/>
  1559. * <feature var='muc_temporary'/>
  1560. * <feature var='muc_open'/>
  1561. * <feature var='muc_unmoderated'/>
  1562. * <feature var='muc_nonanonymous'/>
  1563. * </query>
  1564. * </iq>
  1565. */
  1566. var features_stanza = $iq({
  1567. from: 'coven@chat.shakespeare.lit',
  1568. 'id': IQ_id,
  1569. 'to': 'dummy@localhost/desktop',
  1570. 'type': 'result'
  1571. })
  1572. .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'})
  1573. .c('identity', {
  1574. 'category': 'conference',
  1575. 'name': 'A Dark Cave',
  1576. 'type': 'text'
  1577. }).up()
  1578. .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up()
  1579. .c('feature', {'var': 'muc_passwordprotected'}).up()
  1580. .c('feature', {'var': 'muc_hidden'}).up()
  1581. .c('feature', {'var': 'muc_temporary'}).up()
  1582. .c('feature', {'var': 'muc_open'}).up()
  1583. .c('feature', {'var': 'muc_unmoderated'}).up()
  1584. .c('feature', {'var': 'muc_nonanonymous'});
  1585. _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
  1586. var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
  1587. expect(view.model.get('features_fetched')).toBe(true);
  1588. expect(view.model.get('passwordprotected')).toBe(true);
  1589. expect(view.model.get('hidden')).toBe(true);
  1590. expect(view.model.get('temporary')).toBe(true);
  1591. expect(view.model.get('open')).toBe(true);
  1592. expect(view.model.get('unmoderated')).toBe(true);
  1593. expect(view.model.get('nonanonymous')).toBe(true);
  1594. done();
  1595. }));
  1596. it("updates the shown features when the room configuration has changed",
  1597. mock.initConverseWithPromises(
  1598. null, ['rosterGroupsFetched'], {},
  1599. function (done, _converse) {
  1600. var sent_IQ, IQ_id;
  1601. var sendIQ = _converse.connection.sendIQ;
  1602. test_utils.openAndEnterChatRoom(_converse, 'room', 'conference.example.org', 'dummy').then(function () {
  1603. var view = _converse.chatboxviews.get('room@conference.example.org');
  1604. view.model.set({
  1605. 'passwordprotected': false,
  1606. 'unsecured': true,
  1607. 'hidden': false,
  1608. 'public': true,
  1609. 'membersonly': false,
  1610. 'open': true,
  1611. 'persistent': false,
  1612. 'temporary': true,
  1613. 'nonanonymous': true,
  1614. 'semianonymous': false,
  1615. 'moderated': false,
  1616. 'unmoderated': true
  1617. });
  1618. expect(view.model.get('persistent')).toBe(false);
  1619. expect(view.model.get('temporary')).toBe(true);
  1620. view.model.set({'persistent': true});
  1621. expect(view.model.get('persistent')).toBe(true);
  1622. expect(view.model.get('temporary')).toBe(false);
  1623. expect(view.model.get('unsecured')).toBe(true);
  1624. expect(view.model.get('passwordprotected')).toBe(false);
  1625. view.model.set({'passwordprotected': true});
  1626. expect(view.model.get('unsecured')).toBe(false);
  1627. expect(view.model.get('passwordprotected')).toBe(true);
  1628. expect(view.model.get('unmoderated')).toBe(true);
  1629. expect(view.model.get('moderated')).toBe(false);
  1630. view.model.set({'moderated': true});
  1631. expect(view.model.get('unmoderated')).toBe(false);
  1632. expect(view.model.get('moderated')).toBe(true);
  1633. expect(view.model.get('nonanonymous')).toBe(true);
  1634. expect(view.model.get('semianonymous')).toBe(false);
  1635. view.model.set({'nonanonymous': false});
  1636. expect(view.model.get('nonanonymous')).toBe(false);
  1637. expect(view.model.get('semianonymous')).toBe(true);
  1638. expect(view.model.get('open')).toBe(true);
  1639. expect(view.model.get('membersonly')).toBe(false);
  1640. view.model.set({'membersonly': true});
  1641. expect(view.model.get('open')).toBe(false);
  1642. expect(view.model.get('membersonly')).toBe(true);
  1643. done();
  1644. });
  1645. }));
  1646. it("indicates when a room is no longer anonymous",
  1647. mock.initConverseWithPromises(
  1648. null, ['rosterGroupsFetched'], {},
  1649. function (done, _converse) {
  1650. var sent_IQ, IQ_id;
  1651. var sendIQ = _converse.connection.sendIQ;
  1652. test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'some1').then(function () {
  1653. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  1654. sent_IQ = iq;
  1655. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  1656. });
  1657. // We pretend this is a new room, so no disco info is returned.
  1658. var features_stanza = $iq({
  1659. from: 'coven@chat.shakespeare.lit',
  1660. 'id': IQ_id,
  1661. 'to': 'dummy@localhost/desktop',
  1662. 'type': 'error'
  1663. }).c('error', {'type': 'cancel'})
  1664. .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
  1665. _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
  1666. var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
  1667. /* <message xmlns="jabber:client"
  1668. * type="groupchat"
  1669. * to="dummy@localhost/_converse.js-27854181"
  1670. * from="coven@chat.shakespeare.lit">
  1671. * <x xmlns="http://jabber.org/protocol/muc#user">
  1672. * <status code="104"/>
  1673. * <status code="172"/>
  1674. * </x>
  1675. * </message>
  1676. */
  1677. var message = $msg({
  1678. type:'groupchat',
  1679. to: 'dummy@localhost/_converse.js-27854181',
  1680. from: 'coven@chat.shakespeare.lit'
  1681. }).c('x', {xmlns: Strophe.NS.MUC_USER})
  1682. .c('status', {code: '104'}).up()
  1683. .c('status', {code: '172'});
  1684. _converse.connection._dataRecv(test_utils.createRequest(message));
  1685. var $chat_body = $(view.el.querySelector('.chatroom-body'));
  1686. expect($chat_body.find('.message:last').text()).toBe('This room is now no longer anonymous');
  1687. done();
  1688. });
  1689. }));
  1690. it("informs users if they have been kicked out of the chat room",
  1691. mock.initConverseWithPromises(
  1692. null, ['rosterGroupsFetched'], {},
  1693. function (done, _converse) {
  1694. /* <presence
  1695. * from='harfleur@chat.shakespeare.lit/pistol'
  1696. * to='pistol@shakespeare.lit/harfleur'
  1697. * type='unavailable'>
  1698. * <x xmlns='http://jabber.org/protocol/muc#user'>
  1699. * <item affiliation='none' role='none'>
  1700. * <actor nick='Fluellen'/>
  1701. * <reason>Avaunt, you cullion!</reason>
  1702. * </item>
  1703. * <status code='110'/>
  1704. * <status code='307'/>
  1705. * </x>
  1706. * </presence>
  1707. */
  1708. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
  1709. var presence = $pres().attrs({
  1710. from:'lounge@localhost/dummy',
  1711. to:'dummy@localhost/pda',
  1712. type:'unavailable'
  1713. })
  1714. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  1715. .c('item').attrs({
  1716. affiliation: 'none',
  1717. jid: 'dummy@localhost/pda',
  1718. role: 'none'
  1719. })
  1720. .c('actor').attrs({nick: 'Fluellen'}).up()
  1721. .c('reason').t('Avaunt, you cullion!').up()
  1722. .up()
  1723. .c('status').attrs({code:'110'}).up()
  1724. .c('status').attrs({code:'307'}).nodeTree;
  1725. _converse.connection._dataRecv(test_utils.createRequest(presence));
  1726. var view = _converse.chatboxviews.get('lounge@localhost');
  1727. expect($(view.el.querySelector('.chat-area')).is(':visible')).toBeFalsy();
  1728. expect($(view.el.querySelector('.occupants')).is(':visible')).toBeFalsy();
  1729. var $chat_body = $(view.el.querySelector('.chatroom-body'));
  1730. expect($chat_body.find('.disconnect-msg').text()).toBe(
  1731. 'You have been kicked from this room'+
  1732. 'This action was done by Fluellen.'+
  1733. 'The reason given is: "Avaunt, you cullion!".'
  1734. );
  1735. done();
  1736. });
  1737. }));
  1738. it("can be saved to, and retrieved from, browserStorage",
  1739. mock.initConverseWithPromises(
  1740. null, ['rosterGroupsFetched'], {},
  1741. function (done, _converse) {
  1742. test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  1743. // We instantiate a new ChatBoxes collection, which by default
  1744. // will be empty.
  1745. test_utils.openControlBox();
  1746. var newchatboxes = new _converse.ChatBoxes();
  1747. expect(newchatboxes.length).toEqual(0);
  1748. // The chatboxes will then be fetched from browserStorage inside the
  1749. // onConnected method
  1750. newchatboxes.onConnected();
  1751. expect(newchatboxes.length).toEqual(2);
  1752. // Check that the chatrooms retrieved from browserStorage
  1753. // have the same attributes values as the original ones.
  1754. var attrs = ['id', 'box_id', 'visible'];
  1755. var new_attrs, old_attrs;
  1756. for (var i=0; i<attrs.length; i++) {
  1757. new_attrs = _.map(_.map(newchatboxes.models, 'attributes'), attrs[i]);
  1758. old_attrs = _.map(_.map(_converse.chatboxes.models, 'attributes'), attrs[i]);
  1759. // FIXME: should have have to sort here? Order must
  1760. // probably be the same...
  1761. // This should be fixed once the controlbox always opens
  1762. // only on the right.
  1763. expect(_.isEqual(new_attrs.sort(), old_attrs.sort())).toEqual(true);
  1764. }
  1765. _converse.rosterview.render();
  1766. done();
  1767. }));
  1768. it("can be minimized by clicking a DOM element with class 'toggle-chatbox-button'",
  1769. mock.initConverseWithPromises(
  1770. null, ['rosterGroupsFetched'], {},
  1771. function (done, _converse) {
  1772. test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  1773. var view = _converse.chatboxviews.get('lounge@localhost'),
  1774. trimmed_chatboxes = _converse.minimized_chats;
  1775. spyOn(view, 'minimize').and.callThrough();
  1776. spyOn(view, 'maximize').and.callThrough();
  1777. spyOn(_converse, 'emit');
  1778. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  1779. view.el.querySelector('.toggle-chatbox-button').click();
  1780. expect(view.minimize).toHaveBeenCalled();
  1781. expect(_converse.emit).toHaveBeenCalledWith('chatBoxMinimized', jasmine.any(Object));
  1782. expect(u.isVisible(view.el)).toBeFalsy();
  1783. expect(view.model.get('minimized')).toBeTruthy();
  1784. expect(view.minimize).toHaveBeenCalled();
  1785. var trimmedview = trimmed_chatboxes.get(view.model.get('id'));
  1786. trimmedview.el.querySelector("a.restore-chat").click();
  1787. expect(view.maximize).toHaveBeenCalled();
  1788. expect(_converse.emit).toHaveBeenCalledWith('chatBoxMaximized', jasmine.any(Object));
  1789. expect(view.model.get('minimized')).toBeFalsy();
  1790. expect(_converse.emit.calls.count(), 3);
  1791. done();
  1792. }));
  1793. it("can be closed again by clicking a DOM element with class 'close-chatbox-button'",
  1794. mock.initConverseWithPromises(
  1795. null, ['rosterGroupsFetched'], {},
  1796. function (done, _converse) {
  1797. test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy');
  1798. var view = _converse.chatboxviews.get('lounge@localhost');
  1799. spyOn(view, 'close').and.callThrough();
  1800. spyOn(_converse, 'emit');
  1801. spyOn(view.model, 'leave');
  1802. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  1803. view.el.querySelector('.close-chatbox-button').click();
  1804. expect(view.close).toHaveBeenCalled();
  1805. expect(view.model.leave).toHaveBeenCalled();
  1806. expect(_converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
  1807. done();
  1808. }));
  1809. });
  1810. describe("Each chat room can take special commands", function () {
  1811. it("/help to show the available commands",
  1812. mock.initConverseWithPromises(
  1813. null, ['rosterGroupsFetched'], {},
  1814. function (done, _converse) {
  1815. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
  1816. var view = _converse.chatboxviews.get('lounge@localhost');
  1817. spyOn(view, 'onMessageSubmitted').and.callThrough();
  1818. var textarea = view.el.querySelector('.chat-textarea');
  1819. textarea.value = '/help This is the room subject';
  1820. view.keyPressed({
  1821. target: textarea,
  1822. preventDefault: _.noop,
  1823. keyCode: 13
  1824. });
  1825. expect(view.onMessageSubmitted).toHaveBeenCalled();
  1826. const info_messages = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0);
  1827. expect(info_messages.length).toBe(17);
  1828. expect(info_messages.pop().textContent).toBe('/voice: Allow muted user to post messages');
  1829. expect(info_messages.pop().textContent).toBe('/topic: Set room subject (alias for /subject)');
  1830. expect(info_messages.pop().textContent).toBe('/subject: Set room subject');
  1831. expect(info_messages.pop().textContent).toBe('/revoke: Revoke user\'s membership');
  1832. expect(info_messages.pop().textContent).toBe('/owner: Grant ownership of this room');
  1833. expect(info_messages.pop().textContent).toBe('/op: Grant moderator role to user');
  1834. expect(info_messages.pop().textContent).toBe('/nick: Change your nickname');
  1835. expect(info_messages.pop().textContent).toBe('/mute: Remove user\'s ability to post messages');
  1836. expect(info_messages.pop().textContent).toBe('/member: Grant membership to a user');
  1837. expect(info_messages.pop().textContent).toBe('/me: Write in 3rd person');
  1838. expect(info_messages.pop().textContent).toBe('/kick: Kick user from room');
  1839. expect(info_messages.pop().textContent).toBe('/help: Show this menu');
  1840. expect(info_messages.pop().textContent).toBe('/deop: Change user role to participant');
  1841. expect(info_messages.pop().textContent).toBe('/clear: Remove messages');
  1842. expect(info_messages.pop().textContent).toBe('/ban: Ban user from room');
  1843. expect(info_messages.pop().textContent).toBe('/admin: Change user\'s affiliation to admin');
  1844. done();
  1845. });
  1846. }));
  1847. it("/topic to set the room topic",
  1848. mock.initConverseWithPromises(
  1849. null, ['rosterGroupsFetched'], {},
  1850. function (done, _converse) {
  1851. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
  1852. var sent_stanza;
  1853. var view = _converse.chatboxviews.get('lounge@localhost');
  1854. spyOn(view, 'onMessageSubmitted').and.callThrough();
  1855. spyOn(view, 'clearMessages');
  1856. spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
  1857. sent_stanza = stanza;
  1858. });
  1859. // Check the alias /topic
  1860. var textarea = view.el.querySelector('.chat-textarea');
  1861. textarea.value = '/topic This is the room subject';
  1862. view.keyPressed({
  1863. target: textarea,
  1864. preventDefault: _.noop,
  1865. keyCode: 13
  1866. });
  1867. expect(view.onMessageSubmitted).toHaveBeenCalled();
  1868. expect(_converse.connection.send).toHaveBeenCalled();
  1869. expect(sent_stanza.textContent).toBe('This is the room subject');
  1870. // Check /subject
  1871. textarea.value = '/subject This is a new subject';
  1872. view.keyPressed({
  1873. target: textarea,
  1874. preventDefault: _.noop,
  1875. keyCode: 13
  1876. });
  1877. expect(sent_stanza.textContent).toBe('This is a new subject');
  1878. expect(sent_stanza.outerHTML).toBe(
  1879. '<message to="lounge@localhost" from="dummy@localhost/resource" type="groupchat" xmlns="jabber:client">'+
  1880. '<subject xmlns="jabber:client">This is a new subject</subject>'+
  1881. '</message>');
  1882. // Check case insensitivity
  1883. textarea.value = '/Subject This is yet another subject';
  1884. view.keyPressed({
  1885. target: textarea,
  1886. preventDefault: _.noop,
  1887. keyCode: 13
  1888. });
  1889. expect(sent_stanza.textContent).toBe('This is yet another subject');
  1890. expect(sent_stanza.outerHTML).toBe(
  1891. '<message to="lounge@localhost" from="dummy@localhost/resource" type="groupchat" xmlns="jabber:client">'+
  1892. '<subject xmlns="jabber:client">This is yet another subject</subject>'+
  1893. '</message>');
  1894. done();
  1895. }).catch(_.partial(console.error, _));
  1896. }));
  1897. it("/clear to clear messages",
  1898. mock.initConverseWithPromises(
  1899. null, ['rosterGroupsFetched'], {},
  1900. function (done, _converse) {
  1901. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
  1902. var view = _converse.chatboxviews.get('lounge@localhost');
  1903. spyOn(view, 'onMessageSubmitted').and.callThrough();
  1904. spyOn(view, 'clearMessages');
  1905. var textarea = view.el.querySelector('.chat-textarea')
  1906. textarea.value = '/clear';
  1907. view.keyPressed({
  1908. target: textarea,
  1909. preventDefault: _.noop,
  1910. keyCode: 13
  1911. });
  1912. expect(view.onMessageSubmitted).toHaveBeenCalled();
  1913. expect(view.clearMessages).toHaveBeenCalled();
  1914. done();
  1915. }).catch(_.partial(console.error, _));
  1916. }));
  1917. it("/owner to make a user an owner",
  1918. mock.initConverseWithPromises(
  1919. null, ['rosterGroupsFetched'], {},
  1920. function (done, _converse) {
  1921. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
  1922. var sent_IQ, IQ_id;
  1923. var sendIQ = _converse.connection.sendIQ;
  1924. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  1925. sent_IQ = iq;
  1926. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  1927. });
  1928. var view = _converse.chatboxviews.get('lounge@localhost');
  1929. spyOn(view, 'onMessageSubmitted').and.callThrough();
  1930. spyOn(view.model, 'setAffiliation').and.callThrough();
  1931. spyOn(view, 'showErrorMessage').and.callThrough();
  1932. spyOn(view, 'validateRoleChangeCommand').and.callThrough();
  1933. var textarea = view.el.querySelector('.chat-textarea')
  1934. textarea.value = '/owner';
  1935. view.keyPressed({
  1936. target: textarea,
  1937. preventDefault: _.noop,
  1938. keyCode: 13
  1939. });
  1940. expect(view.onMessageSubmitted).toHaveBeenCalled();
  1941. expect(view.validateRoleChangeCommand).toHaveBeenCalled();
  1942. expect(view.showErrorMessage).toHaveBeenCalledWith(
  1943. "Error: the \"owner\" command takes two arguments, the user's nickname and optionally a reason.",
  1944. true
  1945. );
  1946. expect(view.model.setAffiliation).not.toHaveBeenCalled();
  1947. // Call now with the correct amount of arguments.
  1948. // XXX: Calling onMessageSubmitted directly, trying
  1949. // again via triggering Event doesn't work for some weird
  1950. // reason.
  1951. view.onMessageSubmitted('/owner annoyingGuy@localhost You\'re responsible');
  1952. expect(view.validateRoleChangeCommand.calls.count()).toBe(2);
  1953. expect(view.showErrorMessage.calls.count()).toBe(1);
  1954. expect(view.model.setAffiliation).toHaveBeenCalled();
  1955. // Check that the member list now gets updated
  1956. expect(sent_IQ.toLocaleString()).toBe(
  1957. "<iq to='lounge@localhost' type='set' xmlns='jabber:client' id='"+IQ_id+"'>"+
  1958. "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
  1959. "<item affiliation='owner' jid='annoyingGuy@localhost'>"+
  1960. "<reason>You&apos;re responsible</reason>"+
  1961. "</item>"+
  1962. "</query>"+
  1963. "</iq>");
  1964. done();
  1965. }).catch(_.partial(console.error, _));
  1966. }));
  1967. it("/ban to ban a user",
  1968. mock.initConverseWithPromises(
  1969. null, ['rosterGroupsFetched'], {},
  1970. function (done, _converse) {
  1971. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
  1972. var sent_IQ, IQ_id;
  1973. var sendIQ = _converse.connection.sendIQ;
  1974. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  1975. sent_IQ = iq;
  1976. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  1977. });
  1978. var view = _converse.chatboxviews.get('lounge@localhost');
  1979. spyOn(view, 'onMessageSubmitted').and.callThrough();
  1980. spyOn(view.model, 'setAffiliation').and.callThrough();
  1981. spyOn(view, 'showErrorMessage').and.callThrough();
  1982. spyOn(view, 'validateRoleChangeCommand').and.callThrough();
  1983. var textarea = view.el.querySelector('.chat-textarea')
  1984. textarea.value = '/ban';
  1985. view.keyPressed({
  1986. target: textarea,
  1987. preventDefault: _.noop,
  1988. keyCode: 13
  1989. });
  1990. expect(view.onMessageSubmitted).toHaveBeenCalled();
  1991. expect(view.validateRoleChangeCommand).toHaveBeenCalled();
  1992. expect(view.showErrorMessage).toHaveBeenCalledWith(
  1993. "Error: the \"ban\" command takes two arguments, the user's nickname and optionally a reason.",
  1994. true
  1995. );
  1996. expect(view.model.setAffiliation).not.toHaveBeenCalled();
  1997. // Call now with the correct amount of arguments.
  1998. // XXX: Calling onMessageSubmitted directly, trying
  1999. // again via triggering Event doesn't work for some weird
  2000. // reason.
  2001. view.onMessageSubmitted('/ban annoyingGuy@localhost You\'re annoying');
  2002. expect(view.validateRoleChangeCommand.calls.count()).toBe(2);
  2003. expect(view.showErrorMessage.calls.count()).toBe(1);
  2004. expect(view.model.setAffiliation).toHaveBeenCalled();
  2005. // Check that the member list now gets updated
  2006. expect(sent_IQ.toLocaleString()).toBe(
  2007. "<iq to='lounge@localhost' type='set' xmlns='jabber:client' id='"+IQ_id+"'>"+
  2008. "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
  2009. "<item affiliation='outcast' jid='annoyingGuy@localhost'>"+
  2010. "<reason>You&apos;re annoying</reason>"+
  2011. "</item>"+
  2012. "</query>"+
  2013. "</iq>");
  2014. done();
  2015. }).catch(_.partial(console.error, _));
  2016. }));
  2017. it("/kick to kick a user",
  2018. mock.initConverseWithPromises(
  2019. null, ['rosterGroupsFetched'], {},
  2020. function (done, _converse) {
  2021. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
  2022. var sent_IQ, IQ_id;
  2023. var sendIQ = _converse.connection.sendIQ;
  2024. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  2025. sent_IQ = iq;
  2026. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  2027. });
  2028. var view = _converse.chatboxviews.get('lounge@localhost');
  2029. spyOn(view, 'onMessageSubmitted').and.callThrough();
  2030. spyOn(view, 'modifyRole').and.callThrough();
  2031. spyOn(view, 'showErrorMessage').and.callThrough();
  2032. spyOn(view, 'validateRoleChangeCommand').and.callThrough();
  2033. var textarea = view.el.querySelector('.chat-textarea')
  2034. textarea.value = '/kick';
  2035. view.keyPressed({
  2036. target: textarea,
  2037. preventDefault: _.noop,
  2038. keyCode: 13
  2039. });
  2040. expect(view.onMessageSubmitted).toHaveBeenCalled();
  2041. expect(view.validateRoleChangeCommand).toHaveBeenCalled();
  2042. expect(view.showErrorMessage).toHaveBeenCalledWith(
  2043. "Error: the \"kick\" command takes two arguments, the user's nickname and optionally a reason.",
  2044. true
  2045. );
  2046. expect(view.modifyRole).not.toHaveBeenCalled();
  2047. // Call now with the correct amount of arguments.
  2048. // XXX: Calling onMessageSubmitted directly, trying
  2049. // again via triggering Event doesn't work for some weird
  2050. // reason.
  2051. view.onMessageSubmitted('/kick annoyingGuy You\'re annoying');
  2052. expect(view.validateRoleChangeCommand.calls.count()).toBe(2);
  2053. expect(view.showErrorMessage.calls.count()).toBe(1);
  2054. expect(view.modifyRole).toHaveBeenCalled();
  2055. expect(sent_IQ.toLocaleString()).toBe(
  2056. "<iq to='lounge@localhost' type='set' xmlns='jabber:client' id='"+IQ_id+"'>"+
  2057. "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
  2058. "<item nick='annoyingGuy' role='none'>"+
  2059. "<reason>You&apos;re annoying</reason>"+
  2060. "</item>"+
  2061. "</query>"+
  2062. "</iq>");
  2063. /* <presence
  2064. * from='harfleur@chat.shakespeare.lit/pistol'
  2065. * to='gower@shakespeare.lit/cell'
  2066. * type='unavailable'>
  2067. * <x xmlns='http://jabber.org/protocol/muc#user'>
  2068. * <item affiliation='none' role='none'/>
  2069. * <status code='307'/>
  2070. * </x>
  2071. * </presence>
  2072. */
  2073. var presence = $pres({
  2074. 'from': 'lounge@localhost/annoyingGuy',
  2075. 'to': 'dummy@localhost/desktop',
  2076. 'type': 'unavailable'
  2077. })
  2078. .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'})
  2079. .c('item', {
  2080. 'affiliation': 'none',
  2081. 'role': 'none'
  2082. }).up()
  2083. .c('status', {'code': '307'});
  2084. _converse.connection._dataRecv(test_utils.createRequest(presence));
  2085. expect(
  2086. view.el.querySelectorAll('.chat-info')[2].textContent).toBe(
  2087. "annoyingGuy has been kicked out");
  2088. done();
  2089. }).catch(_.partial(console.error, _));
  2090. }));
  2091. it("/op and /deop to make a user a moderator or not",
  2092. mock.initConverseWithPromises(
  2093. null, ['rosterGroupsFetched'], {},
  2094. function (done, _converse) {
  2095. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
  2096. var sent_IQ, IQ_id;
  2097. var sendIQ = _converse.connection.sendIQ;
  2098. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  2099. sent_IQ = iq;
  2100. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  2101. });
  2102. var view = _converse.chatboxviews.get('lounge@localhost');
  2103. spyOn(view, 'onMessageSubmitted').and.callThrough();
  2104. spyOn(view, 'modifyRole').and.callThrough();
  2105. spyOn(view, 'showErrorMessage').and.callThrough();
  2106. spyOn(view, 'showChatEvent').and.callThrough();
  2107. spyOn(view, 'validateRoleChangeCommand').and.callThrough();
  2108. // New user enters the room
  2109. /* <presence
  2110. * from='coven@chat.shakespeare.lit/thirdwitch'
  2111. * id='27C55F89-1C6A-459A-9EB5-77690145D624'
  2112. * to='crone1@shakespeare.lit/desktop'>
  2113. * <x xmlns='http://jabber.org/protocol/muc#user'>
  2114. * <item affiliation='member' role='moderator'/>
  2115. * </x>
  2116. * </presence>
  2117. */
  2118. var presence = $pres({
  2119. 'from': 'lounge@localhost/trustworthyguy',
  2120. 'id':'27C55F89-1C6A-459A-9EB5-77690145D624',
  2121. 'to': 'dummy@localhost/desktop'
  2122. })
  2123. .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'})
  2124. .c('item', {
  2125. 'jid': 'trustworthyguy@localhost',
  2126. 'affiliation': 'member',
  2127. 'role': 'participant'
  2128. });
  2129. _converse.connection._dataRecv(test_utils.createRequest(presence));
  2130. var info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0);
  2131. expect(info_msgs.pop().textContent).toBe("trustworthyguy has entered the room");
  2132. var textarea = view.el.querySelector('.chat-textarea')
  2133. textarea.value = '/op';
  2134. view.keyPressed({
  2135. target: textarea,
  2136. preventDefault: _.noop,
  2137. keyCode: 13
  2138. });
  2139. expect(view.onMessageSubmitted).toHaveBeenCalled();
  2140. expect(view.validateRoleChangeCommand).toHaveBeenCalled();
  2141. expect(view.showErrorMessage).toHaveBeenCalledWith(
  2142. "Error: the \"op\" command takes two arguments, the user's nickname and optionally a reason.",
  2143. true
  2144. );
  2145. expect(view.modifyRole).not.toHaveBeenCalled();
  2146. // Call now with the correct amount of arguments.
  2147. // XXX: Calling onMessageSubmitted directly, trying
  2148. // again via triggering Event doesn't work for some weird
  2149. // reason.
  2150. view.onMessageSubmitted('/op trustworthyguy You\'re trustworthy');
  2151. expect(view.validateRoleChangeCommand.calls.count()).toBe(2);
  2152. expect(view.showErrorMessage.calls.count()).toBe(1);
  2153. expect(view.modifyRole).toHaveBeenCalled();
  2154. expect(sent_IQ.toLocaleString()).toBe(
  2155. "<iq to='lounge@localhost' type='set' xmlns='jabber:client' id='"+IQ_id+"'>"+
  2156. "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
  2157. "<item nick='trustworthyguy' role='moderator'>"+
  2158. "<reason>You&apos;re trustworthy</reason>"+
  2159. "</item>"+
  2160. "</query>"+
  2161. "</iq>");
  2162. /* <presence
  2163. * from='coven@chat.shakespeare.lit/thirdwitch'
  2164. * to='crone1@shakespeare.lit/desktop'>
  2165. * <x xmlns='http://jabber.org/protocol/muc#user'>
  2166. * <item affiliation='member'
  2167. * jid='hag66@shakespeare.lit/pda'
  2168. * role='moderator'/>
  2169. * </x>
  2170. * </presence>
  2171. */
  2172. presence = $pres({
  2173. 'from': 'lounge@localhost/trustworthyguy',
  2174. 'to': 'dummy@localhost/desktop'
  2175. })
  2176. .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'})
  2177. .c('item', {
  2178. 'jid': 'trustworthyguy@localhost',
  2179. 'affiliation': 'member',
  2180. 'role': 'moderator'
  2181. });
  2182. _converse.connection._dataRecv(test_utils.createRequest(presence));
  2183. info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0);
  2184. expect(info_msgs.pop().textContent).toBe("trustworthyguy is now a moderator");
  2185. view.onMessageSubmitted('/deop trustworthyguy Perhaps not');
  2186. expect(view.validateRoleChangeCommand.calls.count()).toBe(3);
  2187. expect(view.showChatEvent.calls.count()).toBe(1);
  2188. expect(view.modifyRole).toHaveBeenCalled();
  2189. expect(sent_IQ.toLocaleString()).toBe(
  2190. "<iq to='lounge@localhost' type='set' xmlns='jabber:client' id='"+IQ_id+"'>"+
  2191. "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
  2192. "<item nick='trustworthyguy' role='participant'>"+
  2193. "<reason>Perhaps not</reason>"+
  2194. "</item>"+
  2195. "</query>"+
  2196. "</iq>");
  2197. /* <presence
  2198. * from='coven@chat.shakespeare.lit/thirdwitch'
  2199. * to='crone1@shakespeare.lit/desktop'>
  2200. * <x xmlns='http://jabber.org/protocol/muc#user'>
  2201. * <item affiliation='member'
  2202. * jid='hag66@shakespeare.lit/pda'
  2203. * role='participant'/>
  2204. * </x>
  2205. * </presence>
  2206. */
  2207. presence = $pres({
  2208. 'from': 'lounge@localhost/trustworthyguy',
  2209. 'to': 'dummy@localhost/desktop'
  2210. }).c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'})
  2211. .c('item', {
  2212. 'jid': 'trustworthyguy@localhost',
  2213. 'affiliation': 'member',
  2214. 'role': 'participant'
  2215. });
  2216. _converse.connection._dataRecv(test_utils.createRequest(presence));
  2217. info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0);
  2218. expect(info_msgs.pop().textContent).toBe("trustworthyguy is no longer a moderator");
  2219. done();
  2220. }).catch(_.partial(console.error, _));
  2221. }));
  2222. it("/mute and /voice to mute and unmute a user",
  2223. mock.initConverseWithPromises(
  2224. null, ['rosterGroupsFetched'], {},
  2225. function (done, _converse) {
  2226. test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
  2227. var sent_IQ, IQ_id;
  2228. var sendIQ = _converse.connection.sendIQ;
  2229. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  2230. sent_IQ = iq;
  2231. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  2232. });
  2233. var view = _converse.chatboxviews.get('lounge@localhost');
  2234. spyOn(view, 'onMessageSubmitted').and.callThrough();
  2235. spyOn(view, 'modifyRole').and.callThrough();
  2236. spyOn(view, 'showErrorMessage').and.callThrough();
  2237. spyOn(view, 'showChatEvent').and.callThrough();
  2238. spyOn(view, 'validateRoleChangeCommand').and.callThrough();
  2239. // New user enters the room
  2240. /* <presence
  2241. * from='coven@chat.shakespeare.lit/thirdwitch'
  2242. * id='27C55F89-1C6A-459A-9EB5-77690145D624'
  2243. * to='crone1@shakespeare.lit/desktop'>
  2244. * <x xmlns='http://jabber.org/protocol/muc#user'>
  2245. * <item affiliation='member' role='participant'/>
  2246. * </x>
  2247. * </presence>
  2248. */
  2249. var presence = $pres({
  2250. 'from': 'lounge@localhost/annoyingGuy',
  2251. 'id':'27C55F89-1C6A-459A-9EB5-77690145D624',
  2252. 'to': 'dummy@localhost/desktop'
  2253. })
  2254. .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'})
  2255. .c('item', {
  2256. 'jid': 'annoyingguy@localhost',
  2257. 'affiliation': 'member',
  2258. 'role': 'participant'
  2259. });
  2260. _converse.connection._dataRecv(test_utils.createRequest(presence));
  2261. var info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0);
  2262. expect(info_msgs.pop().textContent).toBe("annoyingGuy has entered the room");
  2263. var textarea = view.el.querySelector('.chat-textarea')
  2264. textarea.value = '/mute';
  2265. view.keyPressed({
  2266. target: textarea,
  2267. preventDefault: _.noop,
  2268. keyCode: 13
  2269. });
  2270. expect(view.onMessageSubmitted).toHaveBeenCalled();
  2271. expect(view.validateRoleChangeCommand).toHaveBeenCalled();
  2272. expect(view.showErrorMessage).toHaveBeenCalledWith(
  2273. "Error: the \"mute\" command takes two arguments, the user's nickname and optionally a reason.",
  2274. true
  2275. );
  2276. expect(view.modifyRole).not.toHaveBeenCalled();
  2277. // Call now with the correct amount of arguments.
  2278. // XXX: Calling onMessageSubmitted directly, trying
  2279. // again via triggering Event doesn't work for some weird
  2280. // reason.
  2281. view.onMessageSubmitted('/mute annoyingGuy You\'re annoying');
  2282. expect(view.validateRoleChangeCommand.calls.count()).toBe(2);
  2283. expect(view.showErrorMessage.calls.count()).toBe(1);
  2284. expect(view.modifyRole).toHaveBeenCalled();
  2285. expect(sent_IQ.toLocaleString()).toBe(
  2286. "<iq to='lounge@localhost' type='set' xmlns='jabber:client' id='"+IQ_id+"'>"+
  2287. "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
  2288. "<item nick='annoyingGuy' role='visitor'>"+
  2289. "<reason>You&apos;re annoying</reason>"+
  2290. "</item>"+
  2291. "</query>"+
  2292. "</iq>");
  2293. /* <presence
  2294. * from='coven@chat.shakespeare.lit/thirdwitch'
  2295. * to='crone1@shakespeare.lit/desktop'>
  2296. * <x xmlns='http://jabber.org/protocol/muc#user'>
  2297. * <item affiliation='member'
  2298. * jid='hag66@shakespeare.lit/pda'
  2299. * role='visitor'/>
  2300. * </x>
  2301. * </presence>
  2302. */
  2303. presence = $pres({
  2304. 'from': 'lounge@localhost/annoyingGuy',
  2305. 'to': 'dummy@localhost/desktop'
  2306. })
  2307. .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'})
  2308. .c('item', {
  2309. 'jid': 'annoyingguy@localhost',
  2310. 'affiliation': 'member',
  2311. 'role': 'visitor'
  2312. });
  2313. _converse.connection._dataRecv(test_utils.createRequest(presence));
  2314. info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0);
  2315. expect(info_msgs.pop().textContent).toBe("annoyingGuy has been muted");
  2316. view.onMessageSubmitted('/voice annoyingGuy Now you can talk again');
  2317. expect(view.validateRoleChangeCommand.calls.count()).toBe(3);
  2318. expect(view.showChatEvent.calls.count()).toBe(1);
  2319. expect(view.modifyRole).toHaveBeenCalled();
  2320. expect(sent_IQ.toLocaleString()).toBe(
  2321. "<iq to='lounge@localhost' type='set' xmlns='jabber:client' id='"+IQ_id+"'>"+
  2322. "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
  2323. "<item nick='annoyingGuy' role='participant'>"+
  2324. "<reason>Now you can talk again</reason>"+
  2325. "</item>"+
  2326. "</query>"+
  2327. "</iq>");
  2328. /* <presence
  2329. * from='coven@chat.shakespeare.lit/thirdwitch'
  2330. * to='crone1@shakespeare.lit/desktop'>
  2331. * <x xmlns='http://jabber.org/protocol/muc#user'>
  2332. * <item affiliation='member'
  2333. * jid='hag66@shakespeare.lit/pda'
  2334. * role='visitor'/>
  2335. * </x>
  2336. * </presence>
  2337. */
  2338. presence = $pres({
  2339. 'from': 'lounge@localhost/annoyingGuy',
  2340. 'to': 'dummy@localhost/desktop'
  2341. })
  2342. .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'})
  2343. .c('item', {
  2344. 'jid': 'annoyingguy@localhost',
  2345. 'affiliation': 'member',
  2346. 'role': 'participant'
  2347. });
  2348. _converse.connection._dataRecv(test_utils.createRequest(presence));
  2349. info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0);
  2350. expect(info_msgs.pop().textContent).toBe("annoyingGuy has been given a voice again");
  2351. done();
  2352. }).catch(_.partial(console.error, _));
  2353. }));
  2354. });
  2355. describe("When attempting to enter a chatroom", function () {
  2356. it("will show an error message if the room requires a password",
  2357. mock.initConverseWithPromises(
  2358. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  2359. function (done, _converse) {
  2360. test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
  2361. .then(function () {
  2362. var view = _converse.chatboxviews.get('problematic@muc.localhost');
  2363. spyOn(view, 'renderPasswordForm').and.callThrough();
  2364. var presence = $pres().attrs({
  2365. from:'problematic@muc.localhost/dummy',
  2366. id:'n13mt3l',
  2367. to:'dummy@localhost/pda',
  2368. type:'error'})
  2369. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  2370. .c('error').attrs({by:'lounge@localhost', type:'auth'})
  2371. .c('not-authorized').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'});
  2372. _converse.connection._dataRecv(test_utils.createRequest(presence));
  2373. var $chat_body = $(view.el).find('.chatroom-body');
  2374. expect(view.renderPasswordForm).toHaveBeenCalled();
  2375. expect($chat_body.find('form.chatroom-form').length).toBe(1);
  2376. expect($chat_body.find('legend').text()).toBe('This chatroom requires a password');
  2377. // Let's submit the form
  2378. spyOn(view, 'join');
  2379. var input_el = view.el.querySelector('[name="password"]');
  2380. input_el.value = 'secret';
  2381. view.el.querySelector('input[type=submit]').click();
  2382. expect(view.join).toHaveBeenCalledWith('dummy', 'secret');
  2383. done();
  2384. }).catch(_.partial(console.error, _));
  2385. }));
  2386. it("will show an error message if the room is members-only and the user not included",
  2387. mock.initConverseWithPromises(
  2388. null, ['rosterGroupsFetched'], {},
  2389. function (done, _converse) {
  2390. test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
  2391. .then(function () {
  2392. var presence = $pres().attrs({
  2393. from:'problematic@muc.localhost/dummy',
  2394. id:'n13mt3l',
  2395. to:'dummy@localhost/pda',
  2396. type:'error'})
  2397. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  2398. .c('error').attrs({by:'lounge@localhost', type:'auth'})
  2399. .c('registration-required').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  2400. var view = _converse.chatboxviews.get('problematic@muc.localhost');
  2401. spyOn(view, 'showErrorMessage').and.callThrough();
  2402. _converse.connection._dataRecv(test_utils.createRequest(presence));
  2403. expect($(view.el).find('.chatroom-body p:last').text()).toBe('You are not on the member list of this room.');
  2404. done();
  2405. }).catch(_.partial(console.error, _));
  2406. }));
  2407. it("will show an error message if the user has been banned",
  2408. mock.initConverseWithPromises(
  2409. null, ['rosterGroupsFetched'], {},
  2410. function (done, _converse) {
  2411. test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
  2412. .then(function () {
  2413. var presence = $pres().attrs({
  2414. from:'problematic@muc.localhost/dummy',
  2415. id:'n13mt3l',
  2416. to:'dummy@localhost/pda',
  2417. type:'error'})
  2418. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  2419. .c('error').attrs({by:'lounge@localhost', type:'auth'})
  2420. .c('forbidden').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  2421. var view = _converse.chatboxviews.get('problematic@muc.localhost');
  2422. spyOn(view, 'showErrorMessage').and.callThrough();
  2423. _converse.connection._dataRecv(test_utils.createRequest(presence));
  2424. expect($(view.el).find('.chatroom-body p:last').text()).toBe('You have been banned from this room.');
  2425. done();
  2426. }).catch(_.partial(console.error, _));
  2427. }));
  2428. it("will render a nickname form if a nickname conflict happens and muc_nickname_from_jid=false",
  2429. mock.initConverseWithPromises(
  2430. null, ['rosterGroupsFetched'], {},
  2431. function (done, _converse) {
  2432. test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
  2433. .then(function () {
  2434. var presence = $pres().attrs({
  2435. from:'problematic@muc.localhost/dummy',
  2436. id:'n13mt3l',
  2437. to:'dummy@localhost/pda',
  2438. type:'error'})
  2439. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  2440. .c('error').attrs({by:'lounge@localhost', type:'cancel'})
  2441. .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  2442. var view = _converse.chatboxviews.get('problematic@muc.localhost');
  2443. spyOn(view, 'showErrorMessage').and.callThrough();
  2444. _converse.connection._dataRecv(test_utils.createRequest(presence));
  2445. expect($(view.el).find('.chatroom-body form.chatroom-form label:first').text()).toBe('Please choose your nickname');
  2446. var $input = $(view.el).find('.chatroom-body form.chatroom-form input:first');
  2447. $input.val('nicky');
  2448. view.el.querySelector('input[type=submit]').click();
  2449. done();
  2450. }).catch(_.partial(console.error, _));
  2451. }));
  2452. it("will automatically choose a new nickname if a nickname conflict happens and muc_nickname_from_jid=true",
  2453. mock.initConverseWithPromises(
  2454. null, ['rosterGroupsFetched'], {},
  2455. function (done, _converse) {
  2456. test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
  2457. .then(function () {
  2458. /* <presence
  2459. * from='coven@chat.shakespeare.lit/thirdwitch'
  2460. * id='n13mt3l'
  2461. * to='hag66@shakespeare.lit/pda'
  2462. * type='error'>
  2463. * <x xmlns='http://jabber.org/protocol/muc'/>
  2464. * <error by='coven@chat.shakespeare.lit' type='cancel'>
  2465. * <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
  2466. * </error>
  2467. * </presence>
  2468. */
  2469. _converse.muc_nickname_from_jid = true;
  2470. var attrs = {
  2471. from:'problematic@muc.localhost/dummy',
  2472. to:'dummy@localhost/pda',
  2473. type:'error'
  2474. };
  2475. attrs.id = new Date().getTime();
  2476. var presence = $pres().attrs(attrs)
  2477. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  2478. .c('error').attrs({by:'problematic@muc.localhost', type:'cancel'})
  2479. .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  2480. var view = _converse.chatboxviews.get('problematic@muc.localhost');
  2481. spyOn(view, 'showErrorMessage').and.callThrough();
  2482. spyOn(view, 'join').and.callThrough();
  2483. // Simulate repeatedly that there's already someone in the room
  2484. // with that nickname
  2485. _converse.connection._dataRecv(test_utils.createRequest(presence));
  2486. expect(view.join).toHaveBeenCalledWith('dummy-2');
  2487. attrs.from = 'problematic@muc.localhost/dummy-2';
  2488. attrs.id = new Date().getTime();
  2489. presence = $pres().attrs(attrs)
  2490. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  2491. .c('error').attrs({by:'problematic@muc.localhost', type:'cancel'})
  2492. .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  2493. _converse.connection._dataRecv(test_utils.createRequest(presence));
  2494. expect(view.join).toHaveBeenCalledWith('dummy-3');
  2495. attrs.from = 'problematic@muc.localhost/dummy-3';
  2496. attrs.id = new Date().getTime();
  2497. presence = $pres().attrs(attrs)
  2498. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  2499. .c('error').attrs({by:'problematic@muc.localhost', type:'cancel'})
  2500. .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  2501. _converse.connection._dataRecv(test_utils.createRequest(presence));
  2502. expect(view.join).toHaveBeenCalledWith('dummy-4');
  2503. done();
  2504. }).catch(_.partial(console.error, _));
  2505. }));
  2506. it("will show an error message if the user is not allowed to have created the room",
  2507. mock.initConverseWithPromises(
  2508. null, ['rosterGroupsFetched'], {},
  2509. function (done, _converse) {
  2510. test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
  2511. .then(function () {
  2512. var presence = $pres().attrs({
  2513. from:'problematic@muc.localhost/dummy',
  2514. id:'n13mt3l',
  2515. to:'dummy@localhost/pda',
  2516. type:'error'})
  2517. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  2518. .c('error').attrs({by:'lounge@localhost', type:'cancel'})
  2519. .c('not-allowed').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  2520. var view = _converse.chatboxviews.get('problematic@muc.localhost');
  2521. spyOn(view, 'showErrorMessage').and.callThrough();
  2522. _converse.connection._dataRecv(test_utils.createRequest(presence));
  2523. expect($(view.el).find('.chatroom-body p:last').text()).toBe('You are not allowed to create new rooms.');
  2524. done();
  2525. }).catch(_.partial(console.error, _));
  2526. }));
  2527. it("will show an error message if the user's nickname doesn't conform to room policy",
  2528. mock.initConverseWithPromises(
  2529. null, ['rosterGroupsFetched'], {},
  2530. function (done, _converse) {
  2531. test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
  2532. .then(function () {
  2533. var presence = $pres().attrs({
  2534. from:'problematic@muc.localhost/dummy',
  2535. id:'n13mt3l',
  2536. to:'dummy@localhost/pda',
  2537. type:'error'})
  2538. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  2539. .c('error').attrs({by:'lounge@localhost', type:'cancel'})
  2540. .c('not-acceptable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  2541. var view = _converse.chatboxviews.get('problematic@muc.localhost');
  2542. spyOn(view, 'showErrorMessage').and.callThrough();
  2543. _converse.connection._dataRecv(test_utils.createRequest(presence));
  2544. expect($(view.el).find('.chatroom-body p:last').text()).toBe("Your nickname doesn't conform to this room's policies.");
  2545. done();
  2546. }).catch(_.partial(console.error, _));
  2547. }));
  2548. it("will show an error message if the room doesn't yet exist",
  2549. mock.initConverseWithPromises(
  2550. null, ['rosterGroupsFetched'], {},
  2551. function (done, _converse) {
  2552. test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
  2553. .then(function () {
  2554. var presence = $pres().attrs({
  2555. from:'problematic@muc.localhost/dummy',
  2556. id:'n13mt3l',
  2557. to:'dummy@localhost/pda',
  2558. type:'error'})
  2559. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  2560. .c('error').attrs({by:'lounge@localhost', type:'cancel'})
  2561. .c('item-not-found').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  2562. var view = _converse.chatboxviews.get('problematic@muc.localhost');
  2563. spyOn(view, 'showErrorMessage').and.callThrough();
  2564. _converse.connection._dataRecv(test_utils.createRequest(presence));
  2565. expect($(view.el).find('.chatroom-body p:last').text()).toBe("This room does not (yet) exist.");
  2566. done();
  2567. }).catch(_.partial(console.error, _));
  2568. }));
  2569. it("will show an error message if the room has reached its maximum number of occupants",
  2570. mock.initConverseWithPromises(
  2571. null, ['rosterGroupsFetched'], {},
  2572. function (done, _converse) {
  2573. test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
  2574. .then(function () {
  2575. var presence = $pres().attrs({
  2576. from:'problematic@muc.localhost/dummy',
  2577. id:'n13mt3l',
  2578. to:'dummy@localhost/pda',
  2579. type:'error'})
  2580. .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
  2581. .c('error').attrs({by:'lounge@localhost', type:'cancel'})
  2582. .c('service-unavailable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
  2583. var view = _converse.chatboxviews.get('problematic@muc.localhost');
  2584. spyOn(view, 'showErrorMessage').and.callThrough();
  2585. _converse.connection._dataRecv(test_utils.createRequest(presence));
  2586. expect($(view.el).find('.chatroom-body p:last').text()).toBe("This room has reached its maximum number of occupants.");
  2587. done();
  2588. }).catch(_.partial(console.error, _));
  2589. }));
  2590. });
  2591. describe("Someone being invited to a chat room", function () {
  2592. it("will first be added to the member list if the chat room is members only",
  2593. mock.initConverseWithPromises(
  2594. null, ['rosterGroupsFetched'], {},
  2595. function (done, _converse) {
  2596. var sent_IQs = [], IQ_ids = [];
  2597. var sendIQ = _converse.connection.sendIQ;
  2598. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  2599. sent_IQs.push(iq);
  2600. IQ_ids.push(sendIQ.bind(this)(iq, callback, errback));
  2601. });
  2602. _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'dummy'});
  2603. // State that the chat is members-only via the features IQ
  2604. var features_stanza = $iq({
  2605. from: 'coven@chat.shakespeare.lit',
  2606. 'id': IQ_ids.pop(),
  2607. 'to': 'dummy@localhost/desktop',
  2608. 'type': 'result'
  2609. })
  2610. .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'})
  2611. .c('identity', {
  2612. 'category': 'conference',
  2613. 'name': 'A Dark Cave',
  2614. 'type': 'text'
  2615. }).up()
  2616. .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up()
  2617. .c('feature', {'var': 'muc_hidden'}).up()
  2618. .c('feature', {'var': 'muc_temporary'}).up()
  2619. .c('feature', {'var': 'muc_membersonly'}).up();
  2620. _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
  2621. var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
  2622. expect(view.model.get('membersonly')).toBeTruthy();
  2623. test_utils.createContacts(_converse, 'current');
  2624. var sent_stanza, sent_id;
  2625. spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
  2626. if (stanza.nodeTree && stanza.nodeTree.nodeName === 'message') {
  2627. sent_id = stanza.nodeTree.getAttribute('id');
  2628. sent_stanza = stanza;
  2629. }
  2630. });
  2631. var name = mock.cur_names[0];
  2632. var invitee_jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
  2633. var reason = "Please join this chat room";
  2634. view.model.directInvite(invitee_jid, reason);
  2635. // Check in reverse order that we requested all three lists
  2636. // (member, owner and admin).
  2637. var admin_iq_id = IQ_ids.pop();
  2638. var owner_iq_id = IQ_ids.pop();
  2639. var member_iq_id = IQ_ids.pop();
  2640. expect(sent_IQs.pop().toLocaleString()).toBe(
  2641. "<iq to='coven@chat.shakespeare.lit' type='get' xmlns='jabber:client' id='"+admin_iq_id+"'>"+
  2642. "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
  2643. "<item affiliation='admin'/>"+
  2644. "</query>"+
  2645. "</iq>");
  2646. expect(sent_IQs.pop().toLocaleString()).toBe(
  2647. "<iq to='coven@chat.shakespeare.lit' type='get' xmlns='jabber:client' id='"+owner_iq_id+"'>"+
  2648. "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
  2649. "<item affiliation='owner'/>"+
  2650. "</query>"+
  2651. "</iq>");
  2652. expect(sent_IQs.pop().toLocaleString()).toBe(
  2653. "<iq to='coven@chat.shakespeare.lit' type='get' xmlns='jabber:client' id='"+member_iq_id+"'>"+
  2654. "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
  2655. "<item affiliation='member'/>"+
  2656. "</query>"+
  2657. "</iq>");
  2658. /* Now the service sends the member list to the user
  2659. *
  2660. * <iq from='coven@chat.shakespeare.lit'
  2661. * id='member3'
  2662. * to='crone1@shakespeare.lit/desktop'
  2663. * type='result'>
  2664. * <query xmlns='http://jabber.org/protocol/muc#admin'>
  2665. * <item affiliation='member'
  2666. * jid='hag66@shakespeare.lit'
  2667. * nick='thirdwitch'
  2668. * role='participant'/>
  2669. * </query>
  2670. * </iq>
  2671. */
  2672. var member_list_stanza = $iq({
  2673. 'from': 'coven@chat.shakespeare.lit',
  2674. 'id': member_iq_id,
  2675. 'to': 'dummy@localhost/resource',
  2676. 'type': 'result'
  2677. }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN})
  2678. .c('item', {
  2679. 'affiliation': 'member',
  2680. 'jid': 'hag66@shakespeare.lit',
  2681. 'nick': 'thirdwitch',
  2682. 'role': 'participant'
  2683. });
  2684. _converse.connection._dataRecv(test_utils.createRequest(member_list_stanza));
  2685. var admin_list_stanza = $iq({
  2686. 'from': 'coven@chat.shakespeare.lit',
  2687. 'id': admin_iq_id,
  2688. 'to': 'dummy@localhost/resource',
  2689. 'type': 'result'
  2690. }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN})
  2691. .c('item', {
  2692. 'affiliation': 'admin',
  2693. 'jid': 'wiccarocks@shakespeare.lit',
  2694. 'nick': 'secondwitch'
  2695. });
  2696. _converse.connection._dataRecv(test_utils.createRequest(admin_list_stanza));
  2697. var owner_list_stanza = $iq({
  2698. 'from': 'coven@chat.shakespeare.lit',
  2699. 'id': owner_iq_id,
  2700. 'to': 'dummy@localhost/resource',
  2701. 'type': 'result'
  2702. }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN})
  2703. .c('item', {
  2704. 'affiliation': 'owner',
  2705. 'jid': 'crone1@shakespeare.lit',
  2706. });
  2707. _converse.connection._dataRecv(test_utils.createRequest(owner_list_stanza));
  2708. test_utils.waitUntil(function () {
  2709. return IQ_ids.length;
  2710. }, 300).then(function () {
  2711. // Check that the member list now gets updated
  2712. var iq = "<iq to='coven@chat.shakespeare.lit' type='set' xmlns='jabber:client' id='"+IQ_ids.pop()+"'>"+
  2713. "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
  2714. "<item affiliation='member' jid='"+invitee_jid+"'>"+
  2715. "<reason>Please join this chat room</reason>"+
  2716. "</item>"+
  2717. "</query>"+
  2718. "</iq>";
  2719. test_utils.waitUntil(function () {
  2720. return _.includes(_.invokeMap(sent_IQs, Object.prototype.toLocaleString), iq);
  2721. }, 300).then(function () {
  2722. // Finally check that the user gets invited.
  2723. expect(sent_stanza.toLocaleString()).toBe( // Strophe adds the xmlns attr (although not in spec)
  2724. "<message from='dummy@localhost/resource' to='"+invitee_jid+"' id='"+sent_id+"' xmlns='jabber:client'>"+
  2725. "<x xmlns='jabber:x:conference' jid='coven@chat.shakespeare.lit' reason='Please join this chat room'/>"+
  2726. "</message>"
  2727. );
  2728. done();
  2729. });
  2730. });
  2731. }));
  2732. });
  2733. describe("The affiliations delta", function () {
  2734. it("can be computed in various ways",
  2735. mock.initConverseWithPromises(
  2736. null, ['rosterGroupsFetched'], {},
  2737. function (done, _converse) {
  2738. test_utils.openChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'dummy');
  2739. var roomview = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
  2740. var exclude_existing = false;
  2741. var remove_absentees = false;
  2742. var new_list = [];
  2743. var old_list = [];
  2744. var delta = u.computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list);
  2745. expect(delta.length).toBe(0);
  2746. new_list = [{'jid': 'wiccarocks@shakespeare.lit', 'affiliation': 'member'}];
  2747. old_list = [{'jid': 'wiccarocks@shakespeare.lit', 'affiliation': 'member'}];
  2748. delta = u.computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list);
  2749. expect(delta.length).toBe(0);
  2750. // When remove_absentees is false, then affiliations in the old
  2751. // list which are not in the new one won't be removed.
  2752. old_list = [{'jid': 'oldhag666@shakespeare.lit', 'affiliation': 'owner'},
  2753. {'jid': 'wiccarocks@shakespeare.lit', 'affiliation': 'member'}];
  2754. delta = u.computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list);
  2755. expect(delta.length).toBe(0);
  2756. // With exclude_existing set to false, any changed affiliations
  2757. // will be included in the delta (i.e. existing affiliations
  2758. // are included in the comparison).
  2759. old_list = [{'jid': 'wiccarocks@shakespeare.lit', 'affiliation': 'owner'}];
  2760. delta = u.computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list);
  2761. expect(delta.length).toBe(1);
  2762. expect(delta[0].jid).toBe('wiccarocks@shakespeare.lit');
  2763. expect(delta[0].affiliation).toBe('member');
  2764. // To also remove affiliations from the old list which are not
  2765. // in the new list, we set remove_absentees to true
  2766. remove_absentees = true;
  2767. old_list = [{'jid': 'oldhag666@shakespeare.lit', 'affiliation': 'owner'},
  2768. {'jid': 'wiccarocks@shakespeare.lit', 'affiliation': 'member'}];
  2769. delta = u.computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list);
  2770. expect(delta.length).toBe(1);
  2771. expect(delta[0].jid).toBe('oldhag666@shakespeare.lit');
  2772. expect(delta[0].affiliation).toBe('none');
  2773. delta = u.computeAffiliationsDelta(exclude_existing, remove_absentees, [], old_list);
  2774. expect(delta.length).toBe(2);
  2775. expect(delta[0].jid).toBe('oldhag666@shakespeare.lit');
  2776. expect(delta[0].affiliation).toBe('none');
  2777. expect(delta[1].jid).toBe('wiccarocks@shakespeare.lit');
  2778. expect(delta[1].affiliation).toBe('none');
  2779. // To only add a user if they don't already have an
  2780. // affiliation, we set 'exclude_existing' to true
  2781. exclude_existing = true;
  2782. old_list = [{'jid': 'wiccarocks@shakespeare.lit', 'affiliation': 'owner'}];
  2783. delta = u.computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list);
  2784. expect(delta.length).toBe(0);
  2785. done();
  2786. }));
  2787. });
  2788. describe("The \"Chatrooms\" section", function () {
  2789. it("contains a link to a modal through which a new chatroom can be created",
  2790. mock.initConverseWithPromises(
  2791. null, ['rosterGroupsFetched'], {},
  2792. function (done, _converse) {
  2793. test_utils.openControlBox();
  2794. var roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
  2795. roomspanel.el.querySelector('.trigger-add-chatrooms-modal').click();
  2796. test_utils.closeControlBox(_converse);
  2797. const modal = roomspanel.add_room_modal;
  2798. test_utils.waitUntil(function () {
  2799. return u.isVisible(modal.el);
  2800. }, 1000).then(function () {
  2801. spyOn(_converse.ChatRoom.prototype, 'getRoomFeatures').and.callFake(function () {
  2802. var deferred = new $.Deferred();
  2803. deferred.resolve();
  2804. return deferred.promise();
  2805. });
  2806. roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  2807. modal.el.querySelector('input[name="chatroom"]').value = 'lounce@muc.localhost';
  2808. modal.el.querySelector('form input[type="submit"]').click();
  2809. expect($('.chatroom:visible').length).toBe(1); // There should now be an open chatroom
  2810. done();
  2811. }).catch(_.partial(console.error, _));
  2812. }));
  2813. it("contains a link to a modal which can list rooms publically available on the server",
  2814. mock.initConverseWithPromises(
  2815. null, ['rosterGroupsFetched'], {},
  2816. function (done, _converse) {
  2817. var sendIQ = _converse.connection.sendIQ;
  2818. var sent_stanza, IQ_id;
  2819. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  2820. sent_stanza = iq;
  2821. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  2822. });
  2823. test_utils.openControlBox();
  2824. var roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
  2825. roomspanel.el.querySelector('.trigger-list-chatrooms-modal').click();
  2826. test_utils.closeControlBox(_converse);
  2827. const modal = roomspanel.list_rooms_modal;
  2828. test_utils.waitUntil(function () {
  2829. return u.isVisible(modal.el);
  2830. }, 1000).then(function () {
  2831. spyOn(_converse.ChatRoom.prototype, 'getRoomFeatures').and.callFake(function () {
  2832. var deferred = new $.Deferred();
  2833. deferred.resolve();
  2834. return deferred.promise();
  2835. });
  2836. roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  2837. // See: http://xmpp.org/extensions/xep-0045.html#disco-rooms
  2838. expect(modal.el.querySelectorAll('.available-chatrooms li').length).toBe(0);
  2839. const input = modal.el.querySelector('input[name="server"]').value = 'chat.shakespear.lit';
  2840. modal.el.querySelector('input[type="submit"]').click();
  2841. expect(sent_stanza.toLocaleString()).toBe(
  2842. "<iq to='chat.shakespear.lit' from='dummy@localhost/resource' type='get' xmlns='jabber:client' id='"+IQ_id+"'>"+
  2843. "<query xmlns='http://jabber.org/protocol/disco#items'/>"+
  2844. "</iq>"
  2845. );
  2846. var iq = $iq({
  2847. from:'muc.localhost',
  2848. to:'dummy@localhost/pda',
  2849. id: IQ_id,
  2850. type:'result'
  2851. }).c('query')
  2852. .c('item', { jid:'heath@chat.shakespeare.lit', name:'A Lonely Heath'}).up()
  2853. .c('item', { jid:'coven@chat.shakespeare.lit', name:'A Dark Cave'}).up()
  2854. .c('item', { jid:'forres@chat.shakespeare.lit', name:'The Palace'}).up()
  2855. .c('item', { jid:'inverness@chat.shakespeare.lit', name:'Macbeth&apos;s Castle'}).nodeTree;
  2856. _converse.connection._dataRecv(test_utils.createRequest(iq));
  2857. expect(modal.el.querySelectorAll('.available-chatrooms li').length).toBe(5);
  2858. const rooms = modal.el.querySelectorAll('.available-chatrooms li');
  2859. expect(rooms[0].textContent.trim()).toBe("Rooms found:");
  2860. expect(rooms[1].textContent.trim()).toBe("A Lonely Heath");
  2861. expect(rooms[2].textContent.trim()).toBe("A Dark Cave");
  2862. expect(rooms[3].textContent.trim()).toBe("The Palace");
  2863. expect(rooms[4].textContent.trim()).toBe("Macbeth's Castle");
  2864. rooms[4].querySelector('.open-room').click();
  2865. expect($('.chatroom:visible').length).toBe(1); // There should now be an open chatroom
  2866. var view = _converse.chatboxviews.get('inverness@chat.shakespeare.lit');
  2867. expect(view.el.querySelector('.chat-head-chatroom').textContent.trim()).toBe("Macbeth's Castle");
  2868. done();
  2869. }).catch(_.partial(console.error, _));
  2870. }));
  2871. it("shows the number of unread mentions received",
  2872. mock.initConverseWithPromises(
  2873. null, ['rosterGroupsFetched'], {'allow_bookmarks': false},
  2874. function (done, _converse) {
  2875. // XXX: we set `allow_bookmarks` to false, so that the rooms
  2876. // list gets rendered. Otherwise we would have to mock
  2877. // the bookmark stanza exchange.
  2878. test_utils.openControlBox();
  2879. var roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
  2880. expect(roomspanel.el.querySelectorAll('.available-room').length).toBe(0);
  2881. var room_jid = 'kitchen@conference.shakespeare.lit';
  2882. test_utils.openAndEnterChatRoom(
  2883. _converse, 'kitchen', 'conference.shakespeare.lit', 'fires').then(function () {
  2884. expect(roomspanel.el.querySelectorAll('.available-room').length).toBe(1);
  2885. expect(roomspanel.el.querySelectorAll('.msgs-indicator').length).toBe(0);
  2886. var view = _converse.chatboxviews.get(room_jid);
  2887. view.model.set({'minimized': true});
  2888. var contact_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
  2889. var message = 'fires: Your attention is required';
  2890. var nick = mock.chatroom_names[0];
  2891. view.model.onMessage($msg({
  2892. from: room_jid+'/'+nick,
  2893. id: (new Date()).getTime(),
  2894. to: 'dummy@localhost',
  2895. type: 'groupchat'
  2896. }).c('body').t(message).tree());
  2897. expect(roomspanel.el.querySelectorAll('.available-room').length).toBe(1);
  2898. expect(roomspanel.el.querySelectorAll('.msgs-indicator').length).toBe(1);
  2899. expect(roomspanel.el.querySelector('.msgs-indicator').textContent).toBe('1');
  2900. view.model.onMessage($msg({
  2901. 'from': room_jid+'/'+nick,
  2902. 'id': (new Date()).getTime(),
  2903. 'to': 'dummy@localhost',
  2904. 'type': 'groupchat'
  2905. }).c('body').t(message).tree());
  2906. expect(roomspanel.el.querySelectorAll('.available-room').length).toBe(1);
  2907. expect(roomspanel.el.querySelectorAll('.msgs-indicator').length).toBe(1);
  2908. expect(roomspanel.el.querySelector('.msgs-indicator').textContent).toBe('2');
  2909. view.model.set({'minimized': false});
  2910. expect(roomspanel.el.querySelectorAll('.available-room').length).toBe(1);
  2911. expect(roomspanel.el.querySelectorAll('.msgs-indicator').length).toBe(0);
  2912. done();
  2913. });
  2914. }));
  2915. describe("A Chat Status Notification", function () {
  2916. describe("A composing notification", function () {
  2917. it("will be shown if received",
  2918. mock.initConverseWithPromises(
  2919. null, ['rosterGroupsFetched'], {},
  2920. function (done, _converse) {
  2921. test_utils.openAndEnterChatRoom(
  2922. _converse, 'coven', 'chat.shakespeare.lit', 'some1').then(function () {
  2923. var room_jid = 'coven@chat.shakespeare.lit';
  2924. var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
  2925. var $chat_content = $(view.el).find('.chat-content');
  2926. /* <presence to="dummy@localhost/_converse.js-29092160"
  2927. * from="coven@chat.shakespeare.lit/some1">
  2928. * <x xmlns="http://jabber.org/protocol/muc#user">
  2929. * <item affiliation="owner" jid="dummy@localhost/_converse.js-29092160" role="moderator"/>
  2930. * <status code="110"/>
  2931. * </x>
  2932. * </presence></body>
  2933. */
  2934. var presence = $pres({
  2935. to: 'dummy@localhost/_converse.js-29092160',
  2936. from: 'coven@chat.shakespeare.lit/some1'
  2937. }).c('x', {xmlns: Strophe.NS.MUC_USER})
  2938. .c('item', {
  2939. 'affiliation': 'owner',
  2940. 'jid': 'dummy@localhost/_converse.js-29092160',
  2941. 'role': 'moderator'
  2942. }).up()
  2943. .c('status', {code: '110'});
  2944. _converse.connection._dataRecv(test_utils.createRequest(presence));
  2945. expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(2);
  2946. expect($chat_content.find('div.chat-info:first').html()).toBe("some1 has entered the room");
  2947. expect($chat_content.find('div.chat-info:last').html()).toBe("some1 is now a moderator");
  2948. presence = $pres({
  2949. to: 'dummy@localhost/_converse.js-29092160',
  2950. from: 'coven@chat.shakespeare.lit/newguy'
  2951. })
  2952. .c('x', {xmlns: Strophe.NS.MUC_USER})
  2953. .c('item', {
  2954. 'affiliation': 'none',
  2955. 'jid': 'newguy@localhost/_converse.js-290929789',
  2956. 'role': 'participant'
  2957. });
  2958. _converse.connection._dataRecv(test_utils.createRequest(presence));
  2959. expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(3);
  2960. expect($chat_content.find('div.chat-info:last').html()).toBe("newguy has entered the room");
  2961. presence = $pres({
  2962. to: 'dummy@localhost/_converse.js-29092160',
  2963. from: 'coven@chat.shakespeare.lit/nomorenicks'
  2964. })
  2965. .c('x', {xmlns: Strophe.NS.MUC_USER})
  2966. .c('item', {
  2967. 'affiliation': 'none',
  2968. 'jid': 'nomorenicks@localhost/_converse.js-290929789',
  2969. 'role': 'participant'
  2970. });
  2971. _converse.connection._dataRecv(test_utils.createRequest(presence));
  2972. expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(4);
  2973. expect($chat_content.find('div.chat-info:last').html()).toBe("nomorenicks has entered the room");
  2974. // See XEP-0085 http://xmpp.org/extensions/xep-0085.html#definitions
  2975. // <composing> state
  2976. var msg = $msg({
  2977. from: room_jid+'/newguy',
  2978. id: (new Date()).getTime(),
  2979. to: 'dummy@localhost',
  2980. type: 'groupchat'
  2981. }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  2982. view.model.onMessage(msg);
  2983. // Check that the notification appears inside the chatbox in the DOM
  2984. var events = view.el.querySelectorAll('.chat-event');
  2985. expect(events.length).toBe(4);
  2986. expect(events[0].textContent).toEqual('some1 has entered the room');
  2987. expect(events[1].textContent).toEqual('some1 is now a moderator');
  2988. expect(events[2].textContent).toEqual('newguy has entered the room');
  2989. expect(events[3].textContent).toEqual('nomorenicks has entered the room');
  2990. var notifications = view.el.querySelectorAll('.chat-state-notification');
  2991. expect(notifications.length).toBe(1);
  2992. expect(notifications[0].textContent).toEqual('newguy is typing');
  2993. const timeout_functions = [];
  2994. spyOn(window, 'setTimeout').and.callFake(function (func, delay) {
  2995. timeout_functions.push(func);
  2996. });
  2997. // Check that it doesn't appear twice
  2998. msg = $msg({
  2999. from: room_jid+'/newguy',
  3000. id: (new Date()).getTime(),
  3001. to: 'dummy@localhost',
  3002. type: 'groupchat'
  3003. }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  3004. view.model.onMessage(msg);
  3005. events = view.el.querySelectorAll('.chat-event');
  3006. expect(events.length).toBe(4);
  3007. expect(events[0].textContent).toEqual('some1 has entered the room');
  3008. expect(events[1].textContent).toEqual('some1 is now a moderator');
  3009. expect(events[2].textContent).toEqual('newguy has entered the room');
  3010. expect(events[3].textContent).toEqual('nomorenicks has entered the room');
  3011. notifications = view.el.querySelectorAll('.chat-state-notification');
  3012. expect(notifications.length).toBe(1);
  3013. expect(notifications[0].textContent).toEqual('newguy is typing');
  3014. expect(timeout_functions.length).toBe(1);
  3015. // <composing> state for a different occupant
  3016. msg = $msg({
  3017. from: room_jid+'/nomorenicks',
  3018. id: (new Date()).getTime(),
  3019. to: 'dummy@localhost',
  3020. type: 'groupchat'
  3021. }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  3022. view.model.onMessage(msg);
  3023. events = view.el.querySelectorAll('.chat-event');
  3024. expect(events.length).toBe(4);
  3025. expect(events[0].textContent).toEqual('some1 has entered the room');
  3026. expect(events[1].textContent).toEqual('some1 is now a moderator');
  3027. expect(events[2].textContent).toEqual('newguy has entered the room');
  3028. expect(events[3].textContent).toEqual('nomorenicks has entered the room');
  3029. notifications = view.el.querySelectorAll('.chat-state-notification');
  3030. expect(notifications.length).toBe(2);
  3031. expect(notifications[0].textContent).toEqual('newguy is typing');
  3032. expect(notifications[1].textContent).toEqual('nomorenicks is typing');
  3033. expect(timeout_functions.length).toBe(2);
  3034. // Check that new messages appear under the chat state
  3035. // notifications
  3036. msg = $msg({
  3037. from: 'lounge@localhost/some1',
  3038. id: (new Date()).getTime(),
  3039. to: 'dummy@localhost',
  3040. type: 'groupchat'
  3041. }).c('body').t('hello world').tree();
  3042. view.model.onMessage(msg);
  3043. var messages = view.el.querySelectorAll('.message');
  3044. expect(messages.length).toBe(8);
  3045. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  3046. expect(view.el.querySelector('.chat-msg .chat-msg-text').textContent).toBe('hello world');
  3047. // Test that the composing notifications get removed
  3048. // via timeout.
  3049. timeout_functions[0]();
  3050. events = view.el.querySelectorAll('.chat-event');
  3051. expect(events.length).toBe(4);
  3052. expect(events[0].textContent).toEqual('some1 has entered the room');
  3053. expect(events[1].textContent).toEqual('some1 is now a moderator');
  3054. expect(events[2].textContent).toEqual('newguy has entered the room');
  3055. expect(events[3].textContent).toEqual('nomorenicks has entered the room');
  3056. notifications = view.el.querySelectorAll('.chat-state-notification');
  3057. expect(notifications.length).toBe(1);
  3058. expect(notifications[0].textContent).toEqual('nomorenicks is typing');
  3059. timeout_functions[1]();
  3060. events = view.el.querySelectorAll('.chat-event');
  3061. expect(events.length).toBe(4);
  3062. expect(events[0].textContent).toEqual('some1 has entered the room');
  3063. expect(events[1].textContent).toEqual('some1 is now a moderator');
  3064. expect(events[2].textContent).toEqual('newguy has entered the room');
  3065. expect(events[3].textContent).toEqual('nomorenicks has entered the room');
  3066. notifications = view.el.querySelectorAll('.chat-state-notification');
  3067. expect(notifications.length).toBe(0);
  3068. done();
  3069. });
  3070. }));
  3071. });
  3072. describe("A paused notification", function () {
  3073. it("will be shown if received",
  3074. mock.initConverseWithPromises(
  3075. null, ['rosterGroupsFetched'], {},
  3076. function (done, _converse) {
  3077. test_utils.openChatRoom(_converse, "coven", 'chat.shakespeare.lit', 'some1');
  3078. var room_jid = 'coven@chat.shakespeare.lit';
  3079. var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
  3080. var $chat_content = $(view.el).find('.chat-content');
  3081. /* <presence to="dummy@localhost/_converse.js-29092160"
  3082. * from="coven@chat.shakespeare.lit/some1">
  3083. * <x xmlns="http://jabber.org/protocol/muc#user">
  3084. * <item affiliation="owner" jid="dummy@localhost/_converse.js-29092160" role="moderator"/>
  3085. * <status code="110"/>
  3086. * </x>
  3087. * </presence></body>
  3088. */
  3089. var presence = $pres({
  3090. to: 'dummy@localhost/_converse.js-29092160',
  3091. from: 'coven@chat.shakespeare.lit/some1'
  3092. }).c('x', {xmlns: Strophe.NS.MUC_USER})
  3093. .c('item', {
  3094. 'affiliation': 'owner',
  3095. 'jid': 'dummy@localhost/_converse.js-29092160',
  3096. 'role': 'moderator'
  3097. }).up()
  3098. .c('status', {code: '110'});
  3099. _converse.connection._dataRecv(test_utils.createRequest(presence));
  3100. expect($chat_content.find('div.chat-info:first').html()).toBe("some1 has entered the room");
  3101. presence = $pres({
  3102. to: 'dummy@localhost/_converse.js-29092160',
  3103. from: 'coven@chat.shakespeare.lit/newguy'
  3104. })
  3105. .c('x', {xmlns: Strophe.NS.MUC_USER})
  3106. .c('item', {
  3107. 'affiliation': 'none',
  3108. 'jid': 'newguy@localhost/_converse.js-290929789',
  3109. 'role': 'participant'
  3110. });
  3111. _converse.connection._dataRecv(test_utils.createRequest(presence));
  3112. expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(2);
  3113. expect($chat_content.find('div.chat-info:last').html()).toBe("newguy has entered the room");
  3114. presence = $pres({
  3115. to: 'dummy@localhost/_converse.js-29092160',
  3116. from: 'coven@chat.shakespeare.lit/nomorenicks'
  3117. })
  3118. .c('x', {xmlns: Strophe.NS.MUC_USER})
  3119. .c('item', {
  3120. 'affiliation': 'none',
  3121. 'jid': 'nomorenicks@localhost/_converse.js-290929789',
  3122. 'role': 'participant'
  3123. });
  3124. _converse.connection._dataRecv(test_utils.createRequest(presence));
  3125. expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(3);
  3126. expect($chat_content.find('div.chat-info:last').html()).toBe("nomorenicks has entered the room");
  3127. // See XEP-0085 http://xmpp.org/extensions/xep-0085.html#definitions
  3128. // <composing> state
  3129. var msg = $msg({
  3130. from: room_jid+'/newguy',
  3131. id: (new Date()).getTime(),
  3132. to: 'dummy@localhost',
  3133. type: 'groupchat'
  3134. }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  3135. view.model.onMessage(msg);
  3136. // Check that the notification appears inside the chatbox in the DOM
  3137. var events = view.el.querySelectorAll('.chat-event');
  3138. expect(events.length).toBe(3);
  3139. expect(events[0].textContent).toEqual('some1 has entered the room');
  3140. expect(events[1].textContent).toEqual('newguy has entered the room');
  3141. expect(events[2].textContent).toEqual('nomorenicks has entered the room');
  3142. var notifications = view.el.querySelectorAll('.chat-state-notification');
  3143. expect(notifications.length).toBe(1);
  3144. expect(notifications[0].textContent).toEqual('newguy is typing');
  3145. // Check that it doesn't appear twice
  3146. msg = $msg({
  3147. from: room_jid+'/newguy',
  3148. id: (new Date()).getTime(),
  3149. to: 'dummy@localhost',
  3150. type: 'groupchat'
  3151. }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  3152. view.model.onMessage(msg);
  3153. events = view.el.querySelectorAll('.chat-event');
  3154. expect(events.length).toBe(3);
  3155. expect(events[0].textContent).toEqual('some1 has entered the room');
  3156. expect(events[1].textContent).toEqual('newguy has entered the room');
  3157. expect(events[2].textContent).toEqual('nomorenicks has entered the room');
  3158. notifications = view.el.querySelectorAll('.chat-state-notification');
  3159. expect(notifications.length).toBe(1);
  3160. expect(notifications[0].textContent).toEqual('newguy is typing');
  3161. // <composing> state for a different occupant
  3162. msg = $msg({
  3163. from: room_jid+'/nomorenicks',
  3164. id: (new Date()).getTime(),
  3165. to: 'dummy@localhost',
  3166. type: 'groupchat'
  3167. }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  3168. view.model.onMessage(msg);
  3169. events = view.el.querySelectorAll('.chat-event');
  3170. expect(events.length).toBe(3);
  3171. expect(events[0].textContent).toEqual('some1 has entered the room');
  3172. expect(events[1].textContent).toEqual('newguy has entered the room');
  3173. expect(events[2].textContent).toEqual('nomorenicks has entered the room');
  3174. notifications = view.el.querySelectorAll('.chat-state-notification');
  3175. expect(notifications.length).toBe(2);
  3176. expect(notifications[0].textContent).toEqual('newguy is typing');
  3177. expect(notifications[1].textContent).toEqual('nomorenicks is typing');
  3178. // <paused> state from occupant who typed first
  3179. msg = $msg({
  3180. from: room_jid+'/newguy',
  3181. id: (new Date()).getTime(),
  3182. to: 'dummy@localhost',
  3183. type: 'groupchat'
  3184. }).c('body').c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  3185. view.model.onMessage(msg);
  3186. events = view.el.querySelectorAll('.chat-event');
  3187. expect(events.length).toBe(3);
  3188. expect(events[0].textContent).toEqual('some1 has entered the room');
  3189. expect(events[1].textContent).toEqual('newguy has entered the room');
  3190. expect(events[2].textContent).toEqual('nomorenicks has entered the room');
  3191. notifications = view.el.querySelectorAll('.chat-state-notification');
  3192. expect(notifications.length).toBe(2);
  3193. expect(notifications[0].textContent).toEqual('nomorenicks is typing');
  3194. expect(notifications[1].textContent).toEqual('newguy has stopped typing');
  3195. done();
  3196. }));
  3197. });
  3198. });
  3199. });
  3200. });
  3201. }));