2
0

autocomplete.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. /*global mock, converse */
  2. const $pres = converse.env.$pres;
  3. const $msg = converse.env.$msg;
  4. const Strophe = converse.env.Strophe;
  5. const u = converse.env.utils;
  6. describe("The nickname autocomplete feature", function () {
  7. it("shows all autocompletion options when the user presses @",
  8. mock.initConverse(
  9. ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  10. async function (done, _converse) {
  11. await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'tom');
  12. const view = _converse.chatboxviews.get('lounge@montague.lit');
  13. // Nicknames from presences
  14. ['dick', 'harry'].forEach((nick) => {
  15. _converse.connection._dataRecv(mock.createRequest(
  16. $pres({
  17. 'to': 'tom@montague.lit/resource',
  18. 'from': `lounge@montague.lit/${nick}`
  19. })
  20. .c('x', {xmlns: Strophe.NS.MUC_USER})
  21. .c('item', {
  22. 'affiliation': 'none',
  23. 'jid': `${nick}@montague.lit/resource`,
  24. 'role': 'participant'
  25. })));
  26. });
  27. // Nicknames from messages
  28. const msg = $msg({
  29. from: 'lounge@montague.lit/jane',
  30. id: u.getUniqueId(),
  31. to: 'romeo@montague.lit',
  32. type: 'groupchat'
  33. }).c('body').t('Hello world').tree();
  34. await view.model.handleMessageStanza(msg);
  35. // Test that pressing @ brings up all options
  36. const textarea = view.el.querySelector('textarea.chat-textarea');
  37. const at_event = {
  38. 'target': textarea,
  39. 'preventDefault': function preventDefault () {},
  40. 'stopPropagation': function stopPropagation () {},
  41. 'keyCode': 50,
  42. 'key': '@'
  43. };
  44. view.onKeyDown(at_event);
  45. textarea.value = '@';
  46. view.onKeyUp(at_event);
  47. await u.waitUntil(() => view.el.querySelectorAll('.suggestion-box__results li').length === 4);
  48. expect(view.el.querySelector('.suggestion-box__results li:first-child').textContent).toBe('dick');
  49. expect(view.el.querySelector('.suggestion-box__results li:nth-child(2)').textContent).toBe('harry');
  50. expect(view.el.querySelector('.suggestion-box__results li:nth-child(3)').textContent).toBe('jane');
  51. expect(view.el.querySelector('.suggestion-box__results li:nth-child(4)').textContent).toBe('tom');
  52. done();
  53. }));
  54. it("shows all autocompletion options when the user presses @ right after a new line",
  55. mock.initConverse(
  56. ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  57. async function (done, _converse) {
  58. await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'tom');
  59. const view = _converse.chatboxviews.get('lounge@montague.lit');
  60. // Nicknames from presences
  61. ['dick', 'harry'].forEach((nick) => {
  62. _converse.connection._dataRecv(mock.createRequest(
  63. $pres({
  64. 'to': 'tom@montague.lit/resource',
  65. 'from': `lounge@montague.lit/${nick}`
  66. })
  67. .c('x', {xmlns: Strophe.NS.MUC_USER})
  68. .c('item', {
  69. 'affiliation': 'none',
  70. 'jid': `${nick}@montague.lit/resource`,
  71. 'role': 'participant'
  72. })));
  73. });
  74. // Nicknames from messages
  75. const msg = $msg({
  76. from: 'lounge@montague.lit/jane',
  77. id: u.getUniqueId(),
  78. to: 'romeo@montague.lit',
  79. type: 'groupchat'
  80. }).c('body').t('Hello world').tree();
  81. await view.model.handleMessageStanza(msg);
  82. // Test that pressing @ brings up all options
  83. const textarea = view.el.querySelector('textarea.chat-textarea');
  84. const at_event = {
  85. 'target': textarea,
  86. 'preventDefault': function preventDefault () {},
  87. 'stopPropagation': function stopPropagation () {},
  88. 'keyCode': 50,
  89. 'key': '@'
  90. };
  91. textarea.value = '\n'
  92. view.onKeyDown(at_event);
  93. textarea.value = '\n@';
  94. view.onKeyUp(at_event);
  95. await u.waitUntil(() => view.el.querySelectorAll('.suggestion-box__results li').length === 4);
  96. expect(view.el.querySelector('.suggestion-box__results li:first-child').textContent).toBe('dick');
  97. expect(view.el.querySelector('.suggestion-box__results li:nth-child(2)').textContent).toBe('harry');
  98. expect(view.el.querySelector('.suggestion-box__results li:nth-child(3)').textContent).toBe('jane');
  99. expect(view.el.querySelector('.suggestion-box__results li:nth-child(4)').textContent).toBe('tom');
  100. done();
  101. }));
  102. it("should order by query index position and length", mock.initConverse(
  103. ['rosterGroupsFetched', 'chatBoxesFetched'], {}, async function (done, _converse) {
  104. await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'tom');
  105. const view = _converse.chatboxviews.get('lounge@montague.lit');
  106. // Nicknames from presences
  107. ['bernard', 'naber', 'helberlo', 'john', 'jones'].forEach((nick) => {
  108. _converse.connection._dataRecv(mock.createRequest(
  109. $pres({
  110. 'to': 'tom@montague.lit/resource',
  111. 'from': `lounge@montague.lit/${nick}`
  112. })
  113. .c('x', { xmlns: Strophe.NS.MUC_USER })
  114. .c('item', {
  115. 'affiliation': 'none',
  116. 'jid': `${nick}@montague.lit/resource`,
  117. 'role': 'participant'
  118. })));
  119. });
  120. const textarea = view.el.querySelector('textarea.chat-textarea');
  121. const at_event = {
  122. 'target': textarea,
  123. 'preventDefault': function preventDefault() { },
  124. 'stopPropagation': function stopPropagation() { },
  125. 'keyCode': 50,
  126. 'key': '@'
  127. };
  128. // Test that results are sorted by query index
  129. view.onKeyDown(at_event);
  130. textarea.value = '@ber';
  131. view.onKeyUp(at_event);
  132. await u.waitUntil(() => view.el.querySelectorAll('.suggestion-box__results li').length === 3);
  133. expect(view.el.querySelector('.suggestion-box__results li:first-child').textContent).toBe('bernard');
  134. expect(view.el.querySelector('.suggestion-box__results li:nth-child(2)').textContent).toBe('naber');
  135. expect(view.el.querySelector('.suggestion-box__results li:nth-child(3)').textContent).toBe('helberlo');
  136. // Test that when the query index is equal, results should be sorted by length
  137. textarea.value = '@jo';
  138. view.onKeyUp(at_event);
  139. await u.waitUntil(() => view.el.querySelectorAll('.suggestion-box__results li').length === 2);
  140. expect(view.el.querySelector('.suggestion-box__results li:first-child').textContent).toBe('john');
  141. expect(view.el.querySelector('.suggestion-box__results li:nth-child(2)').textContent).toBe('jones');
  142. done();
  143. }));
  144. it("autocompletes when the user presses tab",
  145. mock.initConverse(
  146. ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  147. async function (done, _converse) {
  148. await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
  149. const view = _converse.chatboxviews.get('lounge@montague.lit');
  150. expect(view.model.occupants.length).toBe(1);
  151. let presence = $pres({
  152. 'to': 'romeo@montague.lit/orchard',
  153. 'from': 'lounge@montague.lit/some1'
  154. })
  155. .c('x', {xmlns: Strophe.NS.MUC_USER})
  156. .c('item', {
  157. 'affiliation': 'none',
  158. 'jid': 'some1@montague.lit/resource',
  159. 'role': 'participant'
  160. });
  161. _converse.connection._dataRecv(mock.createRequest(presence));
  162. expect(view.model.occupants.length).toBe(2);
  163. const textarea = view.el.querySelector('textarea.chat-textarea');
  164. textarea.value = "hello som";
  165. // Press tab
  166. const tab_event = {
  167. 'target': textarea,
  168. 'preventDefault': function preventDefault () {},
  169. 'stopPropagation': function stopPropagation () {},
  170. 'keyCode': 9,
  171. 'key': 'Tab'
  172. }
  173. view.onKeyDown(tab_event);
  174. view.onKeyUp(tab_event);
  175. await u.waitUntil(() => view.el.querySelector('.suggestion-box__results').hidden === false);
  176. expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(1);
  177. expect(view.el.querySelector('.suggestion-box__results li').textContent).toBe('some1');
  178. const backspace_event = {
  179. 'target': textarea,
  180. 'preventDefault': function preventDefault () {},
  181. 'keyCode': 8
  182. }
  183. for (var i=0; i<3; i++) {
  184. // Press backspace 3 times to remove "som"
  185. view.onKeyDown(backspace_event);
  186. textarea.value = textarea.value.slice(0, textarea.value.length-1)
  187. view.onKeyUp(backspace_event);
  188. }
  189. await u.waitUntil(() => view.el.querySelector('.suggestion-box__results').hidden === true);
  190. presence = $pres({
  191. 'to': 'romeo@montague.lit/orchard',
  192. 'from': 'lounge@montague.lit/some2'
  193. })
  194. .c('x', {xmlns: Strophe.NS.MUC_USER})
  195. .c('item', {
  196. 'affiliation': 'none',
  197. 'jid': 'some2@montague.lit/resource',
  198. 'role': 'participant'
  199. });
  200. _converse.connection._dataRecv(mock.createRequest(presence));
  201. textarea.value = "hello s s";
  202. view.onKeyDown(tab_event);
  203. view.onKeyUp(tab_event);
  204. await u.waitUntil(() => view.el.querySelector('.suggestion-box__results').hidden === false);
  205. expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(2);
  206. const up_arrow_event = {
  207. 'target': textarea,
  208. 'preventDefault': () => (up_arrow_event.defaultPrevented = true),
  209. 'stopPropagation': function stopPropagation () {},
  210. 'keyCode': 38
  211. }
  212. view.onKeyDown(up_arrow_event);
  213. view.onKeyUp(up_arrow_event);
  214. expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(2);
  215. expect(view.el.querySelector('.suggestion-box__results li[aria-selected="false"]').textContent).toBe('some1');
  216. expect(view.el.querySelector('.suggestion-box__results li[aria-selected="true"]').textContent).toBe('some2');
  217. view.onKeyDown({
  218. 'target': textarea,
  219. 'preventDefault': function preventDefault () {},
  220. 'stopPropagation': function stopPropagation () {},
  221. 'keyCode': 13 // Enter
  222. });
  223. expect(textarea.value).toBe('hello s @some2 ');
  224. // Test that pressing tab twice selects
  225. presence = $pres({
  226. 'to': 'romeo@montague.lit/orchard',
  227. 'from': 'lounge@montague.lit/z3r0'
  228. })
  229. .c('x', {xmlns: Strophe.NS.MUC_USER})
  230. .c('item', {
  231. 'affiliation': 'none',
  232. 'jid': 'z3r0@montague.lit/resource',
  233. 'role': 'participant'
  234. });
  235. _converse.connection._dataRecv(mock.createRequest(presence));
  236. textarea.value = "hello z";
  237. view.onKeyDown(tab_event);
  238. view.onKeyUp(tab_event);
  239. await u.waitUntil(() => view.el.querySelector('.suggestion-box__results').hidden === false);
  240. view.onKeyDown(tab_event);
  241. view.onKeyUp(tab_event);
  242. await u.waitUntil(() => textarea.value === 'hello @z3r0 ');
  243. done();
  244. }));
  245. it("autocompletes when the user presses backspace",
  246. mock.initConverse(
  247. ['rosterGroupsFetched'], {},
  248. async function (done, _converse) {
  249. await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
  250. const view = _converse.chatboxviews.get('lounge@montague.lit');
  251. expect(view.model.occupants.length).toBe(1);
  252. const presence = $pres({
  253. 'to': 'romeo@montague.lit/orchard',
  254. 'from': 'lounge@montague.lit/some1'
  255. })
  256. .c('x', {xmlns: Strophe.NS.MUC_USER})
  257. .c('item', {
  258. 'affiliation': 'none',
  259. 'jid': 'some1@montague.lit/resource',
  260. 'role': 'participant'
  261. });
  262. _converse.connection._dataRecv(mock.createRequest(presence));
  263. expect(view.model.occupants.length).toBe(2);
  264. const textarea = view.el.querySelector('textarea.chat-textarea');
  265. textarea.value = "hello @some1 ";
  266. // Press backspace
  267. const backspace_event = {
  268. 'target': textarea,
  269. 'preventDefault': function preventDefault () {},
  270. 'stopPropagation': function stopPropagation () {},
  271. 'keyCode': 8,
  272. 'key': 'Backspace'
  273. }
  274. view.onKeyDown(backspace_event);
  275. textarea.value = "hello @some1"; // Mimic backspace
  276. view.onKeyUp(backspace_event);
  277. await u.waitUntil(() => view.el.querySelector('.suggestion-box__results').hidden === false);
  278. expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(1);
  279. expect(view.el.querySelector('.suggestion-box__results li').textContent).toBe('some1');
  280. done();
  281. }));
  282. });