modtools.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. window.addEventListener('converse-loaded', () => {
  2. const mock = window.mock;
  3. const test_utils = window.test_utils;
  4. const _ = converse.env._;
  5. const $iq = converse.env.$iq;
  6. const $pres = converse.env.$pres;
  7. const sizzle = converse.env.sizzle;
  8. const Strophe = converse.env.Strophe;
  9. const u = converse.env.utils;
  10. describe("The groupchat moderator tool", function () {
  11. it("allows you to set affiliations and roles",
  12. mock.initConverse(
  13. ['rosterGroupsFetched'], {},
  14. async function (done, _converse) {
  15. spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
  16. const muc_jid = 'lounge@montague.lit';
  17. let members = [
  18. {'jid': 'hag66@shakespeare.lit', 'nick': 'witch', 'affiliation': 'member'},
  19. {'jid': 'gower@shakespeare.lit', 'nick': 'gower', 'affiliation': 'member'},
  20. {'jid': 'wiccarocks@shakespeare.lit', 'nick': 'wiccan', 'affiliation': 'admin'},
  21. {'jid': 'crone1@shakespeare.lit', 'nick': 'thirdwitch', 'affiliation': 'owner'},
  22. {'jid': 'romeo@montague.lit', 'nick': 'romeo', 'affiliation': 'owner'},
  23. ];
  24. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo', [], members);
  25. const view = _converse.chatboxviews.get(muc_jid);
  26. await u.waitUntil(() => (view.model.occupants.length === 5), 1000);
  27. const textarea = view.el.querySelector('.chat-textarea');
  28. textarea.value = '/modtools';
  29. const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
  30. view.onKeyDown(enter);
  31. await u.waitUntil(() => view.showModeratorToolsModal.calls.count());
  32. const modal = view.modtools_modal;
  33. await u.waitUntil(() => u.isVisible(modal.el), 1000);
  34. let tab = modal.el.querySelector('#affiliations-tab');
  35. // Clear so that we don't match older stanzas
  36. _converse.connection.IQ_stanzas = [];
  37. tab.click();
  38. let select = modal.el.querySelector('.select-affiliation');
  39. expect(select.value).toBe('owner');
  40. select.value = 'admin';
  41. let button = modal.el.querySelector('.btn-primary[name="users_with_affiliation"]');
  42. button.click();
  43. await u.waitUntil(() => !modal.loading_users_with_affiliation);
  44. let user_els = modal.el.querySelectorAll('.list-group--users > li');
  45. expect(user_els.length).toBe(1);
  46. expect(user_els[0].querySelector('.list-group-item.active').textContent.trim()).toBe('JID: wiccarocks@shakespeare.lit');
  47. expect(user_els[0].querySelector('.list-group-item:nth-child(2n)').textContent.trim()).toBe('Nickname: wiccan');
  48. expect(user_els[0].querySelector('.list-group-item:nth-child(3n) div').textContent.trim()).toBe('Affiliation: admin');
  49. _converse.connection.IQ_stanzas = [];
  50. select.value = 'owner';
  51. button.click();
  52. await u.waitUntil(() => !modal.loading_users_with_affiliation);
  53. user_els = modal.el.querySelectorAll('.list-group--users > li');
  54. expect(user_els.length).toBe(2);
  55. expect(user_els[0].querySelector('.list-group-item.active').textContent.trim()).toBe('JID: romeo@montague.lit');
  56. expect(user_els[0].querySelector('.list-group-item:nth-child(2n)').textContent.trim()).toBe('Nickname: romeo');
  57. expect(user_els[0].querySelector('.list-group-item:nth-child(3n) div').textContent.trim()).toBe('Affiliation: owner');
  58. expect(user_els[1].querySelector('.list-group-item.active').textContent.trim()).toBe('JID: crone1@shakespeare.lit');
  59. expect(user_els[1].querySelector('.list-group-item:nth-child(2n)').textContent.trim()).toBe('Nickname: thirdwitch');
  60. expect(user_els[1].querySelector('.list-group-item:nth-child(3n) div').textContent.trim()).toBe('Affiliation: owner');
  61. const toggle = user_els[1].querySelector('.list-group-item:nth-child(3n) .toggle-form');
  62. const form = user_els[1].querySelector('.list-group-item:nth-child(3n) .affiliation-form');
  63. expect(u.hasClass('hidden', form)).toBeTruthy();
  64. toggle.click();
  65. expect(u.hasClass('hidden', form)).toBeFalsy();
  66. select = form.querySelector('.select-affiliation');
  67. expect(select.value).toBe('owner');
  68. select.value = 'admin';
  69. const input = form.querySelector('input[name="reason"]');
  70. input.value = "You're an admin now";
  71. const submit = form.querySelector('.btn-primary');
  72. submit.click();
  73. spyOn(_converse.ChatRoomOccupants.prototype, 'fetchMembers').and.callThrough();
  74. const sent_IQ = _converse.connection.IQ_stanzas.pop();
  75. expect(Strophe.serialize(sent_IQ)).toBe(
  76. `<iq id="${sent_IQ.getAttribute('id')}" to="lounge@montague.lit" type="set" xmlns="jabber:client">`+
  77. `<query xmlns="http://jabber.org/protocol/muc#admin">`+
  78. `<item affiliation="admin" jid="crone1@shakespeare.lit">`+
  79. `<reason>You&apos;re an admin now</reason>`+
  80. `</item>`+
  81. `</query>`+
  82. `</iq>`);
  83. _converse.connection.IQ_stanzas = [];
  84. const stanza = $iq({
  85. 'type': 'result',
  86. 'id': sent_IQ.getAttribute('id'),
  87. 'from': view.model.get('jid'),
  88. 'to': _converse.connection.jid
  89. });
  90. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  91. await u.waitUntil(() => view.model.occupants.fetchMembers.calls.count());
  92. members = [
  93. {'jid': 'hag66@shakespeare.lit', 'nick': 'witch', 'affiliation': 'member'},
  94. {'jid': 'gower@shakespeare.lit', 'nick': 'gower', 'affiliation': 'member'},
  95. {'jid': 'wiccarocks@shakespeare.lit', 'nick': 'wiccan', 'affiliation': 'admin'},
  96. {'jid': 'crone1@shakespeare.lit', 'nick': 'thirdwitch', 'affiliation': 'admin'},
  97. {'jid': 'romeo@montague.lit', 'nick': 'romeo', 'affiliation': 'owner'},
  98. ];
  99. await test_utils.returnMemberLists(_converse, muc_jid, members);
  100. await u.waitUntil(() => view.model.occupants.pluck('affiliation').filter(o => o === 'owner').length === 1);
  101. const alert = modal.el.querySelector('.alert-primary');
  102. expect(alert.textContent.trim()).toBe('Affiliation changed');
  103. user_els = modal.el.querySelectorAll('.list-group--users > li');
  104. expect(user_els.length).toBe(1);
  105. expect(user_els[0].querySelector('.list-group-item.active').textContent.trim()).toBe('JID: romeo@montague.lit');
  106. expect(user_els[0].querySelector('.list-group-item:nth-child(2n)').textContent.trim()).toBe('Nickname: romeo');
  107. expect(user_els[0].querySelector('.list-group-item:nth-child(3n) div').textContent.trim()).toBe('Affiliation: owner');
  108. tab = modal.el.querySelector('#roles-tab');
  109. tab.click();
  110. select = modal.el.querySelector('.select-role');
  111. expect(u.isVisible(select)).toBe(true);
  112. expect(select.value).toBe('moderator');
  113. button = modal.el.querySelector('.btn-primary[name="users_with_role"]');
  114. button.click();
  115. const roles_panel = modal.el.querySelector('#roles-tabpanel');
  116. await u.waitUntil(() => roles_panel.querySelectorAll('.list-group--users > li').length === 1);
  117. select.value = 'participant';
  118. button.click();
  119. await u.waitUntil(() => !modal.loading_users_with_affiliation);
  120. user_els = roles_panel.querySelectorAll('.list-group--users > li')
  121. expect(user_els.length).toBe(1);
  122. expect(user_els[0].textContent.trim()).toBe('No users with that role found.');
  123. done();
  124. }));
  125. it("allows you to filter affiliation search results",
  126. mock.initConverse(
  127. ['rosterGroupsFetched'], {},
  128. async function (done, _converse) {
  129. spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
  130. const muc_jid = 'lounge@montague.lit';
  131. const members = [
  132. {'jid': 'hag66@shakespeare.lit', 'nick': 'witch', 'affiliation': 'member'},
  133. {'jid': 'gower@shakespeare.lit', 'nick': 'gower', 'affiliation': 'member'},
  134. {'jid': 'wiccarocks@shakespeare.lit', 'nick': 'wiccan', 'affiliation': 'member'},
  135. {'jid': 'crone1@shakespeare.lit', 'nick': 'thirdwitch', 'affiliation': 'member'},
  136. {'jid': 'romeo@montague.lit', 'nick': 'romeo', 'affiliation': 'member'},
  137. {'jid': 'juliet@capulet.lit', 'nick': 'juliet', 'affiliation': 'member'},
  138. ];
  139. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo', [], members);
  140. const view = _converse.chatboxviews.get(muc_jid);
  141. await u.waitUntil(() => (view.model.occupants.length === 6), 1000);
  142. const textarea = view.el.querySelector('.chat-textarea');
  143. textarea.value = '/modtools';
  144. const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
  145. view.onKeyDown(enter);
  146. await u.waitUntil(() => view.showModeratorToolsModal.calls.count());
  147. const modal = view.modtools_modal;
  148. await u.waitUntil(() => u.isVisible(modal.el), 1000);
  149. // Clear so that we don't match older stanzas
  150. _converse.connection.IQ_stanzas = [];
  151. const select = modal.el.querySelector('.select-affiliation');
  152. expect(select.value).toBe('owner');
  153. select.value = 'member';
  154. const button = modal.el.querySelector('.btn-primary[name="users_with_affiliation"]');
  155. button.click();
  156. await u.waitUntil(() => !modal.loading_users_with_affiliation);
  157. const user_els = modal.el.querySelectorAll('.list-group--users > li');
  158. expect(user_els.length).toBe(6);
  159. const nicks = Array.from(modal.el.querySelectorAll('.list-group--users > li')).map(el => el.getAttribute('data-nick'));
  160. expect(nicks.join(' ')).toBe('gower juliet romeo thirdwitch wiccan witch');
  161. const filter = modal.el.querySelector('[name="filter"]');
  162. expect(filter).not.toBe(null);
  163. filter.value = 'romeo';
  164. u.triggerEvent(filter, "keyup", "KeyboardEvent");
  165. await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 1));
  166. filter.value = 'r';
  167. u.triggerEvent(filter, "keyup", "KeyboardEvent");
  168. await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 3));
  169. filter.value = 'gower';
  170. u.triggerEvent(filter, "keyup", "KeyboardEvent");
  171. await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 1));
  172. done();
  173. }));
  174. it("allows you to filter role search results",
  175. mock.initConverse(
  176. ['rosterGroupsFetched'], {},
  177. async function (done, _converse) {
  178. spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
  179. const muc_jid = 'lounge@montague.lit';
  180. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo', []);
  181. const view = _converse.chatboxviews.get(muc_jid);
  182. _converse.connection._dataRecv(test_utils.createRequest(
  183. $pres({to: _converse.jid, from: `${muc_jid}/nomorenicks`})
  184. .c('x', {xmlns: Strophe.NS.MUC_USER})
  185. .c('item', {
  186. 'affiliation': 'none',
  187. 'jid': `nomorenicks@montague.lit`,
  188. 'role': 'participant'
  189. })
  190. ));
  191. _converse.connection._dataRecv(test_utils.createRequest(
  192. $pres({to: _converse.jid, from: `${muc_jid}/newb`})
  193. .c('x', {xmlns: Strophe.NS.MUC_USER})
  194. .c('item', {
  195. 'affiliation': 'none',
  196. 'jid': `newb@montague.lit`,
  197. 'role': 'participant'
  198. })
  199. ));
  200. _converse.connection._dataRecv(test_utils.createRequest(
  201. $pres({to: _converse.jid, from: `${muc_jid}/some1`})
  202. .c('x', {xmlns: Strophe.NS.MUC_USER})
  203. .c('item', {
  204. 'affiliation': 'none',
  205. 'jid': `some1@montague.lit`,
  206. 'role': 'participant'
  207. })
  208. ));
  209. _converse.connection._dataRecv(test_utils.createRequest(
  210. $pres({to: _converse.jid, from: `${muc_jid}/oldhag`})
  211. .c('x', {xmlns: Strophe.NS.MUC_USER})
  212. .c('item', {
  213. 'affiliation': 'none',
  214. 'jid': `oldhag@montague.lit`,
  215. 'role': 'participant'
  216. })
  217. ));
  218. _converse.connection._dataRecv(test_utils.createRequest(
  219. $pres({to: _converse.jid, from: `${muc_jid}/crone`})
  220. .c('x', {xmlns: Strophe.NS.MUC_USER})
  221. .c('item', {
  222. 'affiliation': 'none',
  223. 'jid': `crone@montague.lit`,
  224. 'role': 'participant'
  225. })
  226. ));
  227. _converse.connection._dataRecv(test_utils.createRequest(
  228. $pres({to: _converse.jid, from: `${muc_jid}/tux`})
  229. .c('x', {xmlns: Strophe.NS.MUC_USER})
  230. .c('item', {
  231. 'affiliation': 'none',
  232. 'jid': `tux@montague.lit`,
  233. 'role': 'participant'
  234. })
  235. ));
  236. await u.waitUntil(() => (view.model.occupants.length === 7), 1000);
  237. const textarea = view.el.querySelector('.chat-textarea');
  238. textarea.value = '/modtools';
  239. const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
  240. view.onKeyDown(enter);
  241. await u.waitUntil(() => view.showModeratorToolsModal.calls.count());
  242. const modal = view.modtools_modal;
  243. await u.waitUntil(() => u.isVisible(modal.el), 1000);
  244. const tab = modal.el.querySelector('#roles-tab');
  245. tab.click();
  246. // Clear so that we don't match older stanzas
  247. _converse.connection.IQ_stanzas = [];
  248. const select = modal.el.querySelector('.select-role');
  249. expect(select.value).toBe('moderator');
  250. select.value = 'participant';
  251. const button = modal.el.querySelector('.btn-primary[name="users_with_role"]');
  252. button.click();
  253. await u.waitUntil(() => !modal.loading_users_with_role);
  254. const user_els = modal.el.querySelectorAll('.list-group--users > li');
  255. expect(user_els.length).toBe(6);
  256. const nicks = Array.from(modal.el.querySelectorAll('.list-group--users > li')).map(el => el.getAttribute('data-nick'));
  257. expect(nicks.join(' ')).toBe('crone newb nomorenicks oldhag some1 tux');
  258. const filter = modal.el.querySelector('[name="filter"]');
  259. expect(filter).not.toBe(null);
  260. filter.value = 'tux';
  261. u.triggerEvent(filter, "keyup", "KeyboardEvent");
  262. await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 1));
  263. filter.value = 'r';
  264. u.triggerEvent(filter, "keyup", "KeyboardEvent");
  265. await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 2));
  266. filter.value = 'crone';
  267. u.triggerEvent(filter, "keyup", "KeyboardEvent");
  268. await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 1));
  269. done();
  270. }));
  271. it("shows an error message if a particular affiliation list may not be retrieved",
  272. mock.initConverse(
  273. ['rosterGroupsFetched'], {},
  274. async function (done, _converse) {
  275. spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
  276. const muc_jid = 'lounge@montague.lit';
  277. const members = [
  278. {'jid': 'hag66@shakespeare.lit', 'nick': 'witch', 'affiliation': 'member'},
  279. {'jid': 'gower@shakespeare.lit', 'nick': 'gower', 'affiliation': 'member'},
  280. {'jid': 'wiccarocks@shakespeare.lit', 'nick': 'wiccan', 'affiliation': 'admin'},
  281. {'jid': 'crone1@shakespeare.lit', 'nick': 'thirdwitch', 'affiliation': 'owner'},
  282. {'jid': 'romeo@montague.lit', 'nick': 'romeo', 'affiliation': 'owner'},
  283. ];
  284. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo', [], members);
  285. const view = _converse.chatboxviews.get(muc_jid);
  286. await u.waitUntil(() => (view.model.occupants.length === 5));
  287. const textarea = view.el.querySelector('.chat-textarea');
  288. textarea.value = '/modtools';
  289. const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
  290. view.onKeyDown(enter);
  291. await u.waitUntil(() => view.showModeratorToolsModal.calls.count());
  292. const modal = view.modtools_modal;
  293. await u.waitUntil(() => u.isVisible(modal.el), 1000);
  294. const tab = modal.el.querySelector('#affiliations-tab');
  295. // Clear so that we don't match older stanzas
  296. _converse.connection.IQ_stanzas = [];
  297. const IQ_stanzas = _converse.connection.IQ_stanzas;
  298. tab.click();
  299. const select = modal.el.querySelector('.select-affiliation');
  300. select.value = 'outcast';
  301. const button = modal.el.querySelector('.btn-primary[name="users_with_affiliation"]');
  302. button.click();
  303. const iq_query = await u.waitUntil(() => _.filter(
  304. IQ_stanzas,
  305. s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="outcast"]`, s).length
  306. ).pop());
  307. const error = u.toStanza(
  308. `<iq from="${muc_jid}"
  309. id="${iq_query.getAttribute('id')}"
  310. type="error"
  311. to="${_converse.jid}">
  312. <error type="auth">
  313. <forbidden xmlns="${Strophe.NS.STANZAS}"/>
  314. </error>
  315. </iq>`);
  316. _converse.connection._dataRecv(test_utils.createRequest(error));
  317. await u.waitUntil(() => !modal.loading_users_with_affiliation);
  318. const user_els = modal.el.querySelectorAll('.list-group--users > li');
  319. expect(user_els.length).toBe(1);
  320. expect(user_els[0].textContent.trim()).toBe('Error: not allowed to fetch outcast list for MUC lounge@montague.lit');
  321. done();
  322. }));
  323. });
  324. });