1 |
|
2 | import React from 'react';
|
3 | import {findDOMNode} from 'react-dom';
|
4 | import {Simulate} from 'react-dom/test-utils';
|
5 | import {shallow, mount} from 'enzyme';
|
6 |
|
7 | import List from '../list/list';
|
8 | import Input from '../input/input';
|
9 | import sniffr from '../global/sniffer';
|
10 | import simulateCombo from '../../test-helpers/simulate-combo';
|
11 |
|
12 | import Select from './select';
|
13 | import styles from './select.css';
|
14 |
|
15 | const isIE11 = sniffr.browser.name === 'ie' && sniffr.browser.versionString === '11.0';
|
16 |
|
17 | function 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 |
|
27 | describe('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 | });
|