| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import sinon from 'sinon'; |
| import {assert} from 'chai'; |
| import {ChopsAutocomplete} from './chops-autocomplete.js'; |
| |
| let element; |
| let input; |
| |
| describe('chops-autocomplete', () => { |
| beforeEach(() => { |
| element = document.createElement('chops-autocomplete'); |
| document.body.appendChild(element); |
| |
| input = document.createElement('input'); |
| input.id = 'autocomplete-input'; |
| document.body.appendChild(input); |
| |
| element.for = 'autocomplete-input'; |
| }); |
| |
| afterEach(() => { |
| document.body.removeChild(element); |
| document.body.removeChild(input); |
| }); |
| |
| it('initializes', () => { |
| assert.instanceOf(element, ChopsAutocomplete); |
| }); |
| |
| it('registers child input', async () => { |
| await element.updateComplete; |
| |
| assert.isNotNull(element._forRef); |
| assert.equal(element._forRef.tagName.toUpperCase(), 'INPUT'); |
| }); |
| |
| it('completeValue sets input value', async () => { |
| await element.updateComplete; |
| |
| element.completeValue('test'); |
| assert.equal(input.value, 'test'); |
| |
| element.completeValue('again'); |
| assert.equal(input.value, 'again'); |
| }); |
| |
| it('completeValue can run a custom replacer', async () => { |
| element.replacer = (input, value) => input.value = value + ','; |
| await element.updateComplete; |
| |
| element.completeValue('trailing'); |
| assert.equal(input.value, 'trailing,'); |
| |
| element.completeValue('comma'); |
| assert.equal(input.value, 'comma,'); |
| }); |
| |
| it('completions render', async () => { |
| element.completions = ['hello', 'world']; |
| element.docDict = {'hello': 'well hello there'}; |
| await element.updateComplete; |
| |
| const completions = element.querySelectorAll('.completion'); |
| const docstrings = element.querySelectorAll('.docstring'); |
| |
| assert.equal(completions.length, 2); |
| assert.equal(docstrings.length, 2); |
| |
| assert.include(completions[0].textContent, 'hello'); |
| assert.include(completions[1].textContent, 'world'); |
| |
| assert.include(docstrings[0].textContent, 'well hello there'); |
| assert.include(docstrings[1].textContent, ''); |
| }); |
| |
| it('completions bold matched section when rendering', async () => { |
| element.completions = ['hello-world']; |
| element._prefix = 'wor'; |
| element._matchDict = { |
| 'hello-world': {'index': 6}, |
| }; |
| |
| await element.updateComplete; |
| |
| const completion = element.querySelector('.completion'); |
| |
| assert.include(completion.textContent, 'hello-world'); |
| |
| assert.equal(completion.querySelector('b').textContent.trim(), 'wor'); |
| }); |
| |
| |
| it('showCompletions populates completions with matches', async () => { |
| element.strings = [ |
| 'test-one', |
| 'test-two', |
| 'ignore', |
| 'hello', |
| 'woah-test', |
| 'i-am-a-tester', |
| ]; |
| input.value = 'test'; |
| await element.updateComplete; |
| |
| element.showCompletions(); |
| |
| assert.deepEqual(element.completions, [ |
| 'test-one', |
| 'test-two', |
| 'woah-test', |
| 'i-am-a-tester', |
| ]); |
| }); |
| |
| it('showCompletions matches docs', async () => { |
| element.strings = [ |
| 'hello', |
| 'world', |
| 'no-op', |
| ]; |
| element.docDict = {'world': 'this is a test'}; |
| input.value = 'test'; |
| await element.updateComplete; |
| |
| element.showCompletions(); |
| |
| assert.deepEqual(element.completions, [ |
| 'world', |
| ]); |
| }); |
| |
| it('showCompletions caps completions at max', async () => { |
| element.max = 2; |
| element.strings = [ |
| 'test-one', |
| 'test-two', |
| 'ignore', |
| 'hello', |
| 'woah-test', |
| 'i-am-a-tester', |
| ]; |
| input.value = 'test'; |
| await element.updateComplete; |
| |
| element.showCompletions(); |
| |
| assert.deepEqual(element.completions, [ |
| 'test-one', |
| 'test-two', |
| ]); |
| }); |
| |
| it('hideCompletions hides completions', async () => { |
| element.completions = [ |
| 'test-one', |
| 'test-two', |
| ]; |
| |
| await element.updateComplete; |
| |
| const completionTable = element.querySelector('table'); |
| assert.isFalse(completionTable.hidden); |
| |
| element.hideCompletions(); |
| |
| await element.updateComplete; |
| |
| assert.isTrue(completionTable.hidden); |
| }); |
| |
| it('clicking completion completes it', async () => { |
| element.completions = [ |
| 'test-one', |
| 'test-two', |
| 'click me!', |
| 'test', |
| ]; |
| |
| await element.updateComplete; |
| |
| const completions = element.querySelectorAll('tr'); |
| |
| assert.equal(input.value, ''); |
| |
| // Note: the click() event can only trigger click events, not mousedown |
| // events, so we are instead manually running the event handler. |
| element._clickCompletion({ |
| preventDefault: sinon.stub(), |
| currentTarget: completions[2], |
| }); |
| |
| assert.equal(input.value, 'click me!'); |
| }); |
| |
| it('completion is scrolled into view when outside viewport', async () => { |
| element.completions = [ |
| 'i', |
| 'am', |
| 'an option', |
| ]; |
| element._selectedIndex = 0; |
| element.id = 'chops-autocomplete-1'; |
| |
| await element.updateComplete; |
| |
| const container = element.querySelector('tbody'); |
| const completion = container.querySelector('tr'); |
| const completionHeight = completion.offsetHeight; |
| // Make the table one row tall. |
| container.style.height = `${completionHeight}px`; |
| |
| element._selectedIndex = 1; |
| await element.updateComplete; |
| |
| assert.equal(container.scrollTop, completionHeight); |
| |
| element._selectedIndex = 2; |
| await element.updateComplete; |
| |
| assert.equal(container.scrollTop, completionHeight * 2); |
| |
| element._selectedIndex = 0; |
| await element.updateComplete; |
| |
| assert.equal(container.scrollTop, 0); |
| }); |
| |
| it('aria-activedescendant set based on selected option', async () => { |
| element.completions = [ |
| 'i', |
| 'am', |
| 'an option', |
| ]; |
| element._selectedIndex = 1; |
| element.id = 'chops-autocomplete-1'; |
| |
| await element.updateComplete; |
| |
| assert.equal(input.getAttribute('aria-activedescendant'), |
| 'chops-autocomplete-1-option-1'); |
| }); |
| |
| it('hovering over a completion selects it', async () => { |
| element.completions = [ |
| 'hover', |
| 'over', |
| 'me', |
| ]; |
| |
| await element.updateComplete; |
| |
| const completions = element.querySelectorAll('tr'); |
| |
| element._hoverCompletion({ |
| currentTarget: completions[2], |
| }); |
| |
| assert.equal(element._selectedIndex, 2); |
| |
| element._hoverCompletion({ |
| currentTarget: completions[1], |
| }); |
| |
| assert.equal(element._selectedIndex, 1); |
| }); |
| |
| it('ArrowDown moves through completions', async () => { |
| element.completions = [ |
| 'move', |
| 'down', |
| 'me', |
| ]; |
| |
| element._selectedIndex = 0; |
| |
| await element.updateComplete; |
| |
| const preventDefault = sinon.stub(); |
| |
| element._navigateCompletions({preventDefault, key: 'ArrowDown'}); |
| assert.equal(element._selectedIndex, 1); |
| |
| element._navigateCompletions({preventDefault, key: 'ArrowDown'}); |
| assert.equal(element._selectedIndex, 2); |
| |
| // Wrap around. |
| element._navigateCompletions({preventDefault, key: 'ArrowDown'}); |
| assert.equal(element._selectedIndex, 0); |
| |
| sinon.assert.callCount(preventDefault, 3); |
| }); |
| |
| it('ArrowUp moves through completions', async () => { |
| element.completions = [ |
| 'move', |
| 'up', |
| 'me', |
| ]; |
| |
| element._selectedIndex = 0; |
| |
| await element.updateComplete; |
| |
| const preventDefault = sinon.stub(); |
| |
| // Wrap around. |
| element._navigateCompletions({preventDefault, key: 'ArrowUp'}); |
| assert.equal(element._selectedIndex, 2); |
| |
| element._navigateCompletions({preventDefault, key: 'ArrowUp'}); |
| assert.equal(element._selectedIndex, 1); |
| |
| element._navigateCompletions({preventDefault, key: 'ArrowUp'}); |
| assert.equal(element._selectedIndex, 0); |
| |
| sinon.assert.callCount(preventDefault, 3); |
| }); |
| |
| it('Enter completes with selected completion', async () => { |
| element.completions = [ |
| 'hello', |
| 'pick me', |
| 'world', |
| ]; |
| |
| element._selectedIndex = 1; |
| |
| await element.updateComplete; |
| |
| const preventDefault = sinon.stub(); |
| |
| element._navigateCompletions({preventDefault, key: 'Enter'}); |
| |
| assert.equal(input.value, 'pick me'); |
| sinon.assert.callCount(preventDefault, 1); |
| }); |
| |
| it('Escape hides completions', async () => { |
| element.completions = [ |
| 'hide', |
| 'me', |
| ]; |
| |
| await element.updateComplete; |
| |
| const preventDefault = sinon.stub(); |
| element._navigateCompletions({preventDefault, key: 'Escape'}); |
| |
| sinon.assert.callCount(preventDefault, 1); |
| |
| await element.updateComplete; |
| |
| assert.equal(element.completions.length, 0); |
| }); |
| }); |