blob: 6fe5e15f2575d13235c248a345e5083e9c8158a5 [file] [log] [blame]
/**
* @license
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import '../../../test/common-test-setup-karma';
import './gr-autocomplete';
import {html} from '@polymer/polymer/lib/utils/html-tag';
import {flush as flush$0} from '@polymer/polymer/lib/legacy/polymer.dom';
import {AutocompleteSuggestion, GrAutocomplete} from './gr-autocomplete';
import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
import {assertIsDefined} from '../../../utils/common-util';
import {queryAll, queryAndAssert} from '../../../test/test-utils';
import {GrAutocompleteDropdown} from '../gr-autocomplete-dropdown/gr-autocomplete-dropdown';
import {PaperInputElement} from '@polymer/paper-input/paper-input';
const basicFixture = fixtureFromTemplate(
html`<gr-autocomplete no-debounce></gr-autocomplete>`
);
suite('gr-autocomplete tests', () => {
let element: GrAutocomplete;
const focusOnInput = () => {
MockInteractions.pressAndReleaseKeyOn(inputEl(), 13, null, 'enter');
};
const suggestionsEl = () =>
queryAndAssert<GrAutocompleteDropdown>(element, '#suggestions');
const inputEl = () => queryAndAssert<HTMLInputElement>(element, '#input');
setup(() => {
element = basicFixture.instantiate() as GrAutocomplete;
});
test('renders', () => {
let promise: Promise<AutocompleteSuggestion[]> = Promise.resolve([]);
const queryStub = sinon.spy(
(input: string) =>
(promise = Promise.resolve([
{name: input + ' 0', value: '0'},
{name: input + ' 1', value: '1'},
{name: input + ' 2', value: '2'},
{name: input + ' 3', value: '3'},
{name: input + ' 4', value: '4'},
] as AutocompleteSuggestion[]))
);
element.query = queryStub;
assert.isTrue(suggestionsEl().isHidden);
assert.equal(suggestionsEl().cursor.index, -1);
focusOnInput();
element.text = 'blah';
assert.isTrue(queryStub.called);
element._focused = true;
assertIsDefined(promise);
return promise.then(() => {
assert.isFalse(suggestionsEl().isHidden);
const suggestions = queryAll<HTMLElement>(suggestionsEl(), 'li');
assert.equal(suggestions.length, 5);
for (let i = 0; i < 5; i++) {
assert.equal(suggestions[i].innerText.trim(), `blah ${i}`);
}
assert.notEqual(suggestionsEl().cursor.index, -1);
});
});
test('selectAll', async () => {
await flush();
const nativeInput = element._nativeInput;
const selectionStub = sinon.stub(nativeInput, 'setSelectionRange');
element.selectAll();
assert.isFalse(selectionStub.called);
inputEl().value = 'test';
element.selectAll();
assert.isTrue(selectionStub.called);
});
test('esc key behavior', () => {
let promise: Promise<AutocompleteSuggestion[]> = Promise.resolve([]);
const queryStub = sinon.spy(
(_: string) =>
(promise = Promise.resolve([
{name: 'blah', value: '123'},
] as AutocompleteSuggestion[]))
);
element.query = queryStub;
assert.isTrue(suggestionsEl().isHidden);
element._focused = true;
element.text = 'blah';
return promise.then(() => {
assert.isFalse(suggestionsEl().isHidden);
const cancelHandler = sinon.spy();
element.addEventListener('cancel', cancelHandler);
MockInteractions.pressAndReleaseKeyOn(inputEl(), 27, null, 'esc');
assert.isFalse(cancelHandler.called);
assert.isTrue(suggestionsEl().isHidden);
assert.equal(element._suggestions.length, 0);
MockInteractions.pressAndReleaseKeyOn(inputEl(), 27, null, 'esc');
assert.isTrue(cancelHandler.called);
});
});
test('emits commit and handles cursor movement', () => {
let promise: Promise<AutocompleteSuggestion[]> = Promise.resolve([]);
const queryStub = sinon.spy(
(input: string) =>
(promise = Promise.resolve([
{name: input + ' 0', value: '0'},
{name: input + ' 1', value: '1'},
{name: input + ' 2', value: '2'},
{name: input + ' 3', value: '3'},
{name: input + ' 4', value: '4'},
] as AutocompleteSuggestion[]))
);
element.query = queryStub;
assert.isTrue(suggestionsEl().isHidden);
assert.equal(suggestionsEl().cursor.index, -1);
element._focused = true;
element.text = 'blah';
return promise.then(() => {
assert.isFalse(suggestionsEl().isHidden);
const commitHandler = sinon.spy();
element.addEventListener('commit', commitHandler);
assert.equal(suggestionsEl().cursor.index, 0);
MockInteractions.pressAndReleaseKeyOn(inputEl(), 40, null, 'down');
assert.equal(suggestionsEl().cursor.index, 1);
MockInteractions.pressAndReleaseKeyOn(inputEl(), 40, null, 'down');
assert.equal(suggestionsEl().cursor.index, 2);
MockInteractions.pressAndReleaseKeyOn(inputEl(), 38, null, 'up');
assert.equal(suggestionsEl().cursor.index, 1);
MockInteractions.pressAndReleaseKeyOn(inputEl(), 13, null, 'enter');
assert.equal(element.value, '1');
assert.isTrue(commitHandler.called);
assert.equal(commitHandler.getCall(0).args[0].detail.value, 1);
assert.isTrue(suggestionsEl().isHidden);
assert.isTrue(element._focused);
});
});
test('clear-on-commit behavior (off)', () => {
let promise: Promise<AutocompleteSuggestion[]> = Promise.resolve([]);
const queryStub = sinon.spy(() => {
promise = Promise.resolve([
{name: 'suggestion', value: '0'},
] as AutocompleteSuggestion[]);
return promise;
});
element.query = queryStub;
focusOnInput();
element.text = 'blah';
return promise.then(() => {
const commitHandler = sinon.spy();
element.addEventListener('commit', commitHandler);
MockInteractions.pressAndReleaseKeyOn(inputEl(), 13, null, 'enter');
assert.isTrue(commitHandler.called);
assert.equal(element.text, 'suggestion');
});
});
test('clear-on-commit behavior (on)', () => {
let promise: Promise<AutocompleteSuggestion[]> = Promise.resolve([]);
const queryStub = sinon.spy(() => {
promise = Promise.resolve([
{name: 'suggestion', value: '0'},
] as AutocompleteSuggestion[]);
return promise;
});
element.query = queryStub;
focusOnInput();
element.text = 'blah';
element.clearOnCommit = true;
return promise.then(() => {
const commitHandler = sinon.spy();
element.addEventListener('commit', commitHandler);
MockInteractions.pressAndReleaseKeyOn(inputEl(), 13, null, 'enter');
assert.isTrue(commitHandler.called);
assert.equal(element.text, '');
});
});
test('threshold guards the query', () => {
const queryStub = sinon.spy(() =>
Promise.resolve([] as AutocompleteSuggestion[])
);
element.query = queryStub;
element.threshold = 2;
focusOnInput();
element.text = 'a';
assert.isFalse(queryStub.called);
element.text = 'ab';
assert.isTrue(queryStub.called);
});
test('noDebounce=false debounces the query', () => {
const clock = sinon.useFakeTimers();
const queryStub = sinon.spy(() =>
Promise.resolve([] as AutocompleteSuggestion[])
);
element.query = queryStub;
element.noDebounce = false;
focusOnInput();
element.text = 'a';
// not called right away
assert.isFalse(queryStub.called);
// but called after a while
clock.tick(1000);
assert.isTrue(queryStub.called);
});
test('_computeClass respects border property', () => {
assert.equal(element._computeClass(), '');
assert.equal(element._computeClass(false), '');
assert.equal(element._computeClass(true), 'borderless');
});
test('undefined or empty text results in no suggestions', () => {
element._updateSuggestions(undefined, 0, undefined);
assert.equal(element._suggestions.length, 0);
});
test('when focused', () => {
let promise: Promise<AutocompleteSuggestion[]> = Promise.resolve([]);
const queryStub = sinon
.stub()
.returns(
(promise = Promise.resolve([
{name: 'suggestion', value: '0'},
] as AutocompleteSuggestion[]))
);
element.query = queryStub;
focusOnInput();
element.text = 'bla';
assert.equal(element._focused, true);
flush();
return promise.then(() => {
assert.equal(element._suggestions.length, 1);
assert.equal(queryStub.notCalled, false);
});
});
test('when not focused', () => {
let promise: Promise<AutocompleteSuggestion[]> = Promise.resolve([]);
const queryStub = sinon
.stub()
.returns(
(promise = Promise.resolve([
{name: 'suggestion', value: '0'},
] as AutocompleteSuggestion[]))
);
element.query = queryStub;
element.text = 'bla';
assert.equal(element._focused, false);
flush();
return promise.then(() => {
assert.equal(element._suggestions.length, 0);
});
});
test('suggestions should not carry over', () => {
let promise: Promise<AutocompleteSuggestion[]> = Promise.resolve([]);
const queryStub = sinon
.stub()
.returns(
(promise = Promise.resolve([
{name: 'suggestion', value: '0'},
] as AutocompleteSuggestion[]))
);
element.query = queryStub;
focusOnInput();
element.text = 'bla';
flush();
return promise.then(() => {
assert.equal(element._suggestions.length, 1);
element._updateSuggestions('', 0, false);
assert.equal(element._suggestions.length, 0);
});
});
test('multi completes only the last part of the query', () => {
let promise;
const queryStub = sinon
.stub()
.returns(
(promise = Promise.resolve([
{name: 'suggestion', value: '0'},
] as AutocompleteSuggestion[]))
);
element.query = queryStub;
focusOnInput();
element.text = 'blah blah';
element.multi = true;
return promise.then(() => {
const commitHandler = sinon.spy();
element.addEventListener('commit', commitHandler);
MockInteractions.pressAndReleaseKeyOn(inputEl(), 13, null, 'enter');
assert.isTrue(commitHandler.called);
assert.equal(element.text, 'blah 0');
});
});
test('tabComplete flag functions', () => {
// commitHandler checks for the commit event, whereas commitSpy checks for
// the _commit function of the element.
const commitHandler = sinon.spy();
element.addEventListener('commit', commitHandler);
const commitSpy = sinon.spy(element, '_commit');
element._focused = true;
element._suggestions = [{text: 'tunnel snakes rule!'}];
element.tabComplete = false;
MockInteractions.pressAndReleaseKeyOn(inputEl(), 9, null, 'tab');
assert.isFalse(commitHandler.called);
assert.isFalse(commitSpy.called);
assert.isFalse(element._focused);
element.tabComplete = true;
element._focused = true;
MockInteractions.pressAndReleaseKeyOn(inputEl(), 9, null, 'tab');
assert.isFalse(commitHandler.called);
assert.isTrue(commitSpy.called);
assert.isTrue(element._focused);
});
test('_focused flag properly triggered', () => {
flush();
assert.isFalse(element._focused);
const input = queryAndAssert<PaperInputElement>(element, 'paper-input')
.inputElement;
MockInteractions.focus(input);
assert.isTrue(element._focused);
});
test('search icon shows with showSearchIcon property', () => {
flush();
assert.equal(
getComputedStyle(queryAndAssert(element, 'iron-icon')).display,
'none'
);
element.showSearchIcon = true;
assert.notEqual(
getComputedStyle(queryAndAssert(element, 'iron-icon')).display,
'none'
);
});
test('vertical offset overridden by param if it exists', () => {
assert.equal(suggestionsEl().verticalOffset, 31);
element.verticalOffset = 30;
assert.equal(suggestionsEl().verticalOffset, 30);
});
test('_focused flag shows/hides the suggestions', () => {
const openStub = sinon.stub(suggestionsEl(), 'open');
const closedStub = sinon.stub(suggestionsEl(), 'close');
element._suggestions = [{text: 'hello'}, {text: 'its me'}];
assert.isFalse(openStub.called);
assert.isTrue(closedStub.calledOnce);
element._focused = true;
assert.isTrue(openStub.calledOnce);
element._suggestions = [];
assert.isTrue(closedStub.calledTwice);
assert.isTrue(openStub.calledOnce);
});
test(
'_handleInputCommit with autocomplete hidden does nothing without' +
'without allowNonSuggestedValues',
() => {
const commitStub = sinon.stub(element, '_commit');
suggestionsEl().isHidden = true;
element._handleInputCommit();
assert.isFalse(commitStub.called);
}
);
test(
'_handleInputCommit with autocomplete hidden with' +
'allowNonSuggestedValues',
() => {
const commitStub = sinon.stub(element, '_commit');
element.allowNonSuggestedValues = true;
suggestionsEl().isHidden = true;
element._handleInputCommit();
assert.isTrue(commitStub.called);
}
);
test('_handleInputCommit with autocomplete open calls commit', () => {
const commitStub = sinon.stub(element, '_commit');
suggestionsEl().isHidden = false;
element._handleInputCommit();
assert.isTrue(commitStub.calledOnce);
});
test(
'_handleInputCommit with autocomplete open calls commit' +
'with allowNonSuggestedValues',
() => {
const commitStub = sinon.stub(element, '_commit');
element.allowNonSuggestedValues = true;
suggestionsEl().isHidden = false;
element._handleInputCommit();
assert.isTrue(commitStub.calledOnce);
}
);
test('issue 8655', () => {
function makeSuggestion(s: string) {
return {name: s, text: s, value: s};
}
const keydownSpy = sinon.spy(element, '_handleKeydown');
element.setText('file:');
element._suggestions = [makeSuggestion('file:'), makeSuggestion('-file:')];
MockInteractions.pressAndReleaseKeyOn(inputEl(), 88, null, 'x');
// Must set the value, because the MockInteraction does not.
inputEl().value = 'file:x';
assert.isTrue(keydownSpy.calledOnce);
MockInteractions.pressAndReleaseKeyOn(inputEl(), 13, null, 'enter');
assert.isTrue(keydownSpy.calledTwice);
assert.equal(element.text, 'file:x');
});
suite('focus', () => {
let commitSpy: sinon.SinonSpy;
let focusSpy: sinon.SinonSpy;
setup(() => {
commitSpy = sinon.spy(element, '_commit');
});
test('enter does not call focus', () => {
element._suggestions = [{text: 'sugar bombs'}];
focusSpy = sinon.spy(element, 'focus');
MockInteractions.pressAndReleaseKeyOn(inputEl(), 13, null, 'enter');
flush();
assert.isTrue(commitSpy.called);
assert.isFalse(focusSpy.called);
assert.equal(element._suggestions.length, 0);
});
test('tab in input, tabComplete = true', () => {
focusSpy = sinon.spy(element, 'focus');
const commitHandler = sinon.stub();
element.addEventListener('commit', commitHandler);
element.tabComplete = true;
element._suggestions = [{text: 'tunnel snakes drool'}];
MockInteractions.pressAndReleaseKeyOn(inputEl(), 9, null, 'tab');
flush();
assert.isTrue(commitSpy.called);
assert.isTrue(focusSpy.called);
assert.isFalse(commitHandler.called);
assert.equal(element._suggestions.length, 0);
});
test('tab in input, tabComplete = false', () => {
element._suggestions = [{text: 'sugar bombs'}];
focusSpy = sinon.spy(element, 'focus');
MockInteractions.pressAndReleaseKeyOn(inputEl(), 9, null, 'tab');
flush();
assert.isFalse(commitSpy.called);
assert.isFalse(focusSpy.called);
assert.equal(element._suggestions.length, 1);
});
test('tab on suggestion, tabComplete = false', () => {
element._suggestions = [{name: 'sugar bombs'}];
element._focused = true;
// When tabComplete is false, do not focus.
element.tabComplete = false;
focusSpy = sinon.spy(element, 'focus');
flush$0();
assert.isFalse(suggestionsEl().isHidden);
MockInteractions.pressAndReleaseKeyOn(
queryAndAssert(suggestionsEl(), 'li:first-child'),
9,
null,
'tab'
);
flush();
assert.isFalse(commitSpy.called);
assert.isFalse(element._focused);
});
test('tab on suggestion, tabComplete = true', () => {
element._suggestions = [{name: 'sugar bombs'}];
element._focused = true;
// When tabComplete is true, focus.
element.tabComplete = true;
focusSpy = sinon.spy(element, 'focus');
flush$0();
assert.isFalse(suggestionsEl().isHidden);
MockInteractions.pressAndReleaseKeyOn(
queryAndAssert(suggestionsEl(), 'li:first-child'),
9,
null,
'tab'
);
flush();
assert.isTrue(commitSpy.called);
assert.isTrue(element._focused);
});
test('tap on suggestion commits, does not call focus', () => {
focusSpy = sinon.spy(element, 'focus');
element._focused = true;
element._suggestions = [{name: 'first suggestion'}];
flush$0();
assert.isFalse(suggestionsEl().isHidden);
MockInteractions.tap(queryAndAssert(suggestionsEl(), 'li:first-child'));
flush();
assert.isFalse(focusSpy.called);
assert.isTrue(commitSpy.called);
assert.isTrue(suggestionsEl().isHidden);
});
});
test('input-keydown event fired', () => {
const listener = sinon.spy();
element.addEventListener('input-keydown', listener);
MockInteractions.pressAndReleaseKeyOn(inputEl(), 9, null, 'tab');
flush();
assert.isTrue(listener.called);
});
test('enter with modifier does not complete', () => {
const handleSpy = sinon.spy(element, '_handleKeydown');
const commitStub = sinon.stub(element, '_handleInputCommit');
MockInteractions.pressAndReleaseKeyOn(inputEl(), 13, 'ctrl', 'enter');
assert.isTrue(handleSpy.called);
assert.isFalse(commitStub.called);
MockInteractions.pressAndReleaseKeyOn(inputEl(), 13, null, 'enter');
assert.isTrue(commitStub.called);
});
suite('warnUncommitted', () => {
let inputClassList: DOMTokenList;
setup(() => {
inputClassList = inputEl().classList;
});
test('enabled', () => {
element.warnUncommitted = true;
element.text = 'blah blah blah';
MockInteractions.blur(inputEl());
assert.isTrue(inputClassList.contains('warnUncommitted'));
MockInteractions.focus(inputEl());
assert.isFalse(inputClassList.contains('warnUncommitted'));
});
test('disabled', () => {
element.warnUncommitted = false;
element.text = 'blah blah blah';
MockInteractions.blur(inputEl());
assert.isFalse(inputClassList.contains('warnUncommitted'));
});
test('no text', () => {
element.warnUncommitted = true;
element.text = '';
MockInteractions.blur(inputEl());
assert.isFalse(inputClassList.contains('warnUncommitted'));
});
});
});