this.handleKeyUp(event)}
onClick={this.handleInputClick}
value={this.state.value}
+ id={this.id}
/>
{this.state.isOpen && this.renderMenu()}
{this.props.debug && (
diff --git a/lib/__tests__/.setup.js b/lib/__tests__/.setup.js
new file mode 100644
index 00000000..8f39f132
--- /dev/null
+++ b/lib/__tests__/.setup.js
@@ -0,0 +1,6 @@
+// .setup.js
+import { jsdom } from 'jsdom';
+
+global.document = jsdom('');
+global.window = document.defaultView;
+global.navigator = global.window.navigator;
diff --git a/lib/__tests__/Autocomplete-test.js b/lib/__tests__/Autocomplete-test.js
index 84bde946..0ed62408 100644
--- a/lib/__tests__/Autocomplete-test.js
+++ b/lib/__tests__/Autocomplete-test.js
@@ -1,11 +1,248 @@
-import React from 'react/addons';
-let { TestUtils } = React.addons;
-import Autocomplete from '../Autocomplete';
+import React from 'react'
+import ReactDOM from 'react-dom'
+import TestUtils from 'react-addons-test-utils'
+import jsdom from 'mocha-jsdom';
+import chai from 'chai';
+const expect = chai.expect;
+import chaiEnzyme from 'chai-enzyme'
import { ok, equal } from 'assert';
+import { mount, shallow } from 'enzyme';
+import Autocomplete from '../Autocomplete';
+import { getStates, matchStateToTerm, sortStates, styles } from '../utils'
+
+chai.use(chaiEnzyme());
+
+function AutocompleteComponentJSX (extraProps) {
+ return (
+
item.name}
+ items={getStates()}
+ renderItem={(item, isHighlighted) => (
+ {item.name}
+ )}
+ shouldItemRender={matchStateToTerm}
+ {...extraProps}
+ />
+ )
+};
+
+describe('Autocomplete acceptance tests', () => {
+
+ var autocompleteWrapper = mount(AutocompleteComponentJSX({}));
+ var autocompleteInputWrapper = autocompleteWrapper.find('input');
+
+ it('should display autocomplete menu when input has focus', () => {
+
+ expect(autocompleteWrapper.state('isOpen')).to.be.false;
+ expect(autocompleteWrapper.instance().refs.menu).to.not.exist;
+
+ // Display autocomplete menu upon input focus
+ autocompleteInputWrapper.simulate('focus');
+
+ expect(autocompleteWrapper.state('isOpen')).to.be.true;
+ expect(autocompleteWrapper.instance().refs.menu).to.exist;
+ });
+
+ it('should show results when partial match is typed in', () => {
+
+ // Render autocomplete results upon partial input match
+ expect(autocompleteWrapper.ref('menu').children()).to.have.length(50);
+ autocompleteInputWrapper.simulate('change', { target: { value: 'Ar' } });
+ expect(autocompleteWrapper.ref('menu').children()).to.have.length(6);
+
+ });
+
+ it('should close autocomplete menu when input is blurred', () => {
+
+ autocompleteInputWrapper.simulate('blur');
+
+ expect(autocompleteWrapper.state('isOpen')).to.be.false;
+ expect(autocompleteWrapper.instance().refs.menu).to.not.exist;
+
+ });
+
+});
+
+// Event handler unit tests
+
+describe('Autocomplete kewDown->ArrowDown event handlers', () => {
+
+ var autocompleteWrapper = mount(AutocompleteComponentJSX({}));
+ var autocompleteInputWrapper = autocompleteWrapper.find('input');
+
+ it('should highlight the 1st item in the menu when none is selected', () => {
+ autocompleteWrapper.setState({'isOpen': true});
+ autocompleteWrapper.setState({'highlightedIndex': null});
+
+ autocompleteInputWrapper.simulate('keyDown', { key : "ArrowDown", keyCode: 40, which: 40 });
+
+ expect(autocompleteWrapper.state('isOpen')).to.be.true;
+ expect(autocompleteWrapper.state('highlightedIndex')).to.equal(0);
+ });
+
+ it('should highlight the "n+1" item in the menu when "n" is selected', () => {
+ autocompleteWrapper.setState({'isOpen': true});
+
+ var n = 4;
+ // Set input to be an empty value, which displays all 50 states as items in the menu
+ autocompleteInputWrapper.simulate('change', { target: { value: '' } });
+ autocompleteWrapper.setState({'highlightedIndex': n});
+
+ autocompleteInputWrapper.simulate('keyDown', { key : "ArrowDown", keyCode: 40, which: 40 });
+
+ expect(autocompleteWrapper.state('isOpen')).to.be.true;
+ expect(autocompleteWrapper.state('highlightedIndex')).to.equal(n+1);
+ });
+
+ it('should highlight the 1st item in the menu when the last is selected', () => {
+ autocompleteWrapper.setState({'isOpen': true});
+
+ // Set input to be an empty value, which displays all 50 states as items in the menu
+ autocompleteInputWrapper.simulate('change', { target: { value: '' } });
+ autocompleteWrapper.setState({'highlightedIndex': 49});
+
+ autocompleteInputWrapper.simulate('keyDown', { key : "ArrowDown", keyCode: 40, which: 40 });
+
+ expect(autocompleteWrapper.state('isOpen')).to.be.true;
+ expect(autocompleteWrapper.state('highlightedIndex')).to.equal(0);
+ });
+
+});
+
+describe('Autocomplete kewDown->ArrowUp event handlers', () => {
+
+ var autocompleteWrapper = mount(AutocompleteComponentJSX({}));
+ var autocompleteInputWrapper = autocompleteWrapper.find('input');
+
+ it('should highlight the last item in the menu when none is selected', () => {
+ autocompleteWrapper.setState({'isOpen': true});
+ autocompleteWrapper.setState({'highlightedIndex': null});
+ // Set input to be an empty value, which displays all 50 states as items in the menu
+ autocompleteInputWrapper.simulate('change', { target: { value: '' } });
+
+ autocompleteInputWrapper.simulate('keyDown', { key : 'ArrowUp', keyCode: 38, which: 38 });
-/* eslint func-names:0 */
-describe('react-autocomplete', function() {
- it('should have tests', function() {
- ok(false);
+ expect(autocompleteWrapper.state('isOpen')).to.be.true;
+ expect(autocompleteWrapper.state('highlightedIndex')).to.equal(49);
});
+
+ it('should highlight the "n-1" item in the menu when "n" is selected', () => {
+ autocompleteWrapper.setState({'isOpen': true});
+
+ var n = 4;
+ // Set input to be an empty value, which displays all 50 states as items in the menu
+ autocompleteInputWrapper.simulate('change', { target: { value: '' } });
+ autocompleteWrapper.setState({'highlightedIndex': n});
+
+ autocompleteInputWrapper.simulate('keyDown', { key : 'ArrowUp', keyCode: 38, which: 38 });
+
+ expect(autocompleteWrapper.state('isOpen')).to.be.true;
+ expect(autocompleteWrapper.state('highlightedIndex')).to.equal(n-1);
+ });
+
+ it('should highlight the last item in the menu when the 1st is selected', () => {
+ autocompleteWrapper.setState({'isOpen': true});
+
+ // Set input to be an empty value, which displays all 50 states as items in the menu
+ autocompleteInputWrapper.simulate('change', { target: { value: '' } });
+ autocompleteWrapper.setState({'highlightedIndex': 0});
+
+ autocompleteInputWrapper.simulate('keyDown', { key : 'ArrowUp', keyCode: 38, which: 38 });
+
+ expect(autocompleteWrapper.state('isOpen')).to.be.true;
+ expect(autocompleteWrapper.state('highlightedIndex')).to.equal(49);
+ });
+
+});
+
+describe('Autocomplete kewDown->Enter event handlers', () => {
+
+ var autocompleteWrapper = mount(AutocompleteComponentJSX({}));
+ var autocompleteInputWrapper = autocompleteWrapper.find('input');
+
+ it('should do nothing if the menu is closed', () => {
+ autocompleteWrapper.setState({'isOpen': false});
+ autocompleteWrapper.simulate('keyDown', { key : 'Enter', keyCode: 13, which: 13 });
+ expect(autocompleteWrapper.state('isOpen')).to.be.false;
+ });
+
+ it('should close menu if input has focus but no item has been selected and then the Enter key is hit', () => {
+ autocompleteWrapper.setState({'isOpen': true});
+ autocompleteInputWrapper.simulate('focus');
+ autocompleteInputWrapper.simulate('change', { target: { value: '' } });
+
+ // simulate keyUp of backspace, triggering autocomplete suggestion on an empty string, which should result in nothing highlighted
+ autocompleteInputWrapper.simulate('keyUp', { key : 'Backspace', keyCode: 8, which: 8 });
+ expect(autocompleteWrapper.state('highlightedIndex')).to.be.null;
+
+ autocompleteInputWrapper.simulate('keyDown', { key : 'Enter', keyCode: 13, which: 13 });
+
+ expect(autocompleteWrapper.state('value')).to.equal('');
+ expect(autocompleteWrapper.state('isOpen')).to.be.false;
+
+ });
+
+ it('should update input value from selected menu item and close the menu', () => {
+ autocompleteWrapper.setState({'isOpen': true});
+ autocompleteInputWrapper.simulate('focus');
+ autocompleteInputWrapper.simulate('change', { target: { value: 'Ar' } });
+
+ // simulate keyUp of last key, triggering autocomplete suggestion + selection of the suggestion in the menu
+ autocompleteInputWrapper.simulate('keyUp', { key : 'r', keyCode: 82, which: 82 });
+
+ // Hit enter, updating state.value with the selected Autocomplete suggestion
+ autocompleteInputWrapper.simulate('keyDown', { key : 'Enter', keyCode: 13, which: 13 });
+ expect(autocompleteWrapper.state('value')).to.equal('Arizona');
+ expect(autocompleteWrapper.state('isOpen')).to.be.false;
+
+ });
+
+});
+
+describe('Autocomplete kewDown->Escape event handlers', () => {
+
+ var autocompleteWrapper = mount(AutocompleteComponentJSX({}));
+ var autocompleteInputWrapper = autocompleteWrapper.find('input');
+
+ it('should unhighlight any selected menu item + close the menu', () => {
+ autocompleteWrapper.setState({'isOpen': true});
+ autocompleteWrapper.setState({'highlightedIndex': 0});
+
+ autocompleteInputWrapper.simulate('keyDown', { key : 'Escape', keyCode: 27, which: 27 });
+
+ expect(autocompleteWrapper.state('isOpen')).to.be.false;
+ expect(autocompleteWrapper.state('highlightedIndex')).to.be.null;
+ });
+
+});
+
+// Component method unit tests
+describe('Autocomplete#renderMenu', () => {
+
+ var autocompleteWrapper = mount(AutocompleteComponentJSX({}));
+ var autocompleteInputWrapper = autocompleteWrapper.find('input');
+
+ it('should return a ReactComponent when renderMenu() is called', () => {
+ //autocompleteInputWrapper.simulate('change', { target: { value: 'Ar' } });
+ var autocompleteMenu = autocompleteWrapper.instance().renderMenu();
+ expect(autocompleteMenu.type).to.be.equal('div');
+ expect(autocompleteMenu.ref).to.be.equal('menu');
+ expect(autocompleteMenu.props.children.length).to.be.equal(50);
+ });
+
+ it('should return a menu ReactComponent with a subset of children when partial match text has been entered', () => {
+ // Input 'Ar' should result in 6 items in the menu, populated from autocomplete.
+ autocompleteInputWrapper.simulate('change', { target: { value: 'Ar' } });
+
+ var autocompleteMenu = autocompleteWrapper.instance().renderMenu();
+ expect(autocompleteMenu.props.children.length).to.be.equal(6);
+
+ });
+
});
diff --git a/examples/utils.js b/lib/utils.js
similarity index 100%
rename from examples/utils.js
rename to lib/utils.js
diff --git a/package.json b/package.json
index 11639a5f..d6ba81d2 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,8 @@
"example": "examples"
},
"scripts": {
- "test": "echo 'lol'",
+ "test": "BABEL_ENV=test mocha --require './lib/__tests__/.setup.js' --compilers js:babel-register './lib/__tests__/Autocomplete-test.js'",
+ "coverage": "BABEL_ENV=test node_modules/.bin/babel-node node_modules/isparta/bin/isparta cover --report lcov node_modules/mocha/bin/_mocha -- --reporter dot --require './lib/__tests__/.setup.js' './lib/__tests__/Autocomplete-test.js'",
"start": "rackt server"
},
"authors": [
@@ -21,8 +22,21 @@
],
"license": "MIT",
"devDependencies": {
+ "babel-cli": "^6.5.1",
+ "babel-preset-es2015": "^6.5.0",
+ "babel-preset-react": "^6.5.0",
+ "babel-preset-stage-0": "^6.5.0",
+ "babel-register": "^6.6.5",
+ "chai": "^3.5.0",
+ "chai-enzyme": "^0.4.0",
+ "enzyme": "^2.0.0",
+ "isparta": "^4.0.0",
+ "jsdom": "^8.1.0",
+ "mocha": "~2.4.5",
+ "mocha-jsdom": "^1.1.0",
"rackt-cli": "^0.4.0",
- "react": "^0.14.0"
+ "react": "^0.14.0",
+ "react-addons-test-utils": "^0.14.7"
},
"tags": [
"react",
@@ -32,6 +46,10 @@
],
"keywords": [],
"dependencies": {
- "dom-scroll-into-view": "1.0.1"
+ "babel-preset-es2015": "^6.5.0",
+ "dom-scroll-into-view": "1.0.1",
+ "lodash": "^4.5.0",
+ "react": "^0.14.7",
+ "react-dom": "^0.14.7"
}
}