react-typeahead
Version:
React-based typeahead and typeahead-tokenizer
486 lines (408 loc) • 18.7 kB
JavaScript
var _ = require('lodash');
var assert = require('chai').assert;
var sinon = require('sinon');
var React = require('react');
var ReactDOM = require('react-dom');
var Typeahead = require('../src/typeahead');
var TypeaheadOption = require('../src/typeahead/option');
var TypeaheadSelector = require('../src/typeahead/selector');
var Tokenizer = require('../src/tokenizer');
var Token = require('../src/tokenizer/token');
var Keyevent = require('../src/keyevent');
var TestUtils = require('react-addons-test-utils');
function simulateTextInput(component, value) {
var node = component.refs.entry;
node.value = value;
TestUtils.Simulate.change(node);
return TestUtils.scryRenderedComponentsWithType(component, TypeaheadOption);
}
function simulateTokenInput(component, value) {
var typeahead = component.refs.typeahead;
return simulateTextInput(typeahead, value);
}
function getTokens(component) {
return TestUtils.scryRenderedComponentsWithType(component, Token);
}
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('TypeaheadTokenizer Component', function() {
describe('basic tokenizer', function() {
beforeEach(function() {
this.component = TestUtils.renderIntoDocument(
<Tokenizer
options={BEATLES}
customClasses={{
token: 'custom-token'
}}
/>
);
});
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 = simulateTokenInput(this.component, value);
assert.equal(results.length, expected, 'Text input: ' + value);
}, this);
});
it('should have custom and default token classes', function() {
simulateTokenInput(this.component, 'o');
var entry = this.component.refs.typeahead.refs.entry;
TestUtils.Simulate.keyDown(entry, { keyCode: Keyevent.DOM_VK_DOWN });
TestUtils.Simulate.keyDown(entry, { keyCode: Keyevent.DOM_VK_RETURN });
var tokens = getTokens(this.component);
assert.equal(tokens.length, 1);
assert.isDefined(tokens[0]);
TestUtils.findRenderedDOMComponentWithClass(tokens[0], 'typeahead-token');
TestUtils.findRenderedDOMComponentWithClass(tokens[0], 'custom-token');
});
context('onKeyDown', function() {
it('should bind to key events on the input', function(done) {
var component = TestUtils.renderIntoDocument(<Tokenizer
options={ BEATLES }
onKeyDown={ function(e) {
assert.equal(e.keyCode, 87);
done();
}
}
/>);
var input = ReactDOM.findDOMNode(component.refs.typeahead.refs.entry);
TestUtils.Simulate.keyDown(input, { keyCode: 87 });
});
});
context('onKeyPress', function() {
it('should bind to key events on the input', function(done) {
var component = TestUtils.renderIntoDocument(<Tokenizer
options={ BEATLES }
onKeyPress={ function(e) {
assert.equal(e.keyCode, 87);
done();
}
}
/>);
var input = ReactDOM.findDOMNode(component.refs.typeahead.refs.entry);
TestUtils.Simulate.keyPress(input, { keyCode: 87 });
});
});
context('onKeyUp', function() {
it('should bind to key events on the input', function(done) {
var component = TestUtils.renderIntoDocument(<Tokenizer
options={ BEATLES }
onKeyUp={ function(e) {
assert.equal(e.keyCode, 87);
done();
}
}
/>);
var input = ReactDOM.findDOMNode(component.refs.typeahead.refs.entry);
TestUtils.Simulate.keyUp(input, { keyCode: 87 });
});
});
describe('props', function(){
context('displayOption', function() {
it('renders simple options verbatim when not specified', function() {
var component = TestUtils.renderIntoDocument(<Tokenizer
options={ BEATLES }
/>);
var results = simulateTokenInput(component, 'john');
assert.equal(ReactDOM.findDOMNode(results[0]).textContent, 'John');
});
it('renders custom options when specified as a string', function() {
var component = TestUtils.renderIntoDocument(<Tokenizer
options={ BEATLES_COMPLEX }
filterOption='firstName'
displayOption='nameWithTitle'
/>);
var results = simulateTokenInput(component, 'john');
assert.equal(ReactDOM.findDOMNode(results[0]).textContent, 'John Winston Ono Lennon MBE');
});
it('renders custom options when specified as a function', function() {
var component = TestUtils.renderIntoDocument(<Tokenizer
options={ BEATLES_COMPLEX }
filterOption='firstName'
displayOption={ function(o, i) { return i + ' ' + o.firstName + ' ' + o.lastName; } }
/>);
var results = simulateTokenInput(component, 'john');
assert.equal(ReactDOM.findDOMNode(results[0]).textContent, '0 John Lennon');
});
});
context('formInputOption', function() {
it('uses displayOption for the custom option value by default', function() {
var component = TestUtils.renderIntoDocument(<Tokenizer
options={ BEATLES_COMPLEX }
filterOption='firstName'
displayOption='nameWithTitle'
/>);
var results = simulateTokenInput(component, 'john');
var entry = component.refs.typeahead.refs.entry;
TestUtils.Simulate.keyDown(entry, { keyCode: Keyevent.DOM_VK_DOWN });
TestUtils.Simulate.keyDown(entry, { keyCode: Keyevent.DOM_VK_RETURN });
var tokens = getTokens(component);
assert.equal(tokens.length, 1);
assert.isDefined(tokens[0]);
assert.equal(tokens[0].props.value, 'John Winston Ono Lennon MBE');
});
it('renders custom option value when specified as a string', function() {
var component = TestUtils.renderIntoDocument(<Tokenizer
options={ BEATLES_COMPLEX }
filterOption='firstName'
displayOption='nameWithTitle'
formInputOption='lastName'
/>);
var results = simulateTokenInput(component, 'john');
var entry = component.refs.typeahead.refs.entry;
TestUtils.Simulate.keyDown(entry, { keyCode: Keyevent.DOM_VK_DOWN });
TestUtils.Simulate.keyDown(entry, { keyCode: Keyevent.DOM_VK_RETURN });
var tokens = getTokens(component);
assert.equal(tokens.length, 1);
assert.isDefined(tokens[0]);
assert.equal(tokens[0].props.value, 'Lennon');
});
it('renders custom option value when specified as a function', function() {
var component = TestUtils.renderIntoDocument(<Tokenizer
options={ BEATLES_COMPLEX }
filterOption='firstName'
displayOption='nameWithTitle'
formInputOption={ function(o, i) { return o.firstName + ' ' + o.lastName; } }
/>);
results = simulateTokenInput(component, 'john');
var entry = component.refs.typeahead.refs.entry;
TestUtils.Simulate.keyDown(entry, { keyCode: Keyevent.DOM_VK_DOWN });
TestUtils.Simulate.keyDown(entry, { keyCode: Keyevent.DOM_VK_RETURN });
var tokens = getTokens(component);
assert.equal(tokens.length, 1);
assert.isDefined(tokens[0]);
assert.equal(tokens[0].props.value, 'John Lennon');
});
});
});
describe('component functions', function() {
beforeEach(function() {
this.sinon = sinon.sandbox.create();
});
afterEach(function() {
this.sinon.restore();
});
it('focuses the typeahead', function() {
this.sinon.spy(this.component.refs.typeahead, 'focus');
this.component.focus();
assert.equal(this.component.refs.typeahead.focus.calledOnce, true);
});
});
it('should provide an exposed component function to get the selected tokens', function() {
simulateTokenInput(this.component, 'o');
var entry = this.component.refs.typeahead.refs.entry;
TestUtils.Simulate.keyDown(entry, { keyCode: Keyevent.DOM_VK_DOWN });
TestUtils.Simulate.keyDown(entry, { keyCode: Keyevent.DOM_VK_RETURN });
assert.equal(this.component.getSelectedTokens().length, 1);
assert.equal(this.component.getSelectedTokens()[0], "John");
});
describe('keyboard controls', function() {
it('down arrow + return creates a token', function() {
var results = simulateTokenInput(this.component, 'o');
var secondItem = ReactDOM.findDOMNode(results[1]).innerText;
var node = this.component.refs.typeahead.refs.entry;
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 });
var Tokens = getTokens(this.component);
assert.equal(Tokens[0].props.children, secondItem); // Poor Ringo
});
it('up arrow + return navigates and creates a token', function() {
var results = simulateTokenInput(this.component, 'o');
var firstItem = ReactDOM.findDOMNode(results[0]).innerText;
var node = this.component.refs.typeahead.refs.entry;
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 });
var Tokens = getTokens(this.component);
assert.equal(Tokens[0].props.children, firstItem);
});
it('should remove a token when BKSPC is pressed on an empty input', function() {
// Select two items
simulateTokenInput(this.component, 'o');
var entry = this.component.refs.typeahead.refs.entry;
TestUtils.Simulate.keyDown(entry, { keyCode: Keyevent.DOM_VK_DOWN });
TestUtils.Simulate.keyDown(entry, { keyCode: Keyevent.DOM_VK_RETURN });
simulateTokenInput(this.component, 'o');
TestUtils.Simulate.keyDown(entry, { keyCode: Keyevent.DOM_VK_DOWN });
TestUtils.Simulate.keyDown(entry, { keyCode: Keyevent.DOM_VK_DOWN });
TestUtils.Simulate.keyDown(entry, { keyCode: Keyevent.DOM_VK_RETURN });
// re-set the typeahead entry
var results = getTokens(this.component);
var startLength = results.length;
assert.equal(entry.value, "");
assert.equal(startLength, 2);
assert.equal(startLength, results.length);
// Now press backspace with the empty entry
TestUtils.Simulate.keyDown(entry, { keyCode: Keyevent.DOM_VK_BACK_SPACE });
results = getTokens(this.component);
assert.equal(results.length + 1, startLength);
});
it('should not remove a token on BKSPC when input is not empty', function() {
var input = this.component.refs.typeahead.refs.entry;
var startLength = getTokens(this.component).length;
input.value = "hello";
TestUtils.Simulate.change(input);
TestUtils.Simulate.keyDown(input, { keyCode: Keyevent.DOM_VK_BACK_SPACE });
results = getTokens(this.component);
assert.equal(startLength , results.length);
});
it('tab to choose first item', function() {
var results = simulateTokenInput(this.component, 'o');
var itemText = ReactDOM.findDOMNode(results[0]).innerText;
var node = this.component.refs.typeahead.refs.entry;
var tokens = getTokens(this.component);
// Need to check Token list for props.children
TestUtils.Simulate.keyDown(node, { keyCode: Keyevent.DOM_VK_TAB });
var newTokens = getTokens(this.component)
assert.equal(tokens.length, newTokens.length - 1);
assert.equal(newTokens[newTokens.length - 1].props.children, itemText);
});
it('tab to selected current item', function() {
var results = simulateTokenInput(this.component, 'o');
var itemText = ReactDOM.findDOMNode(results[1]).innerText;
var node = this.component.refs.typeahead.refs.entry;
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 });
var tokens = getTokens(this.component);
assert.equal(tokens[tokens.length - 1].props.children, itemText);
});
});
});
describe('AllowCustomValues property test', function() {
var tokenLength = 4;
beforeEach(function() {
this.sinon = sinon.sandbox.create();
this.tokenAdd = this.sinon.spy();
this.tokenRemove = this.sinon.spy();
this.component = TestUtils.renderIntoDocument(
<Tokenizer
options={BEATLES}
onTokenAdd={this.tokenAdd}
onTokenRemove={this.tokenRemove}
allowCustomValues={tokenLength}
customClasses={{
customAdd: 'topcoat-custom__token'
}}
/>
);
});
afterEach(function() {
this.sinon.restore();
})
it('should not allow custom tokens that are less than specified allowCustomValues length', function() {
var tokens = getTokens(this.component);
var results = simulateTokenInput(this.component, "abz");
assert.equal(0, results.length);
});
it('should display custom tokens when equal or exceeds allowCustomValues value', function() {
var results = simulateTokenInput(this.component, "abzz");
assert.equal(1, results.length);
assert.equal("abzz", results[0].props.children);
results = simulateTokenInput(this.component, "bakercharlie")
assert.equal(1, results.length);
assert.equal("bakercharlie", results[0].props.customValue);
})
it('should not add custom class to non-custom selection', function() {
var results = simulateTokenInput(this.component, "o");
assert.equal(3, results.length);
assert(!ReactDOM.findDOMNode(results[0]).getAttribute('class').match(new RegExp(this.component.props.customClasses.customAdd)));
})
it('should add custom class to custom selection', function() {
var results = simulateTokenInput(this.component, "abzz");
assert(1, results.length)
assert(ReactDOM.findDOMNode(results[0]).getAttribute('class').match(new RegExp(this.component.props.customClasses.customAdd)));
})
it('should allow selection of custom token', function() {
var results = simulateTokenInput(this.component, "abzz");
var input = this.component.refs.typeahead.refs.entry;
var tokens = getTokens(this.component);
TestUtils.Simulate.keyDown(input, {keyCode: Keyevent.DOM_VK_DOWN})
TestUtils.Simulate.keyDown(input, {keyCode: Keyevent.DOM_VK_RETURN})
tokens = getTokens(this.component)
assert(tokens.length == 1)
assert.equal("abzz", tokens[0].props.children);
})
it('should call onTokenAdd for custom token', function() {
var results = simulateTokenInput(this.component, "abzz");
var input = this.component.refs.typeahead.refs.entry;
var tokens = getTokens(this.component);
TestUtils.Simulate.keyDown(input, {keyCode: Keyevent.DOM_VK_DOWN})
TestUtils.Simulate.keyDown(input, {keyCode: Keyevent.DOM_VK_RETURN})
assert(this.tokenAdd.called);
assert(this.tokenAdd.calledWith( "abzz" ))
})
it('should call onTokenRemove for custom token', function() {
var results = simulateTokenInput(this.component, "abzz");
var input = this.component.refs.typeahead.refs.entry;
var tokens = getTokens(this.component);
TestUtils.Simulate.keyDown(input, {keyCode: Keyevent.DOM_VK_DOWN})
TestUtils.Simulate.keyDown(input, {keyCode: Keyevent.DOM_VK_RETURN})
assert(this.tokenAdd.called);
assert(this.tokenAdd.calledWith( "abzz" ))
tokens = getTokens(this.component);
var tokenClose = TestUtils.scryRenderedDOMComponentsWithTag(tokens[0], "a")[0];
TestUtils.Simulate.click(tokenClose);
assert(this.tokenRemove.called);
assert(this.tokenRemove.calledWith("abzz"));
})
it('should not return undefined for a custom token when not selected', function() {
var results = simulateTokenInput(this.component, "abzz");
var input = this.component.refs.typeahead.refs.entry;
var tokens = getTokens(this.component);
TestUtils.Simulate.keyDown(input, {keyCode: Keyevent.DOM_VK_TAB})
var newTokens = getTokens(this.component)
// behavior is custom token is selected
assert(tokens.length < newTokens.length);
assert(input.value == "");
assert.equal(newTokens[0].props.children, "abzz");
})
it('should not select value for a custom token when too short', function() {
var results = simulateTokenInput(this.component, "abz");
var input = this.component.refs.typeahead.refs.entry;
var tokens = getTokens(this.component);
TestUtils.Simulate.keyDown(input, {keyCode: Keyevent.DOM_VK_TAB})
var newTokens = getTokens(this.component)
// behavior is custom token is selected
assert(newTokens.length == 0);
assert(input.value == "abz");
})
})
describe('defaultClassNames', function() {
it('should remove default classNames when this prop is specified and false', function() {
var component = TestUtils.renderIntoDocument(<Tokenizer
options={ BEATLES }
defaultClassNames={false}
/>);
assert.notOk(ReactDOM.findDOMNode(component).classList.contains("tokenizer-typeahead"));
assert.equal(false, component.refs.typeahead.props.defaultClassNames);
});
});
});