blob: 75df329f187a9383b39603979a4de1b29a2b0263 [file] [log] [blame]
// 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);
});
});