blob: a1e7c625ba978c134b9bf5fdc772d20ab5bf6911 [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001// Copyright 2021 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4import {assert} from 'chai';
5import React from 'react';
6import sinon from 'sinon';
7import {fireEvent, render} from '@testing-library/react';
8import userEvent from '@testing-library/user-event';
9
10import {ReactAutocomplete, MAX_AUTOCOMPLETE_OPTIONS}
11 from './ReactAutocomplete.tsx';
12
13/**
14 * Cleans autocomplete dropdown from the DOM for the next test.
15 * @param input The autocomplete element to remove the dropdown for.
16 */
17 const cleanAutocomplete = (input: ReactAutocomplete) => {
18 fireEvent.change(input, {target: {value: ''}});
19 fireEvent.keyDown(input, {key: 'Enter', code: 'Enter'});
20};
21
22xdescribe('ReactAutocomplete', () => {
23 it('renders', async () => {
24 const {container} = render(<ReactAutocomplete label="cool" options={[]} />);
25
26 assert.isNotNull(container.querySelector('input'));
27 });
28
29 it('placeholder renders', async () => {
30 const {container} = render(<ReactAutocomplete
31 placeholder="penguins"
32 options={['']}
33 />);
34
35 const input = container.querySelector('input');
36 assert.isNotNull(input);
37 if (!input) return;
38
39 assert.strictEqual(input?.placeholder, 'penguins');
40 });
41
42 it('filterOptions empty input value', async () => {
43 const {container} = render(<ReactAutocomplete
44 label="cool"
45 options={['option 1 label']}
46 />);
47
48 const input = container.querySelector('input');
49 assert.isNotNull(input);
50 if (!input) return;
51
52 assert.strictEqual(input?.value, '');
53
54 fireEvent.keyDown(input, {key: 'Enter', code: 'Enter'});
55 assert.strictEqual(input?.value, '');
56 });
57
58 it('filterOptions truncates values', async () => {
59 const options = [];
60
61 // a0@test.com, a1@test.com, a2@test.com, ...
62 for (let i = 0; i <= MAX_AUTOCOMPLETE_OPTIONS; i++) {
63 options.push(`a${i}@test.com`);
64 }
65
66 const {container} = render(<ReactAutocomplete
67 label="cool"
68 options={options}
69 />);
70
71 const input = container.querySelector('input');
72 assert.isNotNull(input);
73 if (!input) return;
74
75 userEvent.type(input, 'a');
76
77 const results = document.querySelectorAll('.autocomplete-option');
78
79 assert.equal(results.length, MAX_AUTOCOMPLETE_OPTIONS);
80
81 // Clean up autocomplete dropdown from the DOM for the next test.
82 cleanAutocomplete(input);
83 });
84
85 it('filterOptions label matching', async () => {
86 const {container} = render(<ReactAutocomplete
87 label="cool"
88 options={['option 1 label']}
89 />);
90
91 const input = container.querySelector('input');
92 assert.isNotNull(input);
93 if (!input) return;
94
95 assert.strictEqual(input?.value, '');
96
97 userEvent.type(input, 'lab');
98 assert.strictEqual(input?.value, 'lab');
99
100 fireEvent.keyDown(input, {key: 'Enter', code: 'Enter'});
101
102 assert.strictEqual(input?.value, 'option 1 label');
103 });
104
105 it('filterOptions description matching', async () => {
106 const {container} = render(<ReactAutocomplete
107 label="cool"
108 getOptionDescription={() => 'penguin apples'}
109 options={['lol']}
110 />);
111
112 const input = container.querySelector('input');
113 assert.isNotNull(input);
114 if (!input) return;
115
116 assert.strictEqual(input?.value, '');
117
118 userEvent.type(input, 'app');
119 assert.strictEqual(input?.value, 'app');
120
121 fireEvent.keyDown(input, {key: 'Enter', code: 'Enter'});
122 assert.strictEqual(input?.value, 'lol');
123 });
124
125 it('filterOptions no match', async () => {
126 const {container} = render(<ReactAutocomplete
127 label="cool"
128 options={[]}
129 />);
130
131 const input = container.querySelector('input');
132 assert.isNotNull(input);
133 if (!input) return;
134
135 assert.strictEqual(input?.value, '');
136
137 userEvent.type(input, 'foobar');
138 assert.strictEqual(input?.value, 'foobar');
139
140 fireEvent.keyDown(input, {key: 'Enter', code: 'Enter'});
141 assert.strictEqual(input?.value, 'foobar');
142 });
143
144 it('onChange callback is called', async () => {
145 const onChangeStub = sinon.stub();
146
147 const {container} = render(<ReactAutocomplete
148 label="cool"
149 options={[]}
150 onChange={onChangeStub}
151 />);
152
153 const input = container.querySelector('input');
154 assert.isNotNull(input);
155 if (!input) return;
156
157 sinon.assert.notCalled(onChangeStub);
158
159 userEvent.type(input, 'foobar');
160 sinon.assert.notCalled(onChangeStub);
161
162 fireEvent.keyDown(input, {key: 'Enter', code: 'Enter'});
163 sinon.assert.calledOnce(onChangeStub);
164
165 assert.equal(onChangeStub.getCall(0).args[1], 'foobar');
166 });
167
168 it('onChange excludes fixed values', async () => {
169 const onChangeStub = sinon.stub();
170
171 const {container} = render(<ReactAutocomplete
172 label="cool"
173 options={['cute owl']}
174 multiple={true}
175 fixedValues={['immortal penguin']}
176 onChange={onChangeStub}
177 />);
178
179 const input = container.querySelector('input');
180 assert.isNotNull(input);
181 if (!input) return;
182
183 fireEvent.keyDown(input, {key: 'Backspace', code: 'Backspace'});
184 fireEvent.keyDown(input, {key: 'Enter', code: 'Enter'});
185
186 sinon.assert.calledWith(onChangeStub, sinon.match.any, []);
187 });
188
189 it('pressing space creates new chips', async () => {
190 const onChangeStub = sinon.stub();
191
192 const {container} = render(<ReactAutocomplete
193 label="cool"
194 options={['cute owl']}
195 multiple={true}
196 onChange={onChangeStub}
197 />);
198
199 const input = container.querySelector('input');
200 assert.isNotNull(input);
201 if (!input) return;
202
203 sinon.assert.notCalled(onChangeStub);
204
205 userEvent.type(input, 'foobar');
206 sinon.assert.notCalled(onChangeStub);
207
208 fireEvent.keyDown(input, {key: ' ', code: 'Space'});
209 sinon.assert.calledOnce(onChangeStub);
210
211 assert.deepEqual(onChangeStub.getCall(0).args[1], ['foobar']);
212 });
213
214 it('_renderOption shows user input', async () => {
215 const {container} = render(<ReactAutocomplete
216 label="cool"
217 options={['cute@owl.com']}
218 />);
219
220 const input = container.querySelector('input');
221 assert.isNotNull(input);
222 if (!input) return;
223
224 userEvent.type(input, 'ow');
225
226 const options = document.querySelectorAll('.autocomplete-option');
227
228 // Options: cute@owl.com
229 assert.deepEqual(options.length, 1);
230 assert.equal(options[0].textContent, 'cute@owl.com');
231
232 cleanAutocomplete(input);
233 });
234
235 it('_renderOption hides duplicate user input', async () => {
236 const {container} = render(<ReactAutocomplete
237 label="cool"
238 options={['cute@owl.com']}
239 />);
240
241 const input = container.querySelector('input');
242 assert.isNotNull(input);
243 if (!input) return;
244
245 userEvent.type(input, 'cute@owl.com');
246
247 const options = document.querySelectorAll('.autocomplete-option');
248
249 // Options: cute@owl.com
250 assert.equal(options.length, 1);
251
252 assert.equal(options[0].textContent, 'cute@owl.com');
253
254 cleanAutocomplete(input);
255 });
256
257 it('_renderOption highlights matching text', async () => {
258 const {container} = render(<ReactAutocomplete
259 label="cool"
260 options={['cute@owl.com']}
261 />);
262
263 const input = container.querySelector('input');
264 assert.isNotNull(input);
265 if (!input) return;
266
267 userEvent.type(input, 'ow');
268
269 const option = document.querySelector('.autocomplete-option');
270 const match = option?.querySelector('strong');
271
272 assert.isNotNull(match);
273 assert.equal(match?.innerText, 'ow');
274
275 // Description is not rendered.
276 assert.equal(option?.querySelectorAll('span').length, 1);
277 assert.equal(option?.querySelectorAll('strong').length, 1);
278
279 cleanAutocomplete(input);
280 });
281
282 it('_renderOption highlights matching description', async () => {
283 const {container} = render(<ReactAutocomplete
284 label="cool"
285 getOptionDescription={() => 'penguin of-doom'}
286 options={['cute owl']}
287 />);
288
289 const input = container.querySelector('input');
290 assert.isNotNull(input);
291 if (!input) return;
292
293 userEvent.type(input, 'do');
294
295 const option = document.querySelector('.autocomplete-option');
296 const match = option?.querySelector('strong');
297
298 assert.isNotNull(match);
299 assert.equal(match?.innerText, 'do');
300
301 assert.equal(option?.querySelectorAll('span').length, 2);
302 assert.equal(option?.querySelectorAll('strong').length, 1);
303
304 cleanAutocomplete(input);
305 });
306
307 it('_renderTags disables fixedValues', async () => {
308 // TODO(crbug.com/monorail/9393): Add this test once we have a way to stub
309 // out dependent components.
310 });
311});