UNPKG

37.7 kBJavaScriptView Raw
1/* eslint-disable no-magic-numbers, react/no-find-dom-node */
2import React from 'react';
3import {findDOMNode} from 'react-dom';
4import {Simulate} from 'react-dom/test-utils';
5import {shallow, mount} from 'enzyme';
6
7import List from '../list/list';
8import Input from '../input/input';
9import sniffr from '../global/sniffer';
10import simulateCombo from '../../test-helpers/simulate-combo';
11
12import Select from './select';
13import styles from './select.css';
14
15const isIE11 = sniffr.browser.name === 'ie' && sniffr.browser.versionString === '11.0';
16
17function simulateInput(target, value) {
18 target.value = value;
19
20 Simulate.change(target, {target});
21
22 if (isIE11) {
23 Simulate.input(target, {target: {value}});
24 }
25}
26
27describe('Select', () => {
28 const testData = [
29 {key: 1, label: 'first1', type: List.ListProps.Type.ITEM},
30 {key: 2, label: 'test2', type: List.ListProps.Type.ITEM},
31 {key: 3, label: 'test3', type: List.ListProps.Type.ITEM},
32 {key: 4, label: 'four4', selectedLabel: '', type: List.ListProps.Type.ITEM}
33 ];
34
35 const defaultProps = () => ({
36 data: testData,
37 selected: testData[0],
38 onChange: sandbox.spy(),
39 onFilter: sandbox.spy(),
40 onFocus: sandbox.spy(),
41 onBlur: sandbox.spy(),
42 filter: true
43 });
44
45 let mountWrapper;
46 const shallowSelect = props => shallow(<Select {...defaultProps()} {...props}/>);
47 const mountSelect = props => {
48 mountWrapper = mount(<Select {...defaultProps()} {...props}/>);
49 return mountWrapper;
50 };
51
52 afterEach(() => {
53 if (mountWrapper) {
54 mountWrapper.unmount();
55 mountWrapper = null;
56 }
57 });
58
59 it('Should initialize', () => {
60 shallowSelect().should.exist;
61 });
62
63 it('Should save selected item in state', () => {
64 const wrapper = mountSelect();
65 wrapper.should.have.state('selected', wrapper.prop('selected'));
66 });
67
68 it('Should provide select types', () => {
69 Select.Type.should.exist;
70 Select.Type.BUTTON.should.exist;
71 Select.Type.INPUT.should.exist;
72 Select.Type.CUSTOM.should.exist;
73 Select.Type.MATERIAL.should.exist;
74 Select.Type.INLINE.should.exist;
75 });
76
77 it('Should take provided className', () => {
78 const wrapper = shallowSelect({className: 'foo-bar'});
79 wrapper.should.have.className('foo-bar');
80 });
81
82 it('Should compute selected index', () => {
83 const instance = shallowSelect().instance();
84 const selectedIndex = instance._getSelectedIndex(testData[2], testData);
85 selectedIndex.should.equal(2);
86 });
87
88 it('should update rendered data if props change', () => {
89 const wrapper = shallowSelect();
90 wrapper.setProps({data: [testData[0]]});
91 wrapper.state('shownData').should.deep.equal([testData[0]]);
92 });
93
94 it('Should use selectedLabel for select button title if provided', () => {
95 const wrapper = shallowSelect({
96 selected: {
97 key: 1, label: 'test1', selectedLabel: 'testLabel'
98 }
99 });
100 const instance = wrapper.instance();
101 const selectedLabel = instance._getSelectedString();
102 selectedLabel.should.equal('testLabel');
103 });
104
105 it('Should use label for select button title', () => {
106 const instance = shallowSelect().instance();
107 const selectedLabel = instance._getSelectedString();
108 selectedLabel.should.equal('first1');
109 });
110
111 it('Should clear selected on clearing', () => {
112 const wrapper = shallowSelect();
113 const instance = wrapper.instance();
114 instance.clear();
115 wrapper.should.have.state('selected', null);
116 });
117
118 it('Should call onChange on clearing', () => {
119 const wrapper = mountSelect();
120 const instance = wrapper.instance();
121 instance.clear();
122 wrapper.prop('onChange').should.be.calledOnce;
123 wrapper.prop('onChange').should.be.called.calledWith(null);
124 });
125
126 it('Should pass selected item and event to onChange', () => {
127 const wrapper = mountSelect();
128 const instance = wrapper.instance();
129 instance._listSelectHandler({item: 'foo'}, {nativeEvent: 'foo'});
130 wrapper.prop('onChange').should.be.called.calledWith({item: 'foo'}, {nativeEvent: 'foo'});
131 });
132
133 it('Should clear selected when rerendering with no selected item', () => {
134 const wrapper = shallowSelect();
135 wrapper.setProps({selected: null});
136 wrapper.should.have.state('selected', null);
137 });
138
139 it('Should handle UP, DOWN and ENTER shortcuts', () => {
140 const wrapper = shallowSelect();
141 const instance = wrapper.instance();
142 const shortcutsMap = instance.getShortcutsMap();
143 shortcutsMap.enter.should.exist;
144 shortcutsMap.up.should.exist;
145 shortcutsMap.down.should.exist;
146 });
147
148 it('Should generate unique scope for shortcuts', () => {
149 const firstTimeScope = shallowSelect().instance().shortcutsScope;
150 const secondTimeScope = shallowSelect().instance().shortcutsScope;
151 secondTimeScope.should.not.be.equal(firstTimeScope);
152 });
153
154 it('Should open popup on key handling if not opened', () => {
155 const wrapper = mountSelect({type: Select.Type.INPUT});
156 const instance = wrapper.instance();
157 instance._showPopup = sandbox.spy();
158 wrapper.setState({focused: true});
159 instance._inputShortcutHandler();
160 instance._showPopup.should.be.calledOnce;
161 });
162
163 it('Should not open popup if disabled', () => {
164 const wrapper = mountSelect({disabled: true});
165 const instance = wrapper.instance();
166 instance._showPopup = sandbox.spy();
167 instance._clickHandler();
168 instance._showPopup.should.not.be.called;
169 });
170
171 it('Should close popup on click if it is already open', () => {
172 const wrapper = mountSelect();
173 const instance = wrapper.instance();
174 instance._hidePopup = sandbox.spy();
175 instance._showPopup();
176 instance._clickHandler();
177 instance._hidePopup.should.be.called;
178 });
179
180 it('Should call onAdd on adding', () => {
181 const wrapper = mountSelect({onAdd: sandbox.spy()});
182 const instance = wrapper.instance();
183 instance.addHandler();
184 wrapper.prop('onAdd').should.be.calledOnce;
185 });
186
187 it('Should call onFocus on input focus', () => {
188 const wrapper = mountSelect({type: Select.Type.INPUT});
189 const instance = wrapper.instance();
190
191 Simulate.focus(instance.filter);
192 wrapper.prop('onFocus').should.be.called;
193 });
194
195 it('Should call onBlur on input blur', () => {
196 const wrapper = mountSelect({type: Select.Type.INPUT});
197 const instance = wrapper.instance();
198
199 Simulate.blur(instance.filter);
200 wrapper.prop('onBlur').should.be.called;
201 });
202
203 it('Should close popup if input lost focus in INPUT mode', () => {
204 sandbox.useFakeTimers({toFake: ['setTimeout']});
205 const wrapper = mountSelect({type: Select.Type.INPUT});
206 const instance = wrapper.instance();
207 instance._showPopup();
208
209 Simulate.blur(instance.filter);
210 sandbox.clock.tick();
211 instance._popup.props.hidden.should.be.true;
212 });
213
214 it('Should not close popup while clicking on popup in INPUT mode', () => {
215 sandbox.useFakeTimers({toFake: ['setTimeout']});
216 const wrapper = mountSelect({type: Select.Type.INPUT});
217 const instance = wrapper.instance();
218 instance._showPopup();
219
220 Simulate.mouseDown(findDOMNode(instance._popup.list));
221 Simulate.blur(instance.filter);
222 sandbox.clock.tick();
223 instance._popup.props.hidden.should.be.false;
224 });
225
226 describe('componentWillReceiveProps', () => {
227
228 let wrapper;
229 let instance;
230 beforeEach(() => {
231 wrapper = shallowSelect();
232 instance = wrapper.instance();
233 });
234
235
236 beforeEach(() => {
237 sandbox.stub(instance, 'setState');
238 sandbox.stub(instance, '_handleMultipleToggling');
239 });
240
241
242 it('Should update shown data', () => {
243 instance.componentWillReceiveProps({data: []});
244
245 instance.setState.should.be.calledWithMatch({shownData: []});
246 });
247
248 it('Should not update shown data if data is not passed', () => {
249 instance.componentWillReceiveProps({});
250
251 instance.setState.should.not.be.calledWithMatch({shownData: sandbox.match.defined});
252 });
253
254 it('Should not update shown data if data the same as previous', () => {
255 instance.componentWillReceiveProps({data: instance.props.data});
256
257 instance.setState.should.not.be.calledWithMatch({shownData: sandbox.match.defined});
258 });
259
260 it('Should toggle multiple state', () => {
261 const newMultiple = !instance.props.multiple;
262 instance.componentWillReceiveProps({multiple: newMultiple});
263
264 instance._handleMultipleToggling.should.be.calledWith(newMultiple);
265 });
266
267 it('Should not toggle multiple state if value the same as previous', () => {
268 const newMultiple = instance.props.multiple;
269 instance.componentWillReceiveProps({multiple: newMultiple});
270
271 instance._handleMultipleToggling.should.not.be.called;
272 });
273
274 it('Should update selected index for select', () => {
275 const selectedItem = createItem();
276
277 instance.props = {multiple: false, selected: null, data: [selectedItem, createItem()]};
278
279 instance.componentWillReceiveProps({selected: selectedItem, data: instance.props.data});
280
281 instance.setState.should.be.calledWithMatch({selectedIndex: sandbox.match.defined});
282 });
283
284 it('Should not update selected index if selected is the same as previous', () => {
285 const selectedItem = createItem();
286
287 instance.props = {
288 multiple: false,
289 selected: selectedItem,
290 data: [selectedItem, createItem()]
291 };
292
293 instance.componentWillReceiveProps({selected: selectedItem, data: instance.props.data});
294
295 instance.setState.should.not.be.calledWithMatch({selectedIndex: sandbox.match.defined});
296 });
297
298 it('Should update selected index for multiple select if selected is changed', () => {
299 const selectedItem = createItem();
300
301 instance.props = {multiple: true, selected: [], data: [selectedItem]};
302
303 instance.componentWillReceiveProps({selected: [selectedItem]});
304
305 instance.setState.should.be.calledWithMatch({selectedIndex: 0});
306 });
307
308 it('Should update selected index for multiple select if selected is changed but count of element is the same', () => {
309 const selectedItem = createItem();
310
311 instance.props = {multiple: true, selected: [], data: [selectedItem, createItem()]};
312
313 instance.componentWillReceiveProps({selected: [selectedItem, createItem()]});
314
315 instance.setState.should.be.calledWithMatch({selectedIndex: 0});
316 });
317
318 it('Should not update selected index for multiple select if selected is not changed', () => {
319 const selectedItem = createItem();
320
321 instance.props = {multiple: true, selected: [], data: [selectedItem]};
322
323 instance.componentWillReceiveProps({});
324
325 instance.setState.should.not.be.calledWithMatch({selectedIndex: sandbox.match.defined});
326 });
327
328 it('Should not update selected index for multiple select if items inside the selected list are the same and order is same', () => {
329 const selectedItem1 = createItem();
330 const selectedItem2 = createItem();
331
332 instance.props = {
333 multiple: true,
334 selected: [selectedItem1, selectedItem2],
335 data: [selectedItem1, createItem(), selectedItem2]
336 };
337
338 instance.componentWillReceiveProps({selected: [selectedItem1, selectedItem2]});
339
340 instance.setState.should.not.be.calledWithMatch({selectedIndex: sandbox.match.defined});
341 });
342
343 it('Should not update selected index for multiple select if items inside the selected list are the same but order is changed', () => {
344 const selectedItem1 = createItem();
345 const selectedItem2 = createItem();
346
347 instance.props = {
348 multiple: true,
349 selected: [selectedItem1, selectedItem2],
350 data: [selectedItem1, createItem(), selectedItem2]
351 };
352
353 instance.componentWillReceiveProps({selected: [selectedItem2, selectedItem1]});
354
355 instance.setState.should.not.be.calledWithMatch({selectedIndex: sandbox.match.defined});
356 });
357
358 function createItem() {
359 createItem.key = (createItem.key || 0) + 1;
360 return {key: createItem.key};
361 }
362 });
363
364 describe('DOM', () => {
365 it('Should place select button inside container', () => {
366 const wrapper = shallowSelect();
367 wrapper.should.have.className(styles.select);
368 });
369
370 it('Should disable select button if needed', () => {
371 const wrapper = mountSelect({
372 disabled: true
373 });
374 wrapper.should.have.className(styles.disabled);
375 wrapper.instance().button.should.have.attr('disabled');
376 });
377
378 it('Should not disable select button if not needed', () => {
379 const wrapper = mountSelect({
380 disabled: false
381 });
382 wrapper.instance().button.should.not.have.attr('disabled');
383 });
384
385 it('Should place input inside in INPUT mode', () => {
386 const wrapper = shallowSelect({type: Select.Type.INPUT});
387 wrapper.should.have.descendants(Input);
388 });
389
390 it('Should place icons inside', () => {
391 const wrapper = shallowSelect();
392 wrapper.should.have.descendants(`.${styles.icons}`);
393 });
394
395 it('Should add selected item icon to button', () => {
396 const wrapper = shallowSelect({
397 selected: {
398 key: 1,
399 label: 'test',
400 icon: 'fakeImageUrl'
401 }
402 });
403 wrapper.should.have.descendants(`.${styles.selectedIcon}`);
404 });
405
406 it('Should not display selected item icon if it is not provided', () => {
407 const wrapper = shallowSelect({selected: {key: 1, label: 'test', icon: null}});
408 wrapper.should.not.have.descendants(`.${styles.selectedIcon}`);
409 });
410
411 it('Should display selected item icon', () => {
412 const wrapper = mountSelect({
413 selected: {
414 key: 1,
415 label: 'test',
416 icon: 'http://fake.image/'
417 }
418 });
419 const icon = wrapper.find(`.${styles.selectedIcon}`).getDOMNode();
420 icon.style.backgroundImage.should.contain('http://fake.image/');
421 });
422
423 it('Should place icons inside in INPUT mode', () => {
424 const wrapper = shallowSelect({type: Select.Type.INPUT});
425 wrapper.should.have.descendants(`.${styles.icons}`);
426 });
427
428 it('Should open select dropdown on click', () => {
429 const wrapper = shallowSelect();
430 const instance = wrapper.instance();
431 sandbox.spy(instance, '_showPopup');
432 wrapper.simulate('click');
433
434 instance._showPopup.should.be.called;
435 });
436
437 describe('Bottom toolbar', () => {
438 it('Should not add "Add" button if enabled but filter query is empty', () => {
439 const wrapper = mountSelect({add: {}});
440 const instance = wrapper.instance();
441 instance.filterValue = sandbox.stub().returns('');
442 instance._showPopup();
443 instance._popup.popup.popup.should.not.contain('.ring-select__button');
444 });
445
446 it('Should add "Add" button if enabled and filter query not empty', () => {
447 const wrapper = mountSelect({add: {}});
448 const instance = wrapper.instance();
449 instance.filterValue = sandbox.stub().returns('test');
450 instance._showPopup();
451 instance._popup.popup.popup.should.contain(`.${styles.button}`);
452 });
453
454 it('Should add "Add" button if alwaysVisible is set', () => {
455 const wrapper = mountSelect({
456 add: {
457 alwaysVisible: true
458 }
459 });
460 const instance = wrapper.instance();
461 instance._showPopup();
462 instance._popup.popup.popup.should.contain(`.${styles.button}`);
463 });
464
465 it('Should place label instead filterValue to "Add" button if alwaysVisible is set', () => {
466 const wrapper = mountSelect({
467 add: {
468 alwaysVisible: true,
469 label: 'Add Something'
470 }
471 });
472 const instance = wrapper.instance();
473 instance._showPopup();
474 const addButton = instance._popup.popup.popup.query(`.${styles.button}`);
475
476 addButton.should.contain.text('Add Something');
477 });
478
479 it('Should add hint if specified', () => {
480 const wrapper = mountSelect({
481 hint: 'blah blah'
482 });
483 const instance = wrapper.instance();
484 instance._showPopup();
485 instance._popup.popup.popup.should.contain('[data-test=ring-list-hint]');
486 });
487
488 it('Hint should be placed under "add" button', () => {
489 const wrapper = mountSelect({
490 add: {},
491 hint: 'blah blah'
492 });
493 const instance = wrapper.instance();
494 instance._showPopup();
495 const hint = instance._popup.popup.popup.queryAll('[data-test=ring-list-hint]');
496
497 hint.should.exist;
498 });
499 });
500 });
501
502 describe('getListItems', () => {
503 it('Should filter items by label', () => {
504 const wrapper = shallowSelect();
505 const instance = wrapper.instance();
506 const filtered = instance.getListItems('test3');
507 filtered.length.should.equal(1);
508 filtered[0].label.should.equal('test3');
509 });
510
511 it('Should filter items by part of label', () => {
512 const wrapper = shallowSelect();
513 const instance = wrapper.instance();
514 const filtered = instance.getListItems('test');
515 filtered.length.should.equal(2);
516 });
517
518 it('Should not filter separators', () => {
519 const separators = [{
520 type: List.ListProps.Type.SEPARATOR,
521 key: 1,
522 description: 'test'
523 }];
524 const wrapper = shallowSelect({data: separators});
525 const instance = wrapper.instance();
526
527 const filtered = instance.getListItems('foo');
528 filtered.should.deep.equal(separators);
529 });
530
531 it('Should not filter hints', () => {
532 const hints = [{
533 type: List.ListProps.Type.HINT,
534 key: 1,
535 description: 'test'
536 }];
537 const wrapper = shallowSelect({data: hints});
538 const instance = wrapper.instance();
539
540 const filtered = instance.getListItems('foo');
541 filtered.should.deep.equal(hints);
542 });
543
544 it('Should filter custom items with label', () => {
545 const customItems = [{
546 type: List.ListProps.Type.CUSTOM,
547 key: 1,
548 label: 'bar',
549 template: <div/>
550 }];
551 const wrapper = shallowSelect({data: customItems});
552 const instance = wrapper.instance();
553
554 const filtered = instance.getListItems('foo');
555 filtered.should.deep.equal([]);
556 });
557
558 it('Should not filter items without label', () => {
559 const items = [{
560 key: 1,
561 description: 'test'
562 }];
563 const wrapper = shallowSelect({data: items});
564 const instance = wrapper.instance();
565
566 const filtered = instance.getListItems('foo');
567 filtered.should.deep.equal(items);
568 });
569
570 it('Should use custom filter.fn if provided', () => {
571 const filterStub = sandbox.stub().returns(true);
572
573 const wrapper = shallowSelect({
574 filter: {fn: filterStub}
575 });
576 const instance = wrapper.instance();
577
578 const filtered = instance.getListItems('test3');
579
580 filtered.length.should.equal(testData.length);
581 filterStub.should.have.callCount(4);
582 });
583
584 it('Should write filter query on add button if enabled', () => {
585 const wrapper = shallowSelect({
586 add: {
587 prefix: 'Add some'
588 }
589 });
590 const instance = wrapper.instance();
591
592 instance.getListItems('foo');
593
594 instance._addButton.label.should.equal('foo');
595 });
596 });
597
598 describe('Filtering', () => {
599 it('Should call onFilter on input changes', () => {
600 const wrapper = mountSelect();
601 const instance = wrapper.instance();
602 wrapper.setState({
603 focused: true,
604 showPopup: true
605 });
606 simulateInput(instance._popup.filter, 'a');
607 wrapper.prop('onFilter').should.be.called;
608 });
609
610 it('Should save input changes', () => {
611 const wrapper = mountSelect();
612 const instance = wrapper.instance();
613 wrapper.setState({showPopup: true});
614 simulateInput(instance._popup.filter, 'a');
615 wrapper.should.have.state('filterValue', 'a');
616 });
617
618 it('Should open popup on input changes if in focus', () => {
619 const wrapper = mountSelect({type: Select.Type.INPUT});
620 const instance = wrapper.instance();
621 instance._showPopup = sandbox.spy();
622 wrapper.setState({focused: true});
623 simulateInput(instance.filter, 'a');
624 instance._showPopup.should.be.called;
625 });
626
627 it('should filter if not focused but not in input mode', () => {
628 const wrapper = mountSelect({type: Select.Type.MATERIAL});
629 const instance = wrapper.instance();
630 wrapper.setState({showPopup: true});
631 simulateInput(instance._popup.filter, 'a');
632
633 wrapper.prop('onFilter').should.be.called;
634 });
635
636 it('Should not open popup on input changes if not in focus', () => {
637 const wrapper = mountSelect({type: Select.Type.INPUT});
638 const instance = wrapper.instance();
639
640 instance._showPopup = sandbox.spy();
641 simulateInput(instance.filter, 'a');
642 instance._showPopup.should.not.be.called;
643 });
644
645 it('Should return empty string if not input mode and filter is disabled', () => {
646 const wrapper = shallowSelect({filter: false, type: Select.Type.MATERIAL});
647 const instance = wrapper.instance();
648
649 instance.filterValue().should.equal('');
650 });
651
652 it('Should return input value if input mode enabled', () => {
653 const wrapper = mountSelect({filter: false, type: Select.Type.INPUT});
654 const instance = wrapper.instance();
655 wrapper.setState({focused: true});
656 simulateInput(instance.filter, 'test input');
657 instance.filterValue().should.equal('test input');
658 });
659
660 it('Should set value to popup input if passed', () => {
661 const wrapper = mountSelect();
662 const instance = wrapper.instance();
663 instance._showPopup();
664 instance.filterValue('test');
665 findDOMNode(instance._popup.filter).value.should.equal('test');
666 });
667
668 it('Should set target input value in input mode', () => {
669 const wrapper = mountSelect({filter: false, type: Select.Type.INPUT});
670 const instance = wrapper.instance();
671
672 wrapper.setState({focused: true});
673 instance.filterValue('test');
674 instance.filter.value.should.equal('test');
675 });
676
677 it('Should clear filter value when closing', () => {
678 const wrapper = mountSelect();
679 const instance = wrapper.instance();
680 instance.filterValue('test');
681 instance._showPopup();
682 instance._hidePopup();
683 instance._showPopup();
684 findDOMNode(instance._popup.filter).value.should.equal('');
685 });
686 });
687
688 describe('Multiple', () => {
689 const defaultPropsMultiple = () => ({
690 data: testData,
691 selected: testData.slice(0, 2),
692 filter: true,
693 multiple: true,
694 onChange: sandbox.spy()
695 });
696
697 const shallowSelectMultiple = props => shallow(
698 <Select {...defaultPropsMultiple()} {...props}/>
699 );
700 const mountSelectMultiple = props => {
701 mountWrapper = mount(
702 <Select {...defaultPropsMultiple()} {...props}/>
703 );
704 return mountWrapper;
705 };
706
707 it('Should fill _multipleMap on initialization', () => {
708 const wrapper = mountSelectMultiple();
709 const instance = wrapper.instance();
710 instance._multipleMap['1'].should.be.true;
711 });
712
713 it('Should fill _multipleMap on _rebuildMultipleMap', () => {
714 const wrapper = mountSelectMultiple();
715 const instance = wrapper.instance();
716 instance._rebuildMultipleMap(testData.slice(1, 2));
717 instance._multipleMap['2'].should.be.true;
718 });
719
720 it('Should construct label from selected array', () => {
721 const wrapper = shallowSelectMultiple();
722 const instance = wrapper.instance();
723 const selectedLabel = instance._getSelectedString();
724 selectedLabel.should.equal('first1, test2');
725 });
726
727 it('Should skip empty labels', () => {
728 const wrapper = shallowSelectMultiple({
729 selected: testData.slice(2)
730 });
731 const instance = wrapper.instance();
732 const selectedLabel = instance._getSelectedString();
733 selectedLabel.should.equal('test3');
734 });
735
736 it('Should detect selection is empty according on not empty array', () => {
737 const wrapper = shallowSelectMultiple();
738 const instance = wrapper.instance();
739 instance._selectionIsEmpty().should.be.false;
740 });
741
742 it('Should detect selection is empty according on empty array', () => {
743 const wrapper = shallowSelectMultiple({selected: []});
744 const instance = wrapper.instance();
745 instance._selectionIsEmpty().should.be.true;
746 });
747
748 it('Should clear selected on clearing', () => {
749 const wrapper = shallowSelectMultiple();
750 const instance = wrapper.instance();
751 instance.clear();
752 wrapper.state('selected').length.should.equal(0);
753 });
754
755 it('Should call onChange on clearing', () => {
756 const wrapper = mountSelectMultiple();
757 const instance = wrapper.instance();
758 instance.clear();
759 wrapper.prop('onChange').should.be.calledOnce;
760 wrapper.prop('onChange').should.be.called.calledWith([]);
761 });
762
763 it('Should clear selected when rerendering with no selected item in multiple mode', () => {
764 const wrapper = shallowSelectMultiple();
765 wrapper.setProps({selected: null});
766 wrapper.state('selected').should.deep.equal([]);
767 });
768
769 it('Should update selected checkboxes on selected update', () => {
770 const wrapper = shallowSelectMultiple();
771 const instance = wrapper.instance();
772 wrapper.setProps({selected: []});
773 instance.getListItems(instance.filterValue())[0].checkbox.should.be.false;
774 });
775
776 describe('On selecting', () => {
777 let wrapper;
778 let instance;
779 beforeEach(() => {
780 wrapper = mountSelectMultiple();
781 instance = wrapper.instance();
782 });
783
784 it('Should add item to multiple map on selecting item', () => {
785 instance._listSelectHandler(testData[3]);
786 instance._multipleMap['4'].should.be.true;
787 });
788
789 it('Should select just picked item on selecting by clicking item', () => {
790 const lengthBefore = testData.slice(0, 2).length;
791 instance._listSelectHandler(testData[3]);
792 wrapper.state('selected').length.should.equal(lengthBefore + 1);
793 });
794
795 it('Should add item to selection on clicking by checkbox', () => {
796 const lengthBefore = testData.slice(0, 2).length;
797 instance._listSelectHandler(testData[3], {
798 originalEvent: {
799 target: {
800 matches: () => true
801 }
802 }
803 });
804 wrapper.state('selected').length.should.equal(lengthBefore + 1);
805 });
806
807 it('Should close popup on selecting by item', () => {
808 instance._hidePopup = sandbox.spy();
809 instance._listSelectHandler(testData[3], {
810 originalEvent: {
811 target: {
812 matches: () => false
813 }
814 }
815 });
816 instance._hidePopup.should.have.been.called;
817 });
818
819 it('Should not close popup on selecting by checkbox', () => {
820 instance._hidePopup = sandbox.spy();
821 instance._listSelectHandler(testData[3], {
822 originalEvent: {
823 target: {
824 matches: () => true
825 }
826 }
827 });
828 instance._hidePopup.should.not.be.called;
829 });
830
831 it('Should reset filter', () => {
832 wrapper.setState({filterValue: 'query'});
833 instance._listSelectHandler(testData[3]);
834 wrapper.state('filterValue').should.equal('');
835 });
836 });
837
838 describe('On deselecting', () => {
839 it('Should remove item from selected on deselecting', () => {
840 const wrapper = mountSelectMultiple();
841 const instance = wrapper.instance();
842 const lengthBefore = testData.slice(0, 2).length;
843 instance._listSelectHandler(testData[0]);
844 wrapper.state('selected').length.should.equal(lengthBefore - 1);
845 });
846
847 it('Should call onDeselect on deselecting item', () => {
848 const wrapper = mountSelectMultiple({
849 onDeselect: sandbox.spy()
850 });
851 const instance = wrapper.instance();
852 instance._listSelectHandler(testData[0]);
853 wrapper.prop('onDeselect').should.be.calledWith(testData[0]);
854 });
855 });
856
857 });
858
859 describe('On selecting', () => {
860 it('Should not react on selecting disabled element', () => {
861 const wrapper = shallowSelect();
862 const instance = wrapper.instance();
863 instance.setState = sandbox.spy();
864
865 instance._listSelectHandler({
866 key: 1,
867 label: 'test',
868 disabled: true
869 });
870
871 instance.setState.should.not.be.called;
872 });
873
874 it('Should not react on selecting separator', () => {
875 const wrapper = shallowSelect();
876 const instance = wrapper.instance();
877 instance.setState = sandbox.spy();
878
879 instance._listSelectHandler({
880 key: 1,
881 label: 'test',
882 rgItemType: List.ListProps.Type.SEPARATOR
883 });
884
885 instance.setState.should.not.be.called;
886 });
887
888 it('Should react on selecting custom item', () => {
889 const wrapper = shallowSelect();
890 const instance = wrapper.instance();
891 instance.setState = sandbox.spy();
892
893 instance._listSelectHandler({
894 key: 1,
895 label: 'test',
896 type: List.ListProps.Type.CUSTOM
897 });
898
899 instance.setState.should.be.called;
900 });
901
902 it('Should set selected on selecting', () => {
903 const wrapper = shallowSelect();
904 const instance = wrapper.instance();
905 instance._listSelectHandler(testData[3]);
906 wrapper.should.have.state('selected', testData[3]);
907 });
908
909 it('Should set call onSelect on selecting', () => {
910 const wrapper = mountSelect({
911 onSelect: sandbox.spy()
912 });
913 const instance = wrapper.instance();
914 instance._listSelectHandler(testData[1]);
915 wrapper.prop('onSelect').should.be.calledOnce;
916 });
917
918 it('Should set call onChange on selecting', () => {
919 const wrapper = mountSelect({
920 onChange: sandbox.spy()
921 });
922 const instance = wrapper.instance();
923 instance._listSelectHandler(testData[1]);
924 wrapper.prop('onChange').should.be.calledOnce;
925 });
926
927 it('Should hide popup on selecting', () => {
928 const wrapper = mountSelect();
929 const instance = wrapper.instance();
930 instance._hidePopup = sandbox.spy();
931 instance._listSelectHandler(testData[1]);
932 instance._hidePopup.should.be.calledOnce;
933 });
934 });
935
936 describe('Popup', () => {
937 let container;
938 const mountSelectToContainer = props => {
939 mountWrapper = mount(
940 <Select {...props}/>,
941 {
942 attachTo: container
943 }
944 );
945 };
946 beforeEach(() => {
947 container = document.createElement('div');
948 document.body.appendChild(container);
949 });
950
951 afterEach(() => {
952 document.body.removeChild(container);
953 container = null;
954 });
955
956 it('Should pass loading message and indicator to popup if loading', () => {
957 const wrapper = mountSelect({loading: true, loadingMessage: 'test message'});
958 const instance = wrapper.instance();
959 instance._popup.rerender = sandbox.stub();
960 instance._showPopup();
961 instance._popup.props.message.should.equal('test message');
962 instance._popup.props.loading.should.be.true;
963 });
964
965 it('Should pass notFoundMessage message to popup if not loading and data is empty', () => {
966 const wrapper = mountSelect({data: [], notFoundMessage: 'test not found'});
967 const instance = wrapper.instance();
968 instance._popup.rerender = sandbox.stub();
969 instance._showPopup();
970 instance._popup.props.message.should.equal('test not found');
971 });
972
973 describe('filter focusing', () => {
974 const SHOW_TIMEOUT = 300;
975
976 beforeEach(() => {
977 mountSelectToContainer({filter: true});
978 });
979
980 it('Should focus the filter on opening', done => {
981 const instance = mountWrapper.instance();
982 instance._showPopup();
983 // Can't use fake timers here, as Popup redraws by requestAnimationFrame.
984 // Stabbing it isn't possible either, as it hangs IE11
985 setTimeout(() => {
986 instance._popup.filter.should.equal(document.activeElement);
987 done();
988 }, SHOW_TIMEOUT);
989 });
990
991 it('Should focus the filter on second opening', done => {
992 const instance = mountWrapper.instance();
993 instance._showPopup();
994 instance._hidePopup();
995 instance._showPopup();
996 setTimeout(() => {
997 instance._popup.filter.should.equal(document.activeElement);
998 done();
999 }, SHOW_TIMEOUT);
1000 });
1001 });
1002
1003 it('Should restore focus on select in button mode after closing popup', () => {
1004 mountSelectToContainer({
1005 data: testData,
1006 filter: true
1007 });
1008 const instance = mountWrapper.instance();
1009
1010 instance._showPopup();
1011 instance._hidePopup(true);
1012 document.activeElement.should.equal(instance.button);
1013 });
1014
1015 describe('Focus after close', () => {
1016 let instance;
1017 let targetInput;
1018 beforeEach(() => {
1019 targetInput = document.createElement('input');
1020 document.body.appendChild(targetInput);
1021
1022 mountSelectToContainer({
1023 data: testData,
1024 filter: true,
1025 targetElement: targetInput
1026 });
1027 instance = mountWrapper.instance();
1028
1029 instance._showPopup();
1030 });
1031
1032 afterEach(() => {
1033 document.body.removeChild(targetInput);
1034 targetInput = null;
1035 });
1036
1037 it('Should restore focus on provided target element after closing popup', () => {
1038 instance._hidePopup(true);
1039
1040 targetInput.should.equal(document.activeElement);
1041 });
1042
1043 it('Should restore focus on provided target element after closing popup with keyboard', () => {
1044 simulateCombo('esc');
1045 targetInput.should.equal(document.activeElement);
1046 });
1047
1048 it('Should not restore focus on provided target element after closing popup with not keyboard event', () => {
1049 Simulate.click(document.body);
1050
1051 targetInput.should.not.equal(document.activeElement);
1052 });
1053
1054 it('Should not restore focus on provided target element after closing popup', () => {
1055 instance._hidePopup();
1056
1057 targetInput.should.not.equal(document.activeElement);
1058 });
1059 });
1060
1061 });
1062
1063 describe('_resetMultipleSelectionMap', () => {
1064 let instance;
1065 beforeEach(() => {
1066 instance = shallowSelect().instance();
1067 });
1068
1069 it('should reset map', () => {
1070 instance._multipleMap[0] = true;
1071
1072 instance._resetMultipleSelectionMap();
1073
1074 Object.keys(instance._multipleMap).length.
1075 should.be.equal(0);
1076 });
1077 });
1078
1079
1080 describe('_getResetOption', () => {
1081 let instance;
1082
1083 it('should create tags reset option', () => {
1084 const labelMock = 'label';
1085 const tagsMock = {
1086 reset: {
1087 key: labelMock,
1088 label: labelMock,
1089 glyph: 'glyph',
1090 rgItemType: List.ListProps.Type.LINK,
1091 className: 'cssClass',
1092 onClick: () => {}
1093 }
1094 };
1095 instance = shallowSelect({
1096 selected: [{}, {}],
1097 tags: tagsMock
1098 }).instance();
1099
1100 const resetOption = instance._getResetOption();
1101
1102 resetOption.rgItemType.should.be.equal(List.ListProps.Type.ITEM);
1103 resetOption.glyph.should.be.equal(tagsMock.reset.glyph);
1104 resetOption.onClick.should.be.an.instanceof(Function);
1105 });
1106
1107 it('should not create tags reset option if it is not provided', () => {
1108 instance = shallowSelect({
1109 select: [{}, {}]
1110 }).instance();
1111
1112 should.not.exist(instance._getResetOption());
1113 });
1114
1115 it('should not create tags reset option without selected elements', () => {
1116 instance = shallowSelect({
1117 tags: {reset: {}}
1118 }).instance();
1119
1120 should.not.exist(instance._getResetOption());
1121 });
1122 });
1123
1124
1125 describe('_prependResetOption', () => {
1126 let instance;
1127
1128 it('should prepend reset option', () => {
1129 instance = getInstance();
1130 const newShownData = instance._prependResetOption([{}]);
1131
1132 newShownData.length.should.be.equal(4);
1133 });
1134
1135 it('should prepend reset option with separator', () => {
1136 instance = getInstance(true);
1137
1138 const newShownData = instance._prependResetOption([{}]);
1139
1140 newShownData.length.should.be.equal(5);
1141 });
1142
1143 it('should not prepend reset option', () => {
1144 instance = getInstance(true, []);
1145
1146 const newShownData = instance._prependResetOption([]);
1147
1148 newShownData.length.should.be.equal(0);
1149 });
1150
1151 function getInstance(resetWithSeparator, selected) {
1152 const resetMock = {
1153 reset: {}
1154 };
1155 if (resetWithSeparator) {
1156 resetMock.reset.separator = true;
1157 }
1158
1159 return shallowSelect({
1160 selected: selected || [{}, {}],
1161 tags: resetMock
1162 }).instance();
1163 }
1164 });
1165
1166
1167 describe('_redrawPopup', () => {
1168 let clock;
1169
1170 beforeEach(() => {
1171 clock = sandbox.useFakeTimers({toFake: ['setTimeout']});
1172 });
1173
1174 it('should not redraw a popup in default mode', () => {
1175 const instance = shallowSelect().instance();
1176 sandbox.stub(instance, '_showPopup');
1177 instance._redrawPopup();
1178
1179 clock.tick();
1180 instance._showPopup.should.not.have.been.called;
1181 });
1182
1183 it('should redraw a popup in multiselect mode', () => {
1184 const instance = shallowSelect({
1185 multiple: true,
1186 selected: testData.slice(1)
1187 }).instance();
1188
1189 sandbox.stub(instance, '_showPopup');
1190 instance._redrawPopup();
1191
1192 clock.tick();
1193 instance._showPopup.should.have.been.called;
1194 });
1195 });
1196});