SearchPadSpec.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. /* global sinon */
  2. import {
  3. bootstrapDiagram,
  4. inject
  5. } from 'test/TestHelper';
  6. import searchPadModule from 'lib/features/search-pad';
  7. import SearchPad from 'lib/features/search-pad/SearchPad';
  8. import {
  9. query as domQuery,
  10. queryAll as domQueryAll,
  11. classes as domClasses
  12. } from 'min-dom';
  13. var EVENTS = {
  14. closed: 'searchPad.closed',
  15. opened: 'searchPad.opened',
  16. preselected: 'searchPad.preselected',
  17. selected: 'searchPad.selected'
  18. };
  19. describe('features/searchPad', function() {
  20. beforeEach(bootstrapDiagram({ modules: [ searchPadModule ] }));
  21. var capturedEvents;
  22. var searchProvider;
  23. var elements;
  24. var input_node;
  25. beforeEach(inject(function(searchPad, eventBus, canvas) {
  26. canvas.setRootElement({ id: 'FOO' });
  27. elements = {
  28. one: {
  29. a: canvas.addShape({ id: 'one-a', x: 0, y: 0, width: 100, height: 80 })
  30. },
  31. two: {
  32. a: canvas.addShape({ id: 'two-a', x: 0, y: 0, width: 100, height: 80 }),
  33. b: canvas.addShape({ id: 'two-b', x: 0, y: 0, width: 100, height: 80 })
  34. }
  35. };
  36. function SearchProvider() {
  37. this.setup = function(pattern, results) {
  38. this._pattern = pattern;
  39. this._results = results;
  40. };
  41. this.find = function(pattern) {
  42. if (pattern === this._pattern) {
  43. return this._results;
  44. }
  45. if (pattern === 'one') {
  46. return [{
  47. primaryTokens: [
  48. { normal: 'one' }
  49. ],
  50. secondaryTokens: [
  51. { normal: 'some_' },
  52. { matched: 'DataStore' },
  53. { normal: '_123456_id' }
  54. ],
  55. element: elements.one.a
  56. }];
  57. }
  58. if (pattern === 'two') {
  59. return [{
  60. primaryTokens: [
  61. { normal: 'one' }
  62. ],
  63. secondaryTokens: [
  64. { normal: 'some_' },
  65. { matched: 'DataStore' },
  66. { normal: '_123456_id' }
  67. ],
  68. element: elements.two.a
  69. },{
  70. primaryTokens: [
  71. { normal: 'two' }
  72. ],
  73. secondaryTokens: [
  74. { normal: 'some_' },
  75. { matched: 'DataStore' },
  76. { normal: '_123456_id' }
  77. ],
  78. element: elements.two.b
  79. }];
  80. }
  81. if (pattern === 'html') {
  82. return [{
  83. primaryTokens: [
  84. { normal: '<html/>' }
  85. ],
  86. secondaryTokens: [
  87. { normal: 'some_' },
  88. { matched: '<html/>' },
  89. { normal: '_123456_id' }
  90. ],
  91. element: elements.one.a
  92. }];
  93. }
  94. return [];
  95. };
  96. }
  97. searchProvider = new SearchProvider();
  98. searchPad.registerProvider(searchProvider);
  99. capturedEvents = [];
  100. Object.keys(EVENTS).forEach(function(k) {
  101. var e = EVENTS[k];
  102. eventBus.on(e, function() {
  103. capturedEvents.push(e);
  104. });
  105. });
  106. input_node = domQuery(SearchPad.INPUT_SELECTOR, canvas.getContainer());
  107. }));
  108. it('should be closed by default', inject(function(canvas, eventBus, searchPad) {
  109. // then
  110. expect(searchPad.isOpen()).to.equal(false);
  111. }));
  112. it('should open', inject(function(canvas, eventBus, searchPad) {
  113. // when
  114. searchPad.open();
  115. // then
  116. expect(searchPad.isOpen()).to.equal(true);
  117. expect(capturedEvents).to.eql([ EVENTS.opened ]);
  118. }));
  119. it('should error on open when provider not registered', inject(function(canvas, eventBus, searchPad) {
  120. // given
  121. searchPad.registerProvider(undefined);
  122. // when
  123. expect(function() {
  124. searchPad.open();
  125. }).to.throw('no search provider registered');
  126. // then
  127. expect(searchPad.isOpen()).to.equal(false);
  128. expect(capturedEvents).to.eql([]);
  129. }));
  130. it('should close', inject(function(canvas, eventBus, searchPad) {
  131. // given
  132. searchPad.open();
  133. // when
  134. searchPad.close();
  135. // then
  136. expect(searchPad.isOpen()).to.equal(false);
  137. expect(capturedEvents).to.eql([ EVENTS.opened, EVENTS.closed ]);
  138. }));
  139. it('should toggle open/close', inject(function(canvas, eventBus, searchPad) {
  140. // when
  141. searchPad.toggle();
  142. searchPad.toggle();
  143. // then
  144. expect(searchPad.isOpen()).to.equal(false);
  145. expect(capturedEvents).to.eql([ EVENTS.opened, EVENTS.closed ]);
  146. // when
  147. searchPad.toggle();
  148. // then
  149. expect(searchPad.isOpen()).to.equal(true);
  150. expect(capturedEvents).to.eql([ EVENTS.opened, EVENTS.closed, EVENTS.opened ]);
  151. }));
  152. describe('searching/selection', function() {
  153. var element;
  154. beforeEach(inject(function(searchPad, eventBus) {
  155. // given
  156. element = searchProvider.find('one')[0].element;
  157. searchPad.open();
  158. }));
  159. it('should not search on empty string', inject(function(canvas, eventBus, searchPad) {
  160. // given
  161. var find = sinon.spy(searchProvider, 'find');
  162. // when
  163. typeText(input_node, '');
  164. // then
  165. expect(find).callCount(0);
  166. }));
  167. it('should display results', inject(function(canvas, eventBus, searchPad) {
  168. // given
  169. var find = sinon.spy(searchProvider, 'find');
  170. // when
  171. typeText(input_node, 'two');
  172. // then
  173. expect(find).callCount(3);
  174. var result_nodes = domQueryAll(SearchPad.RESULT_SELECTOR, canvas.getContainer());
  175. expect(result_nodes).length(2);
  176. }));
  177. it('should escape displayed results', inject(function(canvas, eventBus, searchPad) {
  178. // when
  179. typeText(input_node, 'html');
  180. // then
  181. var result_nodes = domQueryAll(SearchPad.RESULT_SELECTOR, canvas.getContainer());
  182. expect(result_nodes).to.have.length(1);
  183. expect(result_nodes[0].innerHTML).not.to.contain('<html/>');
  184. }));
  185. it('should preselect first result', inject(function(canvas, eventBus, searchPad) {
  186. // when
  187. typeText(input_node, 'two');
  188. // then
  189. var result_nodes = domQueryAll(SearchPad.RESULT_SELECTOR, canvas.getContainer());
  190. expect(domClasses(result_nodes[0]).has(SearchPad.RESULT_SELECTED_CLASS)).to.be.true;
  191. expect(capturedEvents).to.eql([ EVENTS.opened, EVENTS.preselected ]);
  192. }));
  193. it('should select result on enter', inject(function(canvas, eventBus, searchPad) {
  194. // given
  195. typeText(input_node, 'two');
  196. // when
  197. triggerKeyEvent(input_node, 'keyup', 13);
  198. // then
  199. expect(capturedEvents).to.eql([
  200. EVENTS.opened,
  201. EVENTS.preselected,
  202. EVENTS.closed,
  203. EVENTS.selected
  204. ]);
  205. }));
  206. it('should set overlay on an highlighted element', inject(function(searchPad, overlays) {
  207. // when
  208. typeText(input_node, 'one');
  209. // then
  210. var overlay = overlays.get({ element: element });
  211. expect(overlay).length(1);
  212. }));
  213. it('should remove overlay from an element on enter', inject(function(searchPad, overlays) {
  214. // given
  215. typeText(input_node, 'one');
  216. // when
  217. triggerKeyEvent(input_node, 'keyup', 13);
  218. // then
  219. var overlay = overlays.get({ element: element });
  220. expect(overlay).length(0);
  221. }));
  222. it('select should center viewbox on an element', inject(function(searchPad, canvas) {
  223. // given
  224. typeText(input_node, 'one');
  225. var container = canvas.getContainer();
  226. container.style.width = '1000px';
  227. container.style.height = '1000px';
  228. canvas.viewbox({
  229. x: 0,
  230. y: 0,
  231. width: 1000,
  232. height: 1000
  233. });
  234. // when
  235. triggerKeyEvent(input_node, 'keyup', 13);
  236. // then
  237. var newViewbox = canvas.viewbox();
  238. expect(newViewbox).to.have.property('x', -450);
  239. expect(newViewbox).to.have.property('y', -460);
  240. }));
  241. it('select should keep zoom level', inject(function(searchPad, canvas) {
  242. // given
  243. canvas.zoom(0.4);
  244. typeText(input_node, 'one');
  245. // when
  246. triggerKeyEvent(input_node, 'keyup', 13);
  247. // then
  248. var newViewbox = canvas.viewbox();
  249. expect(newViewbox).to.have.property('scale', 0.4);
  250. }));
  251. it('select should apply selection on an element', inject(function(searchPad, selection) {
  252. // given
  253. typeText(input_node, 'one');
  254. // when
  255. triggerKeyEvent(input_node, 'keyup', 13);
  256. // then
  257. expect(selection.isSelected(element)).to.be.true;
  258. }));
  259. it('should close on escape', inject(function(canvas, eventBus, searchPad) {
  260. // when
  261. triggerKeyEvent(input_node, 'keyup', 27);
  262. // then
  263. expect(searchPad.isOpen()).to.equal(false);
  264. expect(capturedEvents).to.eql([ EVENTS.opened, EVENTS.closed ]);
  265. }));
  266. it('should preselect next/previus results on arrow down/up', inject(function(canvas, eventBus, searchPad) {
  267. // given
  268. typeText(input_node, 'two');
  269. var result_nodes = domQueryAll(SearchPad.RESULT_SELECTOR, canvas.getContainer());
  270. // when press 'down'
  271. triggerKeyEvent(input_node, 'keyup', 40);
  272. // then
  273. expect(domClasses(result_nodes[0]).has(SearchPad.RESULT_SELECTED_CLASS)).to.be.false;
  274. expect(domClasses(result_nodes[1]).has(SearchPad.RESULT_SELECTED_CLASS)).to.be.true;
  275. // when press 'up'
  276. triggerKeyEvent(input_node, 'keyup', 38);
  277. // then
  278. expect(domClasses(result_nodes[0]).has(SearchPad.RESULT_SELECTED_CLASS)).to.be.true;
  279. expect(domClasses(result_nodes[1]).has(SearchPad.RESULT_SELECTED_CLASS)).to.be.false;
  280. expect(capturedEvents).to.eql([
  281. EVENTS.opened,
  282. EVENTS.preselected,
  283. EVENTS.preselected,
  284. EVENTS.preselected
  285. ]);
  286. }));
  287. it('should not move input cursor on arrow down', inject(function(canvas, eventBus, searchPad) {
  288. // given
  289. typeText(input_node, 'two');
  290. // when press 'down'
  291. var e = triggerKeyEvent(input_node, 'keydown', 40);
  292. expect(e.defaultPrevented).to.be.true;
  293. }));
  294. it('should not move input cursor on arrow up', inject(function(canvas, eventBus, searchPad) {
  295. // given
  296. typeText(input_node, 'two');
  297. // when press 'up'
  298. var e = triggerKeyEvent(input_node, 'keydown', 38);
  299. expect(e.defaultPrevented).to.be.true;
  300. }));
  301. it('should not search while navigating text in input box left', inject(function(canvas, eventBus, searchPad) {
  302. // given
  303. var find = sinon.spy(searchProvider, 'find');
  304. typeText(input_node, 'two');
  305. // when press 'left'
  306. triggerKeyEvent(input_node, 'keyup', 37);
  307. // then
  308. expect(find).callCount('two'.length);
  309. }));
  310. it('should not search while navigating text in input box right', inject(function(canvas, eventBus, searchPad) {
  311. // given
  312. var find = sinon.spy(searchProvider, 'find');
  313. typeText(input_node, 'two');
  314. // when press 'right'
  315. triggerKeyEvent(input_node, 'keyup', 39);
  316. // then
  317. expect(find).callCount('two'.length);
  318. }));
  319. });
  320. });
  321. function triggerKeyEvent(element, event, code) {
  322. var e = document.createEvent('Events');
  323. if (e.initEvent) {
  324. e.initEvent(event, true, true);
  325. }
  326. e.keyCode = code;
  327. e.which = code;
  328. element.dispatchEvent(e);
  329. return e;
  330. }
  331. function typeText(element, text) {
  332. var input = text.split('');
  333. element.value = '';
  334. input.forEach(function(c) {
  335. element.value += c;
  336. triggerKeyEvent(element, 'keyup', c.charCodeAt(0));
  337. });
  338. }