ReactComponent when renderMenu() is called', () => {
685 | //autocompleteInputWrapper.simulate('change', { target: { value: 'Ar' } })
686 | const autocompleteMenu = autocompleteWrapper.instance().renderMenu()
687 | expect(autocompleteMenu.type).toEqual('div')
688 | expect(typeof autocompleteMenu.ref).toEqual('function')
689 | expect(autocompleteMenu.props.children.length).toEqual(50)
690 | })
691 |
692 | it('should return a menu ReactComponent with a subset of children when partial match text has been entered', () => {
693 | // Input 'Ar' should result in 6 items in the menu, populated from autocomplete.
694 | autocompleteWrapper.setProps({ value: 'Ar' })
695 |
696 | const autocompleteMenu = autocompleteWrapper.instance().renderMenu()
697 | expect(autocompleteMenu.props.children.length).toEqual(6)
698 |
699 | })
700 |
701 | it('should allow using custom components', () => {
702 | class Menu extends React.Component {
703 | render() {
704 | return
{this.props.items}
705 | }
706 | }
707 | class Item extends React.Component {
708 | render() {
709 | return
{this.props.item.name}
710 | }
711 | }
712 | const wrapper = mount(AutocompleteComponentJSX({
713 | renderMenu(items) {
714 | return
715 | },
716 | renderItem(item) {
717 | return
718 | }
719 | }))
720 | wrapper.setState({ isOpen: true, highlightedIndex: 0 })
721 | expect(wrapper.find(Menu).length).toBe(1)
722 | expect(wrapper.find(Item).length).toBe(50)
723 | })
724 | })
725 |
726 | describe('Autocomplete isItemSelectable', () => {
727 | const autocompleteWrapper = mount(AutocompleteComponentJSX({
728 | open: true,
729 | items: getCategorizedStates(),
730 | isItemSelectable: item => !item.header
731 | }))
732 |
733 | it('should automatically highlight the first selectable item', () => {
734 | // Inputting 'ne' will cause Nevada to be the first selectable state to show up under the header 'West'
735 | // The header (index 0) is not selectable, so should not be automatically highlighted.
736 | autocompleteWrapper.setProps({ value: 'ne' })
737 | expect(autocompleteWrapper.state('highlightedIndex')).toBe(1)
738 | })
739 |
740 | it('should automatically highlight the next available item', () => {
741 | // After inputting 'new h' to get New Hampshire you have a list that looks like:
742 | // [ header, header, header, header, New Hampshire, ... ]
743 | // This test makes sure that New Hampshire is selected, at index 4.
744 | // (maybeAutocompleteText should skip over the headers correctly)
745 | autocompleteWrapper.setProps({ value: 'new h' })
746 | expect(autocompleteWrapper.state('highlightedIndex')).toBe(4)
747 | })
748 |
749 | it('should highlight nothing automatically if there are no selectable items', () => {
750 | // No selectable results should appear in the results, only headers.
751 | // As a result there should be no highlighted index.
752 | autocompleteWrapper.setProps({ value: 'new hrhrhhrr' })
753 | expect(autocompleteWrapper.state('highlightedIndex')).toBe(null)
754 | })
755 | })
756 |
757 | describe('Public imperative API', () => {
758 | it('should expose select APIs available on HTMLInputElement', () => {
759 | const tree = mount(AutocompleteComponentJSX({ value: 'foo' }))
760 | const ac = tree.get(0)
761 | expect(typeof ac.focus).toBe('function')
762 | expect(ac.isInputFocused()).toBe(false)
763 | ac.focus()
764 | expect(ac.isInputFocused()).toBe(true)
765 | expect(typeof ac.setSelectionRange).toBe('function')
766 | ac.setSelectionRange(1, 2)
767 | expect(tree.find('input').get(0).selectionStart).toBe(1)
768 | expect(tree.find('input').get(0).selectionEnd).toBe(2)
769 | expect(typeof ac.blur).toBe('function')
770 | ac.blur()
771 | expect(ac.isInputFocused()).toBe(false)
772 | })
773 | })
774 |
--------------------------------------------------------------------------------
/lib/__tests__/__snapshots__/Autocomplete-test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Autocomplete.props.renderInput should be invoked in \`render\` to create the
element 1`] = `
4 | Object {
5 | "aria-autocomplete": "list",
6 | "aria-expanded": false,
7 | "autoComplete": "off",
8 | "foo": "bar",
9 | "onBlur": [Function],
10 | "onChange": [Function],
11 | "onClick": [Function],
12 | "onFocus": [Function],
13 | "onKeyDown": [Function],
14 | "ref": [Function],
15 | "role": "combobox",
16 | "value": "pants",
17 | }
18 | `;
19 |
20 | exports[`Autocomplete.props.renderInput should be invoked in \`render\` to create the
element 2`] = `
21 |
44 | `;
45 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./Autocomplete')
2 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | export function matchStateToTerm(state, value) {
2 | return (
3 | state.name.toLowerCase().indexOf(value.toLowerCase()) !== -1 ||
4 | state.abbr.toLowerCase().indexOf(value.toLowerCase()) !== -1
5 | )
6 | }
7 |
8 | export function matchStateToTermWithHeaders(state, value) {
9 | return (
10 | state.header ||
11 | state.name.toLowerCase().indexOf(value.toLowerCase()) !== -1 ||
12 | state.abbr.toLowerCase().indexOf(value.toLowerCase()) !== -1
13 | )
14 | }
15 |
16 | /**
17 | * An example of how to implement a relevancy-based sorting method. States are
18 | * sorted based on the location of the match - For example, a search for "or"
19 | * will return "Oregon" before "North Carolina" even though "North Carolina"
20 | * would normally sort above Oregon. Strings where the match is in the same
21 | * location (or there is no match) will be sorted alphabetically - For example,
22 | * a search for "or" would return "North Carolina" above "North Dakota".
23 | */
24 | export function sortStates(a, b, value) {
25 | const aLower = a.name.toLowerCase()
26 | const bLower = b.name.toLowerCase()
27 | const valueLower = value.toLowerCase()
28 | const queryPosA = aLower.indexOf(valueLower)
29 | const queryPosB = bLower.indexOf(valueLower)
30 | if (queryPosA !== queryPosB) {
31 | return queryPosA - queryPosB
32 | }
33 | return aLower < bLower ? -1 : 1
34 | }
35 |
36 | export function fakeRequest(value, cb) {
37 | return setTimeout(cb, 500, value ?
38 | getStates().filter(state => matchStateToTerm(state, value)) :
39 | getStates()
40 | )
41 | }
42 |
43 |
44 | export function fakeCategorizedRequest(value, cb) {
45 | setTimeout(cb, 500, value ?
46 | getCategorizedStates().filter(state => matchStateToTermWithHeaders(state, value)) :
47 | getCategorizedStates()
48 | )
49 | }
50 |
51 | export function getStates() {
52 | return [
53 | { abbr: 'AL', name: 'Alabama' },
54 | { abbr: 'AK', name: 'Alaska' },
55 | { abbr: 'AZ', name: 'Arizona' },
56 | { abbr: 'AR', name: 'Arkansas' },
57 | { abbr: 'CA', name: 'California' },
58 | { abbr: 'CO', name: 'Colorado' },
59 | { abbr: 'CT', name: 'Connecticut' },
60 | { abbr: 'DE', name: 'Delaware' },
61 | { abbr: 'FL', name: 'Florida' },
62 | { abbr: 'GA', name: 'Georgia' },
63 | { abbr: 'HI', name: 'Hawaii' },
64 | { abbr: 'ID', name: 'Idaho' },
65 | { abbr: 'IL', name: 'Illinois' },
66 | { abbr: 'IN', name: 'Indiana' },
67 | { abbr: 'IA', name: 'Iowa' },
68 | { abbr: 'KS', name: 'Kansas' },
69 | { abbr: 'KY', name: 'Kentucky' },
70 | { abbr: 'LA', name: 'Louisiana' },
71 | { abbr: 'ME', name: 'Maine' },
72 | { abbr: 'MD', name: 'Maryland' },
73 | { abbr: 'MA', name: 'Massachusetts' },
74 | { abbr: 'MI', name: 'Michigan' },
75 | { abbr: 'MN', name: 'Minnesota' },
76 | { abbr: 'MS', name: 'Mississippi' },
77 | { abbr: 'MO', name: 'Missouri' },
78 | { abbr: 'MT', name: 'Montana' },
79 | { abbr: 'NE', name: 'Nebraska' },
80 | { abbr: 'NV', name: 'Nevada' },
81 | { abbr: 'NH', name: 'New Hampshire' },
82 | { abbr: 'NJ', name: 'New Jersey' },
83 | { abbr: 'NM', name: 'New Mexico' },
84 | { abbr: 'NY', name: 'New York' },
85 | { abbr: 'NC', name: 'North Carolina' },
86 | { abbr: 'ND', name: 'North Dakota' },
87 | { abbr: 'OH', name: 'Ohio' },
88 | { abbr: 'OK', name: 'Oklahoma' },
89 | { abbr: 'OR', name: 'Oregon' },
90 | { abbr: 'PA', name: 'Pennsylvania' },
91 | { abbr: 'RI', name: 'Rhode Island' },
92 | { abbr: 'SC', name: 'South Carolina' },
93 | { abbr: 'SD', name: 'South Dakota' },
94 | { abbr: 'TN', name: 'Tennessee' },
95 | { abbr: 'TX', name: 'Texas' },
96 | { abbr: 'UT', name: 'Utah' },
97 | { abbr: 'VT', name: 'Vermont' },
98 | { abbr: 'VA', name: 'Virginia' },
99 | { abbr: 'WA', name: 'Washington' },
100 | { abbr: 'WV', name: 'West Virginia' },
101 | { abbr: 'WI', name: 'Wisconsin' },
102 | { abbr: 'WY', name: 'Wyoming' }
103 | ]
104 | }
105 |
106 | export function getCategorizedStates() {
107 | return [
108 | { header: 'West' },
109 | { abbr: 'AZ', name: 'Arizona' },
110 | { abbr: 'CA', name: 'California' },
111 | { abbr: 'CO', name: 'Colorado' },
112 | { abbr: 'ID', name: 'Idaho' },
113 | { abbr: 'MT', name: 'Montana' },
114 | { abbr: 'NV', name: 'Nevada' },
115 | { abbr: 'NM', name: 'New Mexico' },
116 | { abbr: 'OK', name: 'Oklahoma' },
117 | { abbr: 'OR', name: 'Oregon' },
118 | { abbr: 'TX', name: 'Texas' },
119 | { abbr: 'UT', name: 'Utah' },
120 | { abbr: 'WA', name: 'Washington' },
121 | { abbr: 'WY', name: 'Wyoming' },
122 | { header: 'Southeast' },
123 | { abbr: 'AL', name: 'Alabama' },
124 | { abbr: 'AR', name: 'Arkansas' },
125 | { abbr: 'FL', name: 'Florida' },
126 | { abbr: 'GA', name: 'Georgia' },
127 | { abbr: 'KY', name: 'Kentucky' },
128 | { abbr: 'LA', name: 'Louisiana' },
129 | { abbr: 'MS', name: 'Mississippi' },
130 | { abbr: 'NC', name: 'North Carolina' },
131 | { abbr: 'SC', name: 'South Carolina' },
132 | { abbr: 'TN', name: 'Tennessee' },
133 | { abbr: 'VA', name: 'Virginia' },
134 | { abbr: 'WV', name: 'West Virginia' },
135 | { header: 'Midwest' },
136 | { abbr: 'IL', name: 'Illinois' },
137 | { abbr: 'IN', name: 'Indiana' },
138 | { abbr: 'IA', name: 'Iowa' },
139 | { abbr: 'KS', name: 'Kansas' },
140 | { abbr: 'MI', name: 'Michigan' },
141 | { abbr: 'MN', name: 'Minnesota' },
142 | { abbr: 'MO', name: 'Missouri' },
143 | { abbr: 'NE', name: 'Nebraska' },
144 | { abbr: 'OH', name: 'Ohio' },
145 | { abbr: 'ND', name: 'North Dakota' },
146 | { abbr: 'SD', name: 'South Dakota' },
147 | { abbr: 'WI', name: 'Wisconsin' },
148 | { header: 'Northeast' },
149 | { abbr: 'CT', name: 'Connecticut' },
150 | { abbr: 'DE', name: 'Delaware' },
151 | { abbr: 'ME', name: 'Maine' },
152 | { abbr: 'MD', name: 'Maryland' },
153 | { abbr: 'MA', name: 'Massachusetts' },
154 | { abbr: 'NH', name: 'New Hampshire' },
155 | { abbr: 'NJ', name: 'New Jersey' },
156 | { abbr: 'NY', name: 'New York' },
157 | { abbr: 'PA', name: 'Pennsylvania' },
158 | { abbr: 'RI', name: 'Rhode Island' },
159 | { abbr: 'VT', name: 'Vermont' },
160 | { header:'Pacific' },
161 | { abbr: 'AK', name: 'Alaska' },
162 | { abbr: 'HI', name: 'Hawaii' },
163 | ]
164 | }
165 |
166 |
167 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-autocomplete",
3 | "version": "1.8.1",
4 | "description": "Accessible, extensible, Autocomplete for React.js",
5 | "main": "./build/lib/Autocomplete.js",
6 | "files": [
7 | "build/lib/index.js",
8 | "build/lib/Autocomplete.js",
9 | "dist"
10 | ],
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/reactjs/react-autocomplete.git"
14 | },
15 | "homepage": "https://github.com/reactjs/react-autocomplete",
16 | "bugs": "https://github.com/reactjs/react-autocomplete/issues",
17 | "directories": {
18 | "example": "examples"
19 | },
20 | "scripts": {
21 | "generate-readme": "scripty",
22 | "gh-pages": "scripty",
23 | "release": "scripty",
24 | "build": "scripty",
25 | "build:component": "scripty",
26 | "build:examples": "scripty",
27 | "test": "scripty",
28 | "test:lint": "scripty",
29 | "test:jest": "scripty",
30 | "coverage": "scripty",
31 | "start": "SCRIPTY_PARALLEL=true scripty"
32 | },
33 | "authors": [
34 | "Ryan Florence
"
35 | ],
36 | "license": "MIT",
37 | "devDependencies": {
38 | "babel-cli": "^6.5.1",
39 | "babel-eslint": "6.1.2",
40 | "babel-jest": "^20.0.3",
41 | "babel-preset-es2015": "^6.5.0",
42 | "babel-preset-react": "^6.5.0",
43 | "babel-preset-react-hmre": "^1.1.1",
44 | "babel-preset-stage-0": "^6.5.0",
45 | "babelify": "^7.3.0",
46 | "browserify": "^13.3.0",
47 | "browserify-hmr": "^0.3.5",
48 | "enzyme": "^2.8.2",
49 | "enzyme-to-json": "^1.5.1",
50 | "eslint": "^2.13.1",
51 | "eslint-config-rackt": "^1.1.1",
52 | "eslint-plugin-react": "^6.10.3",
53 | "exorcist": "^0.4.0",
54 | "jest": "^20.0.4",
55 | "lodash.flow": "^3.5.0",
56 | "lodash.map": "^4.6.0",
57 | "lodash.sortby": "^4.7.0",
58 | "react": "^15.5.4",
59 | "react-docgen": "^2.15.0",
60 | "react-dom": "^15.5.4",
61 | "react-test-renderer": "^15.5.4",
62 | "scripty": "^1.7.1",
63 | "st": "^1.2.0",
64 | "uglify-js": "^2.7.5",
65 | "watchify": "^3.8.0",
66 | "youemdee": "^1.0.0"
67 | },
68 | "tags": [
69 | "react",
70 | "autocomplete",
71 | "combobox",
72 | "a11y"
73 | ],
74 | "keywords": [],
75 | "dependencies": {
76 | "dom-scroll-into-view": "1.0.1",
77 | "prop-types": "^15.5.10"
78 | },
79 | "peerDependencies": {
80 | "react": "^0.14.7 || ^15.0.0-0 || ^16.0.0-0",
81 | "react-dom": "^0.14.7 || ^15.0.0-0 || ^16.0.0-0"
82 | },
83 | "jest": {
84 | "snapshotSerializers": [
85 | "enzyme-to-json/serializer"
86 | ],
87 | "modulePathIgnorePatterns": [
88 | "/build/",
89 | "/dist/"
90 | ],
91 | "testPathIgnorePatterns": [
92 | "/build/",
93 | "/dist/"
94 | ]
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/scripts/build/component.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | export NODE_ENV=production
4 | mkdir -p build/lib dist
5 | babel lib/{index,Autocomplete}.js -d build/
6 | cd dist
7 | browserify ../lib/Autocomplete.js \
8 | --transform babelify \
9 | --external react \
10 | --external react-dom \
11 | --debug \
12 | | youemdee ReactAutocomplete \
13 | --dependency react:React \
14 | --dependency react-dom:ReactDOM \
15 | | exorcist react-autocomplete.js.map \
16 | > react-autocomplete.js
17 | uglifyjs react-autocomplete.js \
18 | --compress \
19 | --mangle \
20 | --in-source-map react-autocomplete.js.map \
21 | --source-map react-autocomplete.min.js.map \
22 | > react-autocomplete.min.js
23 |
--------------------------------------------------------------------------------
/scripts/build/examples.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | cd examples
4 | mkdir -p __build__
5 | for ex in async-data custom-menu managed-menu-visibility static-data; do
6 | NODE_ENV=production browserify ${ex}/app.js -t babelify > __build__/${ex}.js
7 | done
8 |
--------------------------------------------------------------------------------
/scripts/coverage.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | jest --coverage
4 |
--------------------------------------------------------------------------------
/scripts/generate-readme.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | node bin/generate-readme.js
4 |
--------------------------------------------------------------------------------
/scripts/gh-pages.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | if [ -n "$(git status --porcelain)" ]; then
4 | echo "Working directory is not clean!"
5 | exit 1
6 | fi
7 | git checkout master
8 | npm run build:examples
9 | cp -r examples/* .
10 | touch .nojekyll
11 | git add -A
12 | git commit -m 'Updating gh-pages'
13 | git push --force origin HEAD:gh-pages
14 | git reset --hard HEAD^
15 |
--------------------------------------------------------------------------------
/scripts/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | if [ -n "$(git status --porcelain)" ]; then
4 | echo "Working directory is not clean!"
5 | exit 1
6 | fi
7 | if [ -z "$1" ]; then
8 | echo "No version specified! Usage: npm run release -- [major|minor|patch|x.x.x]"
9 | exit 1
10 | fi
11 | read -n1 -p "Have you remembered to update the CHANGELOG? (y/n) " answer
12 | if [ "$answer" = 'y' -o "$answer" = 'Y' ]; then
13 | npm run generate-readme
14 | if [ -n "$(git status --porcelain)" ]; then
15 | git commit README.md -m 'Update README'
16 | fi
17 | npm version $1
18 | npm run build:component
19 | npm publish
20 | git push origin master --follow-tags
21 | else
22 | exit 1
23 | fi
24 |
--------------------------------------------------------------------------------
/scripts/start/async-data.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | mkdir -p examples/__build__
4 | watchify examples/async-data/app.js -t babelify -p [browserify-hmr --port 1337 --url http://localhost:1337] -v -o examples/__build__/async-data.js
5 |
--------------------------------------------------------------------------------
/scripts/start/custom-menu.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | mkdir -p examples/__build__
4 | watchify examples/custom-menu/app.js -t babelify -p [browserify-hmr --port 1338 --url http://localhost:1338] -v -o examples/__build__/custom-menu.js
5 |
--------------------------------------------------------------------------------
/scripts/start/managed-menu-visibility.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | mkdir -p examples/__build__
4 | watchify examples/managed-menu-visibility/app.js -t babelify -p [browserify-hmr --port 1339 --url http://localhost:1339] -v -o examples/__build__/managed-menu-visibility.js
5 |
--------------------------------------------------------------------------------
/scripts/start/server.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | st --port 8080 --dir examples --index index.html
4 |
--------------------------------------------------------------------------------
/scripts/start/static-data.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | mkdir -p examples/__build__
4 | watchify examples/static-data/app.js -t babelify -p [browserify-hmr --port 1340 --url http://localhost:1340] -v -o examples/__build__/static-data.js
5 |
--------------------------------------------------------------------------------
/scripts/test/index.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | ./scripts/test/lint.sh
4 | ./scripts/test/jest.sh
5 |
--------------------------------------------------------------------------------
/scripts/test/jest.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | jest
4 |
--------------------------------------------------------------------------------
/scripts/test/lint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | eslint lib examples --ignore-pattern=examples/__build__/**
4 |
--------------------------------------------------------------------------------