UNPKG

17.9 kBJavaScriptView Raw
1/* eslint-disable no-magic-numbers,react/no-find-dom-node */
2
3import 'dom4';
4import React from 'react';
5import {findDOMNode} from 'react-dom';
6import {Simulate} from 'react-dom/test-utils';
7import {mount} from 'enzyme';
8
9
10import simulateCombo from '../../test-helpers/simulate-combo';
11
12import QueryAssist from './query-assist';
13import styles from './query-assist.css';
14
15describe('Query Assist', () => {
16 const testQuery = 'oooooooooooo';
17 const testQueryLength = testQuery.length;
18
19 const suggestions = [{
20 prefix: 'login: ',
21 option: 'test',
22 suffix: ' ',
23 description: 'logins',
24 matchingStart: 0,
25 matchingEnd: 4,
26 caret: 2,
27 completionStart: 0,
28 completionEnd: 4,
29 group: 'logins',
30 icon: 'data:uri'
31 }, {
32 prefix: 'login: ',
33 option: 'test.1',
34 suffix: ' ',
35 description: 'logins',
36 matchingStart: 0,
37 matchingEnd: 4,
38 caret: 2,
39 completionStart: 0,
40 completionEnd: 4,
41 group: 'logins',
42 icon: 'data:uri'
43 }, {
44 prefix: 'login: ',
45 option: 'test.2',
46 suffix: ' ',
47 description: 'logins',
48 matchingStart: 0,
49 matchingEnd: 4,
50 caret: 2,
51 completionStart: 0,
52 completionEnd: 4,
53 group: 'logins',
54 icon: 'data:uri'
55 }, {
56 prefix: 'login: ',
57 option: 'test.3',
58 suffix: ' ',
59 description: 'logins',
60 matchingStart: 0,
61 matchingEnd: 4,
62 caret: 2,
63 completionStart: 0,
64 completionEnd: 4,
65 group: 'logins',
66 icon: 'data:uri'
67 }];
68
69 const defaultProps = () => ({
70 query: testQuery,
71 focus: true,
72 dataSource: sandbox.spy(({query, caret}) => ({
73 query,
74 caret,
75 suggestions
76 }))
77 });
78 const mountQueryAssist = (props, options) =>
79 mount(<QueryAssist {...defaultProps()} {...props}/>, options);
80
81 describe('props to state passing', () => {
82 it('should create component', () => {
83 const wrapper = mountQueryAssist();
84
85 wrapper.should.exist;
86 wrapper.instance().input.should.exist;
87 });
88
89 it('should set state props to state on init', () => {
90 const wrapper = mountQueryAssist();
91 wrapper.should.have.state('query', testQuery);
92 wrapper.should.have.state('placeholderEnabled', false);
93 });
94
95 it('should not set other props to state on init', () => {
96 const wrapper = mountQueryAssist();
97
98 wrapper.should.not.have.state('popupClassName');
99 wrapper.should.not.have.state('dataSource');
100 wrapper.should.not.have.state('disabled');
101 wrapper.should.not.have.state('clear');
102 wrapper.should.not.have.state('hint');
103 wrapper.should.not.have.state('hintOnSelection');
104 wrapper.should.not.have.state('glass');
105 wrapper.should.not.have.state('placeholder');
106 wrapper.should.not.have.state('onApply');
107 wrapper.should.not.have.state('onChange');
108 wrapper.should.not.have.state('onClear');
109 wrapper.should.not.have.state('onFocusChange');
110 });
111
112 it('should set state props to state on update', () => {
113 const wrapper = mountQueryAssist({
114 query: 'update',
115 caret: 2,
116 focus: false
117 });
118
119 wrapper.should.have.state('query', 'update');
120 });
121
122 it('should set state props to immediateState on update', () => {
123 const instance = mountQueryAssist({
124 query: 'update',
125 caret: 2,
126 focus: false
127 }).instance();
128
129 instance.immediateState.query.should.equal('update');
130 instance.immediateState.caret.should.equal(2);
131 instance.immediateState.focus.should.equal(false);
132 });
133
134 it('should not set undefined state props to state on update', () => {
135 const wrapper = mountQueryAssist();
136
137 wrapper.setProps({
138 query: undefined
139 });
140
141 wrapper.should.have.state('query', testQuery);
142 });
143
144 it('should not set caret with query on update', () => {
145 const wrapper = mountQueryAssist();
146 const instance = wrapper.instance();
147
148 wrapper.setProps({
149 query: 'update'
150 });
151
152 wrapper.should.have.state('query', 'update');
153 instance.immediateState.query.should.equal('update');
154 instance.immediateState.caret.should.equal(testQueryLength);
155 });
156 });
157
158
159 describe('setFocus', () => {
160 it('should set focus in query assist', () => {
161 const instance = mountQueryAssist({focus: null}).instance();
162
163 instance.setFocus(true);
164
165 instance.immediateState.focus.should.equal(true);
166 });
167
168 it('should remove focus from query assist', () => {
169 const instance = mountQueryAssist({focus: true}).instance();
170
171 instance.setFocus(false);
172
173 instance.immediateState.focus.should.equal(false);
174 });
175 });
176
177
178 describe('shortcuts', () => {
179 it('should enable shortcuts when we set focus', () => {
180 const instance = mountQueryAssist({focus: null}).instance();
181 instance.state.shortcuts.should.equal(false);
182
183 instance.setFocus(true);
184 instance.state.shortcuts.should.equal(true);
185 });
186
187
188 it('should disable shortcuts when we remove focus', () => {
189 const instance = mountQueryAssist({focus: true}).instance();
190 instance.state.shortcuts.should.equal(true);
191
192 instance.setFocus(false);
193 instance.state.shortcuts.should.equal(false);
194 });
195
196
197 it('should not enable shortcuts after rerender', () => {
198 const wrapper = mountQueryAssist({focus: false, placeholder: 'bar'});
199 const instance = wrapper.instance();
200 instance.state.shortcuts.should.equal(false);
201
202 wrapper.setProps({placeholder: 'foo'});
203 instance.state.shortcuts.should.equal(false);
204 });
205 });
206
207
208 describe('init', () => {
209 it('requestData should exist', () => {
210 const instance = mountQueryAssist().instance();
211 instance.requestData.should.be.a('function');
212 instance.requestData.should.equal(instance.requestHandler);
213 });
214
215 it('requestData should be debounced when delay set', () => {
216 const instance = mountQueryAssist({
217 delay: 0
218 }).instance();
219 instance.requestData.should.be.a('function');
220 instance.requestData.should.not.equal(instance.requestHandler);
221 });
222
223
224 it('should create popup when autoOpen', done => {
225 mountQueryAssist({
226 autoOpen: true,
227 dataSource: params => {
228 params.should.not.have.property('omitSuggestions');
229 done();
230 }
231 });
232 });
233
234 it('should not create popup by default', done => {
235 mountQueryAssist({
236 dataSource: params => {
237 params.should.have.property('omitSuggestions', true);
238 done();
239 }
240 });
241 });
242 });
243
244 describe('rendering', () => {
245 const LETTER_CLASS = styles.letter;
246
247 it('should render letters', () => {
248 const instance = mountQueryAssist().instance();
249
250 instance.input.should.contain(`.${LETTER_CLASS}`);
251 instance.input.queryAll(`.${LETTER_CLASS}`).
252 should.have.length(testQueryLength);
253 });
254
255
256 it('should render nothing on empty query', () => {
257 const instance = mountQueryAssist({
258 query: ''
259 }).instance();
260
261 instance.input.textContent.should.be.empty;
262 });
263
264 it('should render nothing on falsy query', () => {
265 const instance = mountQueryAssist({
266 query: null
267 }).instance();
268
269 instance.input.textContent.should.be.empty;
270 });
271
272 it('Shouldnt make duplicate requests for styleRanges on initiating if query is provided', () => {
273 const wrapper = mountQueryAssist();
274
275 //Emulate multiple rerender when rendering component with react-ng
276 wrapper.setProps({});
277 wrapper.setProps({});
278
279 wrapper.prop('dataSource').should.have.been.calledOnce;
280 });
281
282 it('should render placeholder when enabled on empty query', () => {
283 const instance = mountQueryAssist({
284 query: '',
285 placeholder: 'plz'
286 }).instance();
287
288 instance.placeholder.should.exist;
289 instance.placeholder.should.have.text('plz');
290 });
291
292 it('should not render placeholder when disabled on empty query', () => {
293 const instance = mountQueryAssist({
294 query: ''
295 }).instance();
296
297 should.not.exist(instance.placeholder);
298 });
299
300 it('should render with colors', () => {
301 const wrapper = mountQueryAssist();
302
303 wrapper.setState({
304 styleRanges: [
305 {start: 0, length: 1, style: 'text'},
306 {start: 1, length: 1, style: 'field_value'},
307 {start: 2, length: 1, style: 'field_name'},
308 {start: 3, length: 1, style: 'operator'}
309 ]
310 });
311
312 const letters = wrapper.instance().input.queryAll(`.${LETTER_CLASS}`);
313
314 letters[0].should.have.class(styles['letter-text']);
315 letters[1].should.have.class(styles['letter-field-value']);
316 letters[2].should.have.class(styles['letter-field-name']);
317 letters[3].should.have.class(styles['letter-operator']);
318 });
319
320 it('should render last text range with default style when applied', () => {
321 const wrapper = mountQueryAssist({
322 query: 'a a'
323 });
324
325 wrapper.setState({
326 dirty: true,
327 styleRanges: [
328 {start: 0, length: 1, style: 'text'},
329 {start: 2, length: 1, style: 'text'}
330 ]
331 });
332
333 const letters = wrapper.instance().input.queryAll(`.${LETTER_CLASS}`);
334
335 letters[0].should.have.class(styles['letter-text']);
336 letters[1].should.have.class(styles.letterDefault);
337 letters[2].should.have.class(styles.letterDefault);
338 });
339
340 it('should render last text range with text style when applied', () => {
341 const wrapper = mountQueryAssist({
342 query: 'a a'
343 });
344
345 wrapper.setState({
346 styleRanges: [
347 {start: 0, length: 1, style: 'text'},
348 {start: 2, length: 1, style: 'text'}
349 ]
350 });
351
352 const letters = wrapper.instance().input.queryAll(`.${LETTER_CLASS}`);
353
354 letters[0].should.have.class(styles['letter-text']);
355 letters[1].should.have.class(styles.letterDefault);
356 letters[2].should.have.class(styles['letter-text']);
357 });
358
359 it('should disable field when component disabled', () => {
360 const instance = mountQueryAssist({
361 disabled: true
362 }).instance();
363
364 instance.input.should.have.attr('contenteditable', 'false');
365 instance.input.should.have.class(styles.inputDisabled);
366 });
367
368 it('should render glass when enabled', () => {
369 const instance = mountQueryAssist({
370 glass: true
371 }).instance();
372
373 instance.glass.should.exist;
374 });
375
376 it('should not render glass when disabled', () => {
377 const instance = mountQueryAssist({
378 glass: false
379 }).instance();
380
381 should.not.exist(instance.glass);
382 });
383
384 it('should render clear when enabled', () => {
385 const instance = mountQueryAssist({
386 clear: true
387 }).instance();
388
389 instance.clear.should.exist;
390 });
391
392 it('should not render clear when disabled', () => {
393 const instance = mountQueryAssist({
394 clear: false
395 }).instance();
396
397 should.not.exist(instance.clear);
398 });
399
400 it('should not render clear when query is empty', () => {
401 const instance = mountQueryAssist({
402 clear: true,
403 query: ''
404 }).instance();
405
406 should.not.exist(instance.clear);
407 });
408
409 it('should show loader on long request', () => {
410 const wrapper = mountQueryAssist();
411 wrapper.setState({
412 loading: true
413 });
414
415 wrapper.instance().loader.should.exist;
416 });
417 });
418
419 describe('suggestions', () => {
420 it('should not show popup when no suggestions provided', done => {
421 const instance = mountQueryAssist({
422 dataSource: ({query, caret}) => ({
423 query,
424 caret,
425 suggestions: []
426 })
427 }).instance();
428
429 instance.requestData().
430 then(() => {
431 instance._popup.isVisible().should.be.false;
432 done();
433 });
434 });
435
436 it('should show popup when suggestions provided', done => {
437 const instance = mountQueryAssist().instance();
438
439 instance.requestData().
440 then(() => {
441 instance._popup.isVisible().should.be.true;
442 instance._popup.list.should.exist;
443 done();
444 });
445 });
446
447 it('should close popup with after zero suggestions provided', done => {
448 let currentSuggestions = suggestions;
449 const instance = mountQueryAssist({
450 dataSource: ({query, caret}) => ({
451 query,
452 caret,
453 suggestions: currentSuggestions
454 })
455 }).instance();
456
457 instance.requestData().
458 then(() => {
459 currentSuggestions = [];
460 instance.requestData().
461 then(() => {
462 instance._popup.isVisible().should.be.false;
463 done();
464 });
465 });
466 });
467
468 it('should show popup with proper suggestions', done => {
469 const container = document.createElement('div');
470 document.body.appendChild(container);
471 const wrapper = mountQueryAssist({}, {attachTo: container});
472 const instance = wrapper.instance();
473
474 const TWICE = 2;
475
476 instance.requestData().
477 then(() => {
478 const list = findDOMNode(instance._popup.list);
479 const {length} = suggestions;
480
481 list.queryAll('[data-test~=ring-list-item]').should.have.length(length);
482 list.queryAll(`.${styles.highlight}`).should.have.length(length);
483 list.queryAll(`.${styles.service}`).should.have.length(length * TWICE);
484
485 wrapper.detach();
486 document.body.removeChild(container);
487 done();
488 }).catch(done);
489 });
490
491 });
492
493 describe('completion', () => {
494 const completeQuery = 'test';
495 const middleCaret = completeQuery.length / 2;
496
497 function getSuggestionText({prefix, option, suffix}) {
498 return (prefix + option + suffix).replace(/\s/g, '\u00a0');
499 }
500
501 it('should complete by tab in the end of phrase', () => {
502 const instance = mountQueryAssist({
503 query: completeQuery
504 }).instance();
505
506 return instance.requestData().then(() => {
507 simulateCombo('tab');
508
509 instance.input.should.have.text(getSuggestionText(suggestions[0]));
510 });
511 });
512
513 it('should complete selected suggestion by enter in the end of phrase', () => {
514 const instance = mountQueryAssist({
515 query: completeQuery
516 }).instance();
517
518 return instance.requestData().then(() => {
519 simulateCombo('down enter');
520
521 instance.input.should.have.text(getSuggestionText(suggestions[0]));
522 });
523 });
524
525 it('should complete by tab in the middle of phrase', () => {
526 const instance = mountQueryAssist({
527 query: completeQuery,
528 caret: middleCaret
529 }).instance();
530
531 return instance.requestData().then(() => {
532 simulateCombo('tab');
533
534 instance.input.should.have.text(getSuggestionText(suggestions[0]));
535 });
536 });
537
538 it('should complete selected suggestion by enter in the middle of phrase', () => {
539 const instance = mountQueryAssist({
540 query: completeQuery,
541 caret: middleCaret
542 }).instance();
543
544 return instance.requestData().then(() => {
545 simulateCombo('down enter');
546
547 instance.input.should.
548 have.text(getSuggestionText(suggestions[0]) + completeQuery.substring(middleCaret));
549 });
550 });
551
552 it('should complete selected suggestion by tab in the middle of phrase', () => {
553 const instance = mountQueryAssist({
554 query: completeQuery,
555 caret: middleCaret
556 }).instance();
557
558 return instance.requestData().then(() => {
559 simulateCombo('down down down tab');
560
561 instance.input.should.have.text(getSuggestionText(suggestions[2]));
562 });
563 });
564 });
565
566 describe('callbacks', () => {
567 let onApply;
568 beforeEach(() => {
569 onApply = sandbox.stub();
570 });
571
572 it('should call onApply', () => {
573 mountQueryAssist({
574 onApply
575 });
576
577 simulateCombo('enter');
578 onApply.should.have.been.calledWithMatch({
579 query: testQuery,
580 caret: testQueryLength
581 });
582 });
583
584 it('should call onApply when press ctrl/cmd + enter', () => {
585 mountQueryAssist({
586 onApply
587 });
588
589 simulateCombo('ctrl+enter');
590 onApply.should.have.been.calledWithMatch({
591 query: testQuery,
592 caret: testQueryLength
593 });
594 });
595
596 it('should call onApply from glass', () => {
597 const instance = mountQueryAssist({
598 glass: true,
599 onApply
600 }).instance();
601
602 Simulate.click(findDOMNode(instance.glass));
603 onApply.should.have.been.calledWithMatch({
604 query: testQuery,
605 caret: testQueryLength
606 });
607 });
608
609 it('should call onClear', () => {
610 const onClear = sandbox.stub();
611 const instance = mountQueryAssist({
612 clear: true,
613 onClear
614 }).instance();
615
616 Simulate.click(findDOMNode(instance.clear));
617 onClear.should.have.been.calledWithExactly();
618 });
619 });
620
621 describe('request data', () => {
622 it('should batch requests', () => {
623 sandbox.useFakeTimers({toFake: ['setTimeout', 'clearTimeout']});
624
625 const wrapper = mountQueryAssist();
626 const instance = wrapper.instance();
627 wrapper.setProps({
628 delay: 100
629 }, () => {
630 wrapper.prop('dataSource').resetHistory();
631
632 instance.requestData();
633 instance.requestData();
634 instance.requestData();
635 sandbox.clock.tick(400);
636
637 wrapper.prop('dataSource').should.have.been.calledOnce;
638 });
639
640 });
641 });
642
643
644 describe('custom actions', () => {
645 it('should allow to pass custom actions', () => {
646 const wrapper = mountQueryAssist({
647 actions: [
648 <div id={'A'} key={'A'}/>,
649 <div id={'B'} key={'B'}/>
650 ]
651 });
652
653 wrapper.find('#A').should.exist;
654 wrapper.find('#B').should.exist;
655 });
656 });
657});