react-typeahead
Version:
React-based typeahead and typeahead-tokenizer
479 lines (417 loc) • 17.7 kB
JavaScript
var _ = require('lodash');
var assert = require('chai').assert;
var sinon = require('sinon');
var React = require('react/addons');
var Typeahead = require('../src/typeahead');
var TypeaheadOption = require('../src/typeahead/option');
var TypeaheadSelector = require('../src/typeahead/selector');
var Keyevent = require('../src/keyevent');
var TestUtils = React.addons.TestUtils;
function simulateTextInput(component, value) {
var node = component.refs.entry.getDOMNode();
node.value = value;
TestUtils.Simulate.change(node);
return TestUtils.scryRenderedComponentsWithType(component, TypeaheadOption);
}
var BEATLES = ['John', 'Paul', 'George', 'Ringo'];
var BEATLES_COMPLEX = [
{
firstName: 'John',
lastName: 'Lennon',
nameWithTitle: 'John Winston Ono Lennon MBE'
}, {
firstName: 'Paul',
lastName: 'McCartney',
nameWithTitle: 'Sir James Paul McCartney MBE'
}, {
firstName: 'George',
lastName: 'Harrison',
nameWithTitle: 'George Harrison MBE'
}, {
firstName: 'Ringo',
lastName: 'Starr',
nameWithTitle: 'Richard Starkey Jr. MBE'
}
];
describe('Typeahead Component', function() {
describe('sanity', function() {
beforeEach(function() {
this.component = TestUtils.renderIntoDocument(<Typeahead options={
BEATLES
} />);
});
it('should fuzzy search and render matching results', function() {
// input value: num of expected results
var testplan = {
'o': 3,
'pa': 1,
'Grg': 1,
'Ringo': 1,
'xxx': 0
};
_.each(testplan, function(expected, value) {
var results = simulateTextInput(this.component, value);
assert.equal(results.length, expected, 'Text input: ' + value);
}, this);
});
it('does not change the url hash when clicking on options', function() {
var results = simulateTextInput(this.component, 'o');
var firstResult = results[0];
var anchor = TestUtils.findRenderedDOMComponentWithTag(firstResult, 'a');
var href = anchor.getDOMNode().getAttribute('href');
assert.notEqual(href, '#');
});
describe('keyboard controls', function() {
it('down arrow + return selects an option', function() {
var results = simulateTextInput(this.component, 'o');
var secondItem = results[1].getDOMNode().innerText;
var node = this.component.refs.entry.getDOMNode();
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_DOWN });
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_DOWN });
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_RETURN });
assert.equal(node.value, secondItem); // Poor Ringo
});
it('up arrow + return navigates and selects an option', function() {
var results = simulateTextInput(this.component, 'o');
var firstItem = results[0].getDOMNode().innerText;
var node = this.component.refs.entry.getDOMNode();
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_DOWN });
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_DOWN });
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_UP });
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_RETURN });
assert.equal(node.value, firstItem);
});
it('escape clears selection', function() {
var results = simulateTextInput(this.component, 'o');
var firstItem = results[0].getDOMNode();
var node = this.component.refs.entry.getDOMNode();
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_DOWN });
assert.ok(firstItem.classList.contains('hover'));
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_ESCAPE });
assert.notOk(firstItem.classList.contains('hover'));
});
it('tab to choose first item', function() {
var results = simulateTextInput(this.component, 'o');
var itemText = results[0].getDOMNode().innerText;
var node = this.component.refs.entry.getDOMNode();
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_TAB });
assert.equal(node.value, itemText);
});
it('tab to selected current item', function() {
var results = simulateTextInput(this.component, 'o');
var itemText = results[1].getDOMNode().innerText;
var node = this.component.refs.entry.getDOMNode();
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_DOWN });
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_DOWN });
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_TAB });
assert.equal(node.value, itemText);
});
it('tab on no selection should not be undefined', function() {
var results = simulateTextInput(this.component, 'oz');
assert(results.length == 0);
var node = this.component.refs.entry.getDOMNode();
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_TAB });
assert.equal("oz", node.value);
});
it('should set hover', function() {
var results = simulateTextInput(this.component, 'o');
var node = this.component.refs.entry.getDOMNode();
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_DOWN });
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_DOWN });
assert.equal(true, results[1].props.hover);
});
});
describe('component functions', function() {
beforeEach(function() {
this.sinon = sinon.sandbox.create();
});
afterEach(function() {
this.sinon.restore();
});
it('focuses the typeahead', function() {
var node = React.findDOMNode(this.component.refs.entry);
this.sinon.spy(node, 'focus');
this.component.focus();
assert.equal(node.focus.calledOnce, true);
});
});
});
describe('props', function() {
context('maxVisible', function() {
it('limits the result set based on the maxVisible option', function() {
var component = TestUtils.renderIntoDocument(<Typeahead
options={ BEATLES }
maxVisible={ 1 }
></Typeahead>);
var results = simulateTextInput(component, 'o');
assert.equal(results.length, 1);
});
});
context('displayOption', function() {
it('renders simple options verbatim when not specified', function() {
var component = TestUtils.renderIntoDocument(<Typeahead
options={ BEATLES }
/>);
var results = simulateTextInput(component, 'john');
assert.equal(results[0].getDOMNode().textContent, 'John');
});
it('renders custom options when specified as a string', function() {
var component = TestUtils.renderIntoDocument(<Typeahead
options={ BEATLES_COMPLEX }
filterOption='firstName'
displayOption='nameWithTitle'
/>);
var results = simulateTextInput(component, 'john');
assert.equal(results[0].getDOMNode().textContent, 'John Winston Ono Lennon MBE');
});
it('renders custom options when specified as a function', function() {
var component = TestUtils.renderIntoDocument(<Typeahead
options={ BEATLES_COMPLEX }
filterOption='firstName'
displayOption={ function(o, i) { return i + ' ' + o.firstName + ' ' + o.lastName; } }
/>);
var results = simulateTextInput(component, 'john');
assert.equal(results[0].getDOMNode().textContent, '0 John Lennon');
});
});
context('allowCustomValues', function() {
beforeEach(function() {
this.sinon = sinon.sandbox.create()
this.selectSpy = this.sinon.spy();
this.component = TestUtils.renderIntoDocument(<Typeahead
options={BEATLES}
allowCustomValues={3}
onOptionSelected={this.selectSpy}
></Typeahead>);
});
afterEach(function() {
this.sinon.restore();
})
it('should not display custom value if input length is less than entered', function() {
var input = this.component.refs.entry.getDOMNode();
input.value = "zz";
TestUtils.Simulate.change(input);
var results = TestUtils.scryRenderedComponentsWithType(this.component, TypeaheadOption);
assert.equal(0, results.length);
assert.equal(false, this.selectSpy.called);
});
it('should display custom value if input exceeds props.allowCustomValues', function() {
var input = this.component.refs.entry.getDOMNode();
input.value = "ZZZ";
TestUtils.Simulate.change(input);
var results = TestUtils.scryRenderedComponentsWithType(this.component, TypeaheadOption);
assert.equal(1, results.length);
assert.equal(false, this.selectSpy.called);
});
it('should call onOptionSelected when selecting from options', function() {
var results = simulateTextInput(this.component, 'o');
var firstItem = results[0].getDOMNode().innerText;
var node = this.component.refs.entry.getDOMNode();
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_DOWN });
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_DOWN });
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_UP });
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_RETURN });
assert.equal(true, this.selectSpy.called);
assert(this.selectSpy.calledWith(firstItem));
})
it('should call onOptionSelected when custom value is selected', function() {
var input = this.component.refs.entry.getDOMNode();
input.value = "ZZZ";
TestUtils.Simulate.change(input);
TestUtils.Simulate.keyDown(input, { keyCode: Keyevent.DOM_VK_DOWN });
TestUtils.Simulate.keyDown(input, { keyCode: Keyevent.DOM_VK_RETURN });
assert.equal(true, this.selectSpy.called);
assert(this.selectSpy.calledWith(input.value));
})
it('should add hover prop to customValue', function() {
var input = this.component.refs.entry.getDOMNode();
input.value = "ZZZ";
TestUtils.Simulate.change(input);
var results = TestUtils.scryRenderedComponentsWithType(this.component, TypeaheadOption);
TestUtils.Simulate.keyDown(input, { keyCode: Keyevent.DOM_VK_DOWN });
assert.equal(true, results[0].props.hover)
})
});
context('customClasses', function() {
before(function() {
var customClasses = {
input: 'topcoat-text-input',
results: 'topcoat-list__container',
listItem: 'topcoat-list__item',
listAnchor: 'topcoat-list__link',
hover: 'topcoat-list__item-active'
};
this.component = TestUtils.renderIntoDocument(<Typeahead
options={ BEATLES }
customClasses={ customClasses }
></Typeahead>);
simulateTextInput(this.component, 'o');
});
it('adds a custom class to the typeahead input', function() {
var input = this.component.refs.entry.getDOMNode();
assert.isTrue(input.classList.contains('topcoat-text-input'));
});
it('adds a custom class to the results component', function() {
var results = TestUtils.findRenderedComponentWithType(this.component, TypeaheadSelector).getDOMNode();
assert.isTrue(results.classList.contains('topcoat-list__container'));
});
it('adds a custom class to the list items', function() {
var typeaheadOptions = TestUtils.scryRenderedComponentsWithType(this.component, TypeaheadOption);
var listItem = typeaheadOptions[1].getDOMNode();
assert.isTrue(listItem.classList.contains('topcoat-list__item'));
});
it('adds a custom class to the option anchor tags', function() {
var typeaheadOptions = TestUtils.scryRenderedComponentsWithType(this.component, TypeaheadOption);
var listAnchor = typeaheadOptions[1].refs.anchor.getDOMNode();
assert.isTrue(listAnchor.classList.contains('topcoat-list__link'));
});
it('adds a custom class to the list items when active', function() {
var typeaheadOptions = TestUtils.scryRenderedComponentsWithType(this.component, TypeaheadOption);
var node = this.component.refs.entry.getDOMNode();
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_DOWN });
var listItem = typeaheadOptions[0];
var domListItem = listItem.getDOMNode();
assert.isTrue(domListItem.classList.contains('topcoat-list__item-active'));
});
});
context('defaultValue', function() {
it('should perform an initial search if a default value is provided', function() {
var component = TestUtils.renderIntoDocument(<Typeahead
options={ BEATLES }
defaultValue={ 'o' }
/>);
var results = TestUtils.scryRenderedComponentsWithType(component, TypeaheadOption);
assert.equal(results.length, 3);
});
});
context('onKeyDown', function() {
it('should bind to key events on the input', function() {
var component = TestUtils.renderIntoDocument(<Typeahead
options={ BEATLES }
onKeyDown={ function(e) {
assert.equal(e.keyCode, 87);
}
}
/>);
var input = component.refs.entry.getDOMNode();
TestUtils.Simulate.keyDown(input, { keyCode: 87 });
});
});
context('onKeyUp', function() {
it('should bind to key events on the input', function() {
var component = TestUtils.renderIntoDocument(<Typeahead
options={ BEATLES }
onKeyUp={ function(e) {
assert.equal(e.keyCode, 87);
}
}
/>);
var input = component.refs.entry.getDOMNode();
TestUtils.Simulate.keyUp(input, { keyCode: 87 });
});
});
context('inputProps', function() {
it('should forward props to the input element', function() {
var component = TestUtils.renderIntoDocument(<Typeahead
options={ BEATLES }
inputProps={{ autoCorrect: 'off' }}
/>);
var input = component.refs.entry;
assert.equal(input.props.autoCorrect, 'off');
});
});
context('filterOption', function() {
var FN_TEST_PLANS = [
{
name: 'accepts everything',
fn: function() { return true; },
input: 'xxx',
output: 4
}, {
name: 'rejects everything',
fn: function() { return false; },
input: 'o',
output: 0
}
];
_.each(FN_TEST_PLANS, function(testplan) {
it('should filter with a custom function that ' + testplan.name, function() {
var component = TestUtils.renderIntoDocument(<Typeahead
options={ BEATLES }
filterOption={ testplan.fn }
/>);
var results = simulateTextInput(component, testplan.input);
assert.equal(results.length, testplan.output);
});
});
var STRING_TEST_PLANS = {
'o': 3,
'pa': 1,
'Grg': 1,
'Ringo': 1,
'xxx': 0
};
it('should filter using fuzzy matching on the provided field name', function() {
var component = TestUtils.renderIntoDocument(<Typeahead
options={ BEATLES_COMPLEX }
filterOption='firstName'
displayOption='firstName'
/>);
_.each(STRING_TEST_PLANS, function(expected, value) {
var results = simulateTextInput(component, value);
assert.equal(results.length, expected, 'Text input: ' + value);
}, this);
});
});
context('formInputOption', function() {
var FORM_INPUT_TEST_PLANS = [
{
name: 'uses simple options verbatim when not specified',
props: {
options: BEATLES
},
output: 'John'
}, {
name: 'defaults to the display string when not specified',
props: {
options: BEATLES_COMPLEX,
filterOption: 'firstName',
displayOption: 'nameWithTitle'
},
output: 'John Winston Ono Lennon MBE'
}, {
name: 'uses custom options when specified as a string',
props: {
options: BEATLES_COMPLEX,
filterOption: 'firstName',
displayOption: 'nameWithTitle',
formInputOption: 'lastName'
},
output: 'Lennon'
}, {
name: 'uses custom optinos when specified as a function',
props: {
options: BEATLES_COMPLEX,
filterOption: 'firstName',
displayOption: 'nameWithTitle',
formInputOption: function(o, i) { return o.firstName + ' ' + o.lastName; }
},
output: 'John Lennon'
}
];
_.each(FORM_INPUT_TEST_PLANS, function(testplan) {
it(testplan.name, function() {
var component = TestUtils.renderIntoDocument(<Typeahead
{...testplan.props}
name='beatles'
/>);
var results = simulateTextInput(component, 'john');
var node = component.refs.entry.getDOMNode();
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_DOWN });
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_RETURN });
assert.equal(component.state.selection, testplan.output);
});
});
});
});
});