blob: d0e905e86cce5370ef1d830eb8bf229e21cb11b6 [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001// Copyright 2021 The Chromium Authors
Copybara854996b2021-09-07 19:36:02 +00002// 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
Adrià Vilanova Martínezac4a6442022-05-15 19:05:13 +020022describe.skip('ReactAutocomplete', () => {
Copybara854996b2021-09-07 19:36:02 +000023 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
Adrià Vilanova Martínezac4a6442022-05-15 19:05:13 +0200144 it('filterOptions matching prefix first', async () => {
145 const options = [`a_test`, `test`];
146
147 const {container} = render(<ReactAutocomplete
148 label="cool"
149 options={options}
150 />);
151
152 const input = container.querySelector('input');
153 assert.isNotNull(input);
154 if (!input) return;
155
156 userEvent.type(input, 'tes');
157
158 const results = document.querySelectorAll('.autocomplete-option');
159
160 fireEvent.keyDown(input, {key: 'Enter', code: 'Enter'});
161
162 assert.strictEqual(input?.value, 'test');
163 });
164
Copybara854996b2021-09-07 19:36:02 +0000165 it('onChange callback is called', async () => {
166 const onChangeStub = sinon.stub();
167
168 const {container} = render(<ReactAutocomplete
169 label="cool"
170 options={[]}
171 onChange={onChangeStub}
172 />);
173
174 const input = container.querySelector('input');
175 assert.isNotNull(input);
176 if (!input) return;
177
178 sinon.assert.notCalled(onChangeStub);
179
180 userEvent.type(input, 'foobar');
181 sinon.assert.notCalled(onChangeStub);
182
183 fireEvent.keyDown(input, {key: 'Enter', code: 'Enter'});
184 sinon.assert.calledOnce(onChangeStub);
185
186 assert.equal(onChangeStub.getCall(0).args[1], 'foobar');
187 });
188
189 it('onChange excludes fixed values', async () => {
190 const onChangeStub = sinon.stub();
191
192 const {container} = render(<ReactAutocomplete
193 label="cool"
194 options={['cute owl']}
195 multiple={true}
196 fixedValues={['immortal penguin']}
197 onChange={onChangeStub}
198 />);
199
200 const input = container.querySelector('input');
201 assert.isNotNull(input);
202 if (!input) return;
203
204 fireEvent.keyDown(input, {key: 'Backspace', code: 'Backspace'});
205 fireEvent.keyDown(input, {key: 'Enter', code: 'Enter'});
206
207 sinon.assert.calledWith(onChangeStub, sinon.match.any, []);
208 });
209
210 it('pressing space creates new chips', async () => {
211 const onChangeStub = sinon.stub();
212
213 const {container} = render(<ReactAutocomplete
214 label="cool"
215 options={['cute owl']}
216 multiple={true}
217 onChange={onChangeStub}
218 />);
219
220 const input = container.querySelector('input');
221 assert.isNotNull(input);
222 if (!input) return;
223
224 sinon.assert.notCalled(onChangeStub);
225
226 userEvent.type(input, 'foobar');
227 sinon.assert.notCalled(onChangeStub);
228
229 fireEvent.keyDown(input, {key: ' ', code: 'Space'});
230 sinon.assert.calledOnce(onChangeStub);
231
232 assert.deepEqual(onChangeStub.getCall(0).args[1], ['foobar']);
233 });
234
235 it('_renderOption shows 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, 'ow');
246
247 const options = document.querySelectorAll('.autocomplete-option');
248
249 // Options: cute@owl.com
250 assert.deepEqual(options.length, 1);
251 assert.equal(options[0].textContent, 'cute@owl.com');
252
253 cleanAutocomplete(input);
254 });
255
256 it('_renderOption hides duplicate user input', async () => {
257 const {container} = render(<ReactAutocomplete
258 label="cool"
259 options={['cute@owl.com']}
260 />);
261
262 const input = container.querySelector('input');
263 assert.isNotNull(input);
264 if (!input) return;
265
266 userEvent.type(input, 'cute@owl.com');
267
268 const options = document.querySelectorAll('.autocomplete-option');
269
270 // Options: cute@owl.com
271 assert.equal(options.length, 1);
272
273 assert.equal(options[0].textContent, 'cute@owl.com');
274
275 cleanAutocomplete(input);
276 });
277
278 it('_renderOption highlights matching text', async () => {
279 const {container} = render(<ReactAutocomplete
280 label="cool"
281 options={['cute@owl.com']}
282 />);
283
284 const input = container.querySelector('input');
285 assert.isNotNull(input);
286 if (!input) return;
287
288 userEvent.type(input, 'ow');
289
290 const option = document.querySelector('.autocomplete-option');
291 const match = option?.querySelector('strong');
292
293 assert.isNotNull(match);
294 assert.equal(match?.innerText, 'ow');
295
296 // Description is not rendered.
297 assert.equal(option?.querySelectorAll('span').length, 1);
298 assert.equal(option?.querySelectorAll('strong').length, 1);
299
300 cleanAutocomplete(input);
301 });
302
303 it('_renderOption highlights matching description', async () => {
304 const {container} = render(<ReactAutocomplete
305 label="cool"
306 getOptionDescription={() => 'penguin of-doom'}
307 options={['cute owl']}
308 />);
309
310 const input = container.querySelector('input');
311 assert.isNotNull(input);
312 if (!input) return;
313
314 userEvent.type(input, 'do');
315
316 const option = document.querySelector('.autocomplete-option');
317 const match = option?.querySelector('strong');
318
319 assert.isNotNull(match);
320 assert.equal(match?.innerText, 'do');
321
322 assert.equal(option?.querySelectorAll('span').length, 2);
323 assert.equal(option?.querySelectorAll('strong').length, 1);
324
325 cleanAutocomplete(input);
326 });
327
328 it('_renderTags disables fixedValues', async () => {
329 // TODO(crbug.com/monorail/9393): Add this test once we have a way to stub
330 // out dependent components.
331 });
332});