blob: 75df329f187a9383b39603979a4de1b29a2b0263 [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001// Copyright 2019 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.
4
5import sinon from 'sinon';
6import {assert} from 'chai';
7import {ChopsAutocomplete} from './chops-autocomplete.js';
8
9let element;
10let input;
11
12describe('chops-autocomplete', () => {
13 beforeEach(() => {
14 element = document.createElement('chops-autocomplete');
15 document.body.appendChild(element);
16
17 input = document.createElement('input');
18 input.id = 'autocomplete-input';
19 document.body.appendChild(input);
20
21 element.for = 'autocomplete-input';
22 });
23
24 afterEach(() => {
25 document.body.removeChild(element);
26 document.body.removeChild(input);
27 });
28
29 it('initializes', () => {
30 assert.instanceOf(element, ChopsAutocomplete);
31 });
32
33 it('registers child input', async () => {
34 await element.updateComplete;
35
36 assert.isNotNull(element._forRef);
37 assert.equal(element._forRef.tagName.toUpperCase(), 'INPUT');
38 });
39
40 it('completeValue sets input value', async () => {
41 await element.updateComplete;
42
43 element.completeValue('test');
44 assert.equal(input.value, 'test');
45
46 element.completeValue('again');
47 assert.equal(input.value, 'again');
48 });
49
50 it('completeValue can run a custom replacer', async () => {
51 element.replacer = (input, value) => input.value = value + ',';
52 await element.updateComplete;
53
54 element.completeValue('trailing');
55 assert.equal(input.value, 'trailing,');
56
57 element.completeValue('comma');
58 assert.equal(input.value, 'comma,');
59 });
60
61 it('completions render', async () => {
62 element.completions = ['hello', 'world'];
63 element.docDict = {'hello': 'well hello there'};
64 await element.updateComplete;
65
66 const completions = element.querySelectorAll('.completion');
67 const docstrings = element.querySelectorAll('.docstring');
68
69 assert.equal(completions.length, 2);
70 assert.equal(docstrings.length, 2);
71
72 assert.include(completions[0].textContent, 'hello');
73 assert.include(completions[1].textContent, 'world');
74
75 assert.include(docstrings[0].textContent, 'well hello there');
76 assert.include(docstrings[1].textContent, '');
77 });
78
79 it('completions bold matched section when rendering', async () => {
80 element.completions = ['hello-world'];
81 element._prefix = 'wor';
82 element._matchDict = {
83 'hello-world': {'index': 6},
84 };
85
86 await element.updateComplete;
87
88 const completion = element.querySelector('.completion');
89
90 assert.include(completion.textContent, 'hello-world');
91
92 assert.equal(completion.querySelector('b').textContent.trim(), 'wor');
93 });
94
95
96 it('showCompletions populates completions with matches', async () => {
97 element.strings = [
98 'test-one',
99 'test-two',
100 'ignore',
101 'hello',
102 'woah-test',
103 'i-am-a-tester',
104 ];
105 input.value = 'test';
106 await element.updateComplete;
107
108 element.showCompletions();
109
110 assert.deepEqual(element.completions, [
111 'test-one',
112 'test-two',
113 'woah-test',
114 'i-am-a-tester',
115 ]);
116 });
117
118 it('showCompletions matches docs', async () => {
119 element.strings = [
120 'hello',
121 'world',
122 'no-op',
123 ];
124 element.docDict = {'world': 'this is a test'};
125 input.value = 'test';
126 await element.updateComplete;
127
128 element.showCompletions();
129
130 assert.deepEqual(element.completions, [
131 'world',
132 ]);
133 });
134
135 it('showCompletions caps completions at max', async () => {
136 element.max = 2;
137 element.strings = [
138 'test-one',
139 'test-two',
140 'ignore',
141 'hello',
142 'woah-test',
143 'i-am-a-tester',
144 ];
145 input.value = 'test';
146 await element.updateComplete;
147
148 element.showCompletions();
149
150 assert.deepEqual(element.completions, [
151 'test-one',
152 'test-two',
153 ]);
154 });
155
156 it('hideCompletions hides completions', async () => {
157 element.completions = [
158 'test-one',
159 'test-two',
160 ];
161
162 await element.updateComplete;
163
164 const completionTable = element.querySelector('table');
165 assert.isFalse(completionTable.hidden);
166
167 element.hideCompletions();
168
169 await element.updateComplete;
170
171 assert.isTrue(completionTable.hidden);
172 });
173
174 it('clicking completion completes it', async () => {
175 element.completions = [
176 'test-one',
177 'test-two',
178 'click me!',
179 'test',
180 ];
181
182 await element.updateComplete;
183
184 const completions = element.querySelectorAll('tr');
185
186 assert.equal(input.value, '');
187
188 // Note: the click() event can only trigger click events, not mousedown
189 // events, so we are instead manually running the event handler.
190 element._clickCompletion({
191 preventDefault: sinon.stub(),
192 currentTarget: completions[2],
193 });
194
195 assert.equal(input.value, 'click me!');
196 });
197
198 it('completion is scrolled into view when outside viewport', async () => {
199 element.completions = [
200 'i',
201 'am',
202 'an option',
203 ];
204 element._selectedIndex = 0;
205 element.id = 'chops-autocomplete-1';
206
207 await element.updateComplete;
208
209 const container = element.querySelector('tbody');
210 const completion = container.querySelector('tr');
211 const completionHeight = completion.offsetHeight;
212 // Make the table one row tall.
213 container.style.height = `${completionHeight}px`;
214
215 element._selectedIndex = 1;
216 await element.updateComplete;
217
218 assert.equal(container.scrollTop, completionHeight);
219
220 element._selectedIndex = 2;
221 await element.updateComplete;
222
223 assert.equal(container.scrollTop, completionHeight * 2);
224
225 element._selectedIndex = 0;
226 await element.updateComplete;
227
228 assert.equal(container.scrollTop, 0);
229 });
230
231 it('aria-activedescendant set based on selected option', async () => {
232 element.completions = [
233 'i',
234 'am',
235 'an option',
236 ];
237 element._selectedIndex = 1;
238 element.id = 'chops-autocomplete-1';
239
240 await element.updateComplete;
241
242 assert.equal(input.getAttribute('aria-activedescendant'),
243 'chops-autocomplete-1-option-1');
244 });
245
246 it('hovering over a completion selects it', async () => {
247 element.completions = [
248 'hover',
249 'over',
250 'me',
251 ];
252
253 await element.updateComplete;
254
255 const completions = element.querySelectorAll('tr');
256
257 element._hoverCompletion({
258 currentTarget: completions[2],
259 });
260
261 assert.equal(element._selectedIndex, 2);
262
263 element._hoverCompletion({
264 currentTarget: completions[1],
265 });
266
267 assert.equal(element._selectedIndex, 1);
268 });
269
270 it('ArrowDown moves through completions', async () => {
271 element.completions = [
272 'move',
273 'down',
274 'me',
275 ];
276
277 element._selectedIndex = 0;
278
279 await element.updateComplete;
280
281 const preventDefault = sinon.stub();
282
283 element._navigateCompletions({preventDefault, key: 'ArrowDown'});
284 assert.equal(element._selectedIndex, 1);
285
286 element._navigateCompletions({preventDefault, key: 'ArrowDown'});
287 assert.equal(element._selectedIndex, 2);
288
289 // Wrap around.
290 element._navigateCompletions({preventDefault, key: 'ArrowDown'});
291 assert.equal(element._selectedIndex, 0);
292
293 sinon.assert.callCount(preventDefault, 3);
294 });
295
296 it('ArrowUp moves through completions', async () => {
297 element.completions = [
298 'move',
299 'up',
300 'me',
301 ];
302
303 element._selectedIndex = 0;
304
305 await element.updateComplete;
306
307 const preventDefault = sinon.stub();
308
309 // Wrap around.
310 element._navigateCompletions({preventDefault, key: 'ArrowUp'});
311 assert.equal(element._selectedIndex, 2);
312
313 element._navigateCompletions({preventDefault, key: 'ArrowUp'});
314 assert.equal(element._selectedIndex, 1);
315
316 element._navigateCompletions({preventDefault, key: 'ArrowUp'});
317 assert.equal(element._selectedIndex, 0);
318
319 sinon.assert.callCount(preventDefault, 3);
320 });
321
322 it('Enter completes with selected completion', async () => {
323 element.completions = [
324 'hello',
325 'pick me',
326 'world',
327 ];
328
329 element._selectedIndex = 1;
330
331 await element.updateComplete;
332
333 const preventDefault = sinon.stub();
334
335 element._navigateCompletions({preventDefault, key: 'Enter'});
336
337 assert.equal(input.value, 'pick me');
338 sinon.assert.callCount(preventDefault, 1);
339 });
340
341 it('Escape hides completions', async () => {
342 element.completions = [
343 'hide',
344 'me',
345 ];
346
347 await element.updateComplete;
348
349 const preventDefault = sinon.stub();
350 element._navigateCompletions({preventDefault, key: 'Escape'});
351
352 sinon.assert.callCount(preventDefault, 1);
353
354 await element.updateComplete;
355
356 assert.equal(element.completions.length, 0);
357 });
358});