UNPKG

react-typeahead

Version:

React-based typeahead and typeahead-tokenizer

486 lines (408 loc) 18.7 kB
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); }); }); });