Merge "Add gr-smart-search to handle rest API calls and navigation"
This commit is contained in:
commit
acdc81bb81
@ -22,7 +22,7 @@ limitations under the License.
|
||||
<link rel="import" href="../../shared/gr-dropdown/gr-dropdown.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
<link rel="import" href="../gr-account-dropdown/gr-account-dropdown.html">
|
||||
<link rel="import" href="../gr-search-bar/gr-search-bar.html">
|
||||
<link rel="import" href="../gr-smart-search/gr-smart-search.html">
|
||||
|
||||
<dom-module id="gr-main-header">
|
||||
<template>
|
||||
@ -86,7 +86,7 @@ limitations under the License.
|
||||
.rightItems gr-endpoint-decorator:not(:empty) {
|
||||
margin-left: 1em;
|
||||
}
|
||||
gr-search-bar {
|
||||
gr-smart-search {
|
||||
flex-grow: 1;
|
||||
margin-left: .5em;
|
||||
max-width: 500px;
|
||||
@ -129,7 +129,7 @@ limitations under the License.
|
||||
font-size: var(--font-size-large);
|
||||
font-family: var(--font-family-bold);
|
||||
}
|
||||
gr-search-bar,
|
||||
gr-smart-search,
|
||||
.browse,
|
||||
.rightItems .hideOnMobile,
|
||||
.links > li.hideOnMobile {
|
||||
@ -171,7 +171,7 @@ limitations under the License.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="rightItems">
|
||||
<gr-search-bar value="{{searchQuery}}" role="search"></gr-search-bar>
|
||||
<gr-smart-search id="search" value="{{searchQuery}}"></gr-smart-search>
|
||||
<gr-endpoint-decorator
|
||||
class="hideOnMobile"
|
||||
name="header-browse-source"></gr-endpoint-decorator>
|
||||
|
@ -237,6 +237,15 @@ limitations under the License.
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* Navigate to a search query
|
||||
* @param {string} query
|
||||
* @param {number=} opt_offset
|
||||
*/
|
||||
navigateToSearchQuery(query, opt_offset) {
|
||||
return this._navigate(this.getUrlForSearchQuery(query, opt_offset));
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {!Object} change The change object.
|
||||
* @param {number=} opt_patchNum
|
||||
|
@ -15,20 +15,15 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<link rel="import" href="../../../behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html">
|
||||
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior.html">
|
||||
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
|
||||
<link rel="import" href="../../../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.html">
|
||||
|
||||
<dom-module id="gr-search-bar">
|
||||
<template>
|
||||
<style include="shared-styles">
|
||||
:host {
|
||||
display: inline-block;
|
||||
}
|
||||
form {
|
||||
display: flex;
|
||||
}
|
||||
@ -55,7 +50,6 @@ limitations under the License.
|
||||
threshold="[[_threshold]]"
|
||||
tab-complete
|
||||
vertical-offset="30"></gr-autocomplete>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
</form>
|
||||
</template>
|
||||
<script src="gr-search-bar.js"></script>
|
||||
|
@ -92,9 +92,6 @@
|
||||
const SEARCH_OPERATORS_WITH_NEGATIONS =
|
||||
SEARCH_OPERATORS.concat(SEARCH_OPERATORS.map(op => `-${op}`));
|
||||
|
||||
const SELF_EXPRESSION = 'self';
|
||||
const ME_EXPRESSION = 'me';
|
||||
|
||||
const MAX_AUTOCOMPLETE_RESULTS = 10;
|
||||
|
||||
const TOKENIZE_REGEX = /(?:[^\s"]+|"[^"]*")+\s*/g;
|
||||
@ -102,8 +99,13 @@
|
||||
Polymer({
|
||||
is: 'gr-search-bar',
|
||||
|
||||
/**
|
||||
* Fired when a search is committed
|
||||
*
|
||||
* @event handle-search
|
||||
*/
|
||||
|
||||
behaviors: [
|
||||
Gerrit.AnonymousNameBehavior,
|
||||
Gerrit.KeyboardShortcutBehavior,
|
||||
Gerrit.URLEncodingBehavior,
|
||||
],
|
||||
@ -129,18 +131,29 @@
|
||||
return this._getSearchSuggestions.bind(this);
|
||||
},
|
||||
},
|
||||
projectSuggestions: {
|
||||
type: Function,
|
||||
value() {
|
||||
return () => Promise.resolve([]);
|
||||
},
|
||||
},
|
||||
groupSuggestions: {
|
||||
type: Function,
|
||||
value() {
|
||||
return () => Promise.resolve([]);
|
||||
},
|
||||
},
|
||||
accountSuggestions: {
|
||||
type: Function,
|
||||
value() {
|
||||
return () => Promise.resolve([]);
|
||||
},
|
||||
},
|
||||
_inputVal: String,
|
||||
_threshold: {
|
||||
type: Number,
|
||||
value: 1,
|
||||
},
|
||||
_config: Object,
|
||||
},
|
||||
|
||||
attached() {
|
||||
this.$.restAPI.getConfig().then(cfg => {
|
||||
this._config = cfg;
|
||||
});
|
||||
},
|
||||
|
||||
_valueChanged(value) {
|
||||
@ -170,87 +183,12 @@
|
||||
target.blur();
|
||||
}
|
||||
if (this._inputVal) {
|
||||
page.show('/q/' + this.encodeURL(this._inputVal, false));
|
||||
this.dispatchEvent(new CustomEvent('handle-search', {
|
||||
detail: {inputVal: this._inputVal},
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
_accountOrAnon(name) {
|
||||
return this.getUserName(this._config, name, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch from the API the predicted accounts.
|
||||
* @param {string} predicate - The first part of the search term, e.g.
|
||||
* 'owner'
|
||||
* @param {string} expression - The second part of the search term, e.g.
|
||||
* 'kasp'
|
||||
* @return {!Promise} This returns a promise that resolves to an array of
|
||||
* strings.
|
||||
*/
|
||||
_fetchAccounts(predicate, expression) {
|
||||
if (expression.length === 0) { return Promise.resolve([]); }
|
||||
return this.$.restAPI.getSuggestedAccounts(
|
||||
expression,
|
||||
MAX_AUTOCOMPLETE_RESULTS)
|
||||
.then(accounts => {
|
||||
if (!accounts) { return []; }
|
||||
return accounts.map(acct => acct.email ?
|
||||
`${predicate}:${acct.email}` :
|
||||
`${predicate}:"${this._accountOrAnon(acct)}"`);
|
||||
}).then(accounts => {
|
||||
// When the expression supplied is a beginning substring of 'self',
|
||||
// add it as an autocomplete option.
|
||||
if (SELF_EXPRESSION.startsWith(expression)) {
|
||||
return accounts.concat([predicate + ':' + SELF_EXPRESSION]);
|
||||
} else if (ME_EXPRESSION.startsWith(expression)) {
|
||||
return accounts.concat([predicate + ':' + ME_EXPRESSION]);
|
||||
} else {
|
||||
return accounts;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch from the API the predicted groups.
|
||||
* @param {string} predicate - The first part of the search term, e.g.
|
||||
* 'ownerin'
|
||||
* @param {string} expression - The second part of the search term, e.g.
|
||||
* 'polyger'
|
||||
* @return {!Promise} This returns a promise that resolves to an array of
|
||||
* strings.
|
||||
*/
|
||||
_fetchGroups(predicate, expression) {
|
||||
if (expression.length === 0) { return Promise.resolve([]); }
|
||||
return this.$.restAPI.getSuggestedGroups(
|
||||
expression,
|
||||
MAX_AUTOCOMPLETE_RESULTS)
|
||||
.then(groups => {
|
||||
if (!groups) { return []; }
|
||||
const keys = Object.keys(groups);
|
||||
return keys.map(key => predicate + ':' + key);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch from the API the predicted projects.
|
||||
* @param {string} predicate - The first part of the search term, e.g.
|
||||
* 'project'
|
||||
* @param {string} expression - The second part of the search term, e.g.
|
||||
* 'gerr'
|
||||
* @return {!Promise} This returns a promise that resolves to an array of
|
||||
* strings.
|
||||
*/
|
||||
_fetchProjects(predicate, expression) {
|
||||
return this.$.restAPI.getSuggestedProjects(
|
||||
expression,
|
||||
MAX_AUTOCOMPLETE_RESULTS)
|
||||
.then(projects => {
|
||||
if (!projects) { return []; }
|
||||
const keys = Object.keys(projects);
|
||||
return keys.map(key => predicate + ':' + key);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine what array of possible suggestions should be provided
|
||||
* to _getSearchSuggestions.
|
||||
@ -268,12 +206,12 @@
|
||||
case 'ownerin':
|
||||
case 'reviewerin':
|
||||
// Fetch groups.
|
||||
return this._fetchGroups(predicate, expression);
|
||||
return this.groupSuggestions(predicate, expression);
|
||||
|
||||
case 'parentproject':
|
||||
case 'project':
|
||||
// Fetch projects.
|
||||
return this._fetchProjects(predicate, expression);
|
||||
return this.projectSuggestions(predicate, expression);
|
||||
|
||||
case 'author':
|
||||
case 'cc':
|
||||
@ -284,7 +222,7 @@
|
||||
case 'reviewedby':
|
||||
case 'reviewer':
|
||||
// Fetch accounts.
|
||||
return this._fetchAccounts(predicate, expression);
|
||||
return this.accountSuggestions(predicate, expression);
|
||||
|
||||
default:
|
||||
return Promise.resolve(SEARCH_OPERATORS_WITH_NEGATIONS
|
||||
|
@ -60,9 +60,8 @@ limitations under the License.
|
||||
document.activeElement;
|
||||
};
|
||||
|
||||
test('enter in search input triggers nav', done => {
|
||||
sandbox.stub(page, 'show', () => {
|
||||
page.show.restore();
|
||||
test('enter in search input fires event', done => {
|
||||
element.addEventListener('handle-search', () => {
|
||||
assert.notEqual(getActiveElement(), element.$.searchInput);
|
||||
assert.notEqual(getActiveElement(), element.$.searchButton);
|
||||
done();
|
||||
@ -72,16 +71,7 @@ limitations under the License.
|
||||
null, 'enter');
|
||||
});
|
||||
|
||||
test('search query should be double-escaped', () => {
|
||||
const showStub = sandbox.stub(page, 'show');
|
||||
element.$.searchInput.text = 'fate/stay';
|
||||
MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13,
|
||||
null, 'enter');
|
||||
assert.equal(showStub.lastCall.args[0], '/q/fate%252Fstay');
|
||||
});
|
||||
|
||||
test('input blurred after commit', () => {
|
||||
sandbox.stub(page, 'show');
|
||||
const blurSpy = sandbox.spy(element.$.searchInput.$.input, 'blur');
|
||||
element.$.searchInput.text = 'fate/stay';
|
||||
MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13,
|
||||
@ -90,11 +80,12 @@ limitations under the License.
|
||||
});
|
||||
|
||||
test('empty search query does not trigger nav', () => {
|
||||
const showSpy = sandbox.spy(page, 'show');
|
||||
const searchSpy = sandbox.spy();
|
||||
element.addEventListener('handle-search', searchSpy);
|
||||
element.value = '';
|
||||
MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13,
|
||||
null, 'enter');
|
||||
assert.isFalse(showSpy.called);
|
||||
assert.isFalse(searchSpy.called);
|
||||
});
|
||||
|
||||
test('keyboard shortcuts', () => {
|
||||
@ -107,64 +98,20 @@ limitations under the License.
|
||||
|
||||
suite('_getSearchSuggestions', () => {
|
||||
test('Autocompletes accounts', () => {
|
||||
sandbox.stub(element.$.restAPI, 'getSuggestedAccounts', () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
name: 'fred',
|
||||
email: 'fred@goog.co',
|
||||
},
|
||||
])
|
||||
sandbox.stub(element, 'accountSuggestions', () =>
|
||||
Promise.resolve(['owner:fred@goog.co'])
|
||||
);
|
||||
return element._getSearchSuggestions('owner:fr').then(s => {
|
||||
assert.equal(s[0].value, 'owner:fred@goog.co');
|
||||
});
|
||||
});
|
||||
|
||||
test('Inserts self as option when valid', done => {
|
||||
sandbox.stub(element.$.restAPI, 'getSuggestedAccounts', () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
name: 'fred',
|
||||
email: 'fred@goog.co',
|
||||
},
|
||||
])
|
||||
);
|
||||
element._getSearchSuggestions('owner:s').then(s => {
|
||||
assert.equal(s[0].value, 'owner:self');
|
||||
}).then(() => {
|
||||
element._getSearchSuggestions('owner:selfs').then(s => {
|
||||
assert.notEqual(s[0].value, 'owner:self');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Inserts me as option when valid', done => {
|
||||
sandbox.stub(element.$.restAPI, 'getSuggestedAccounts', () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
name: 'fred',
|
||||
email: 'fred@goog.co',
|
||||
},
|
||||
])
|
||||
);
|
||||
element._getSearchSuggestions('owner:m').then(s => {
|
||||
assert.equal(s[0].value, 'owner:me');
|
||||
}).then(() => {
|
||||
element._getSearchSuggestions('owner:meme').then(s => {
|
||||
assert.notEqual(s[0].value, 'owner:me');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Autocompletes groups', done => {
|
||||
sandbox.stub(element.$.restAPI, 'getSuggestedGroups', () =>
|
||||
Promise.resolve({
|
||||
Polygerrit: 0,
|
||||
gerrit: 0,
|
||||
gerrittest: 0,
|
||||
})
|
||||
sandbox.stub(element, 'groupSuggestions', () =>
|
||||
Promise.resolve([
|
||||
'ownerin:Polygerrit',
|
||||
'ownerin:gerrit',
|
||||
])
|
||||
);
|
||||
element._getSearchSuggestions('ownerin:pol').then(s => {
|
||||
assert.equal(s[0].value, 'ownerin:Polygerrit');
|
||||
@ -173,10 +120,12 @@ limitations under the License.
|
||||
});
|
||||
|
||||
test('Autocompletes projects', done => {
|
||||
sandbox.stub(element.$.restAPI, 'getSuggestedProjects', () =>
|
||||
Promise.resolve({
|
||||
Polygerrit: 0,
|
||||
})
|
||||
sandbox.stub(element, 'projectSuggestions', () =>
|
||||
Promise.resolve([
|
||||
'project:Polygerrit',
|
||||
'project:gerrit',
|
||||
'project:gerrittest',
|
||||
])
|
||||
);
|
||||
element._getSearchSuggestions('project:pol').then(s => {
|
||||
assert.equal(s[0].value, 'project:Polygerrit');
|
||||
@ -200,67 +149,6 @@ limitations under the License.
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('Autocomplete doesnt override exact matches to input', done => {
|
||||
sandbox.stub(element.$.restAPI, 'getSuggestedGroups', () =>
|
||||
Promise.resolve({
|
||||
Polygerrit: 0,
|
||||
gerrit: 0,
|
||||
gerrittest: 0,
|
||||
})
|
||||
);
|
||||
element._getSearchSuggestions('ownerin:gerrit').then(s => {
|
||||
assert.equal(s[0].value, 'ownerin:gerrit');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('Autocomplete respects spaces', done => {
|
||||
sandbox.stub(element.$.restAPI, 'getSuggestedAccounts', () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
name: 'fred',
|
||||
email: 'fred@goog.co',
|
||||
},
|
||||
])
|
||||
);
|
||||
element._getSearchSuggestions('is:ope').then(s => {
|
||||
assert.equal(s[0].name, 'is:open');
|
||||
assert.equal(s[0].value, 'is:open');
|
||||
element._getSearchSuggestions('is:ope ').then(s => {
|
||||
assert.equal(s.length, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Autocompletes accounts with no email', done => {
|
||||
sandbox.stub(element.$.restAPI, 'getSuggestedAccounts', () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
name: 'fred',
|
||||
},
|
||||
])
|
||||
);
|
||||
element._getSearchSuggestions('owner:fr').then(s => {
|
||||
assert.equal(s[0].value, 'owner:"fred"');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('Autocompletes accounts with email', done => {
|
||||
sandbox.stub(element.$.restAPI, 'getSuggestedAccounts', () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
email: 'fred@goog.co',
|
||||
},
|
||||
])
|
||||
);
|
||||
element._getSearchSuggestions('owner:fr').then(s => {
|
||||
assert.equal(s[0].value, 'owner:fred@goog.co');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -0,0 +1,38 @@
|
||||
<!--
|
||||
@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.
|
||||
-->
|
||||
<link rel="import" href="../../../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../../../behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html">
|
||||
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
<link rel="import" href="../gr-search-bar/gr-search-bar.html">
|
||||
|
||||
<dom-module id="gr-smart-search">
|
||||
<template>
|
||||
<style include="shared-styles">
|
||||
|
||||
</style>
|
||||
<gr-search-bar id="search"
|
||||
value="{{searchQuery}}"
|
||||
on-handle-search="_handleSearch"
|
||||
project-suggestions="[[_projectSuggestions]]"
|
||||
group-suggestions="[[_groupSuggestions]]"
|
||||
account-suggestions="[[_accountSuggestions]]"></gr-search-bar>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
</template>
|
||||
<script src="gr-smart-search.js"></script>
|
||||
</dom-module>
|
@ -0,0 +1,144 @@
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const MAX_AUTOCOMPLETE_RESULTS = 10;
|
||||
const SELF_EXPRESSION = 'self';
|
||||
const ME_EXPRESSION = 'me';
|
||||
|
||||
Polymer({
|
||||
is: 'gr-smart-search',
|
||||
|
||||
properties: {
|
||||
searchQuery: String,
|
||||
_config: Object,
|
||||
_projectSuggestions: {
|
||||
type: Function,
|
||||
value() {
|
||||
return this._fetchProjects.bind(this);
|
||||
},
|
||||
},
|
||||
_groupSuggestions: {
|
||||
type: Function,
|
||||
value() {
|
||||
return this._fetchGroups.bind(this);
|
||||
},
|
||||
},
|
||||
_accountSuggestions: {
|
||||
type: Function,
|
||||
value() {
|
||||
return this._fetchAccounts.bind(this);
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
behaviors: [
|
||||
Gerrit.AnonymousNameBehavior,
|
||||
],
|
||||
|
||||
attached() {
|
||||
this.$.restAPI.getConfig().then(cfg => {
|
||||
this._config = cfg;
|
||||
});
|
||||
},
|
||||
|
||||
_handleSearch(e) {
|
||||
const input = e.detail.inputVal;
|
||||
if (input) {
|
||||
Gerrit.Nav.navigateToSearchQuery(input);
|
||||
}
|
||||
},
|
||||
|
||||
_accountOrAnon(name) {
|
||||
return this.getUserName(this._serverConfig, name, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch from the API the predicted projects.
|
||||
* @param {string} predicate - The first part of the search term, e.g.
|
||||
* 'project'
|
||||
* @param {string} expression - The second part of the search term, e.g.
|
||||
* 'gerr'
|
||||
* @return {!Promise} This returns a promise that resolves to an array of
|
||||
* strings.
|
||||
*/
|
||||
_fetchProjects(predicate, expression) {
|
||||
return this.$.restAPI.getSuggestedProjects(
|
||||
expression,
|
||||
MAX_AUTOCOMPLETE_RESULTS)
|
||||
.then(projects => {
|
||||
if (!projects) { return []; }
|
||||
const keys = Object.keys(projects);
|
||||
return keys.map(key => predicate + ':' + key);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch from the API the predicted groups.
|
||||
* @param {string} predicate - The first part of the search term, e.g.
|
||||
* 'ownerin'
|
||||
* @param {string} expression - The second part of the search term, e.g.
|
||||
* 'polyger'
|
||||
* @return {!Promise} This returns a promise that resolves to an array of
|
||||
* strings.
|
||||
*/
|
||||
_fetchGroups(predicate, expression) {
|
||||
if (expression.length === 0) { return Promise.resolve([]); }
|
||||
return this.$.restAPI.getSuggestedGroups(
|
||||
expression,
|
||||
MAX_AUTOCOMPLETE_RESULTS)
|
||||
.then(groups => {
|
||||
if (!groups) { return []; }
|
||||
const keys = Object.keys(groups);
|
||||
return keys.map(key => predicate + ':' + key);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch from the API the predicted accounts.
|
||||
* @param {string} predicate - The first part of the search term, e.g.
|
||||
* 'owner'
|
||||
* @param {string} expression - The second part of the search term, e.g.
|
||||
* 'kasp'
|
||||
* @return {!Promise} This returns a promise that resolves to an array of
|
||||
* strings.
|
||||
*/
|
||||
_fetchAccounts(predicate, expression) {
|
||||
if (expression.length === 0) { return Promise.resolve([]); }
|
||||
return this.$.restAPI.getSuggestedAccounts(
|
||||
expression,
|
||||
MAX_AUTOCOMPLETE_RESULTS)
|
||||
.then(accounts => {
|
||||
if (!accounts) { return []; }
|
||||
return accounts.map(acct => acct.email ?
|
||||
`${predicate}:${acct.email}` :
|
||||
`${predicate}:"${this._accountOrAnon(acct)}"`);
|
||||
}).then(accounts => {
|
||||
// When the expression supplied is a beginning substring of 'self',
|
||||
// add it as an autocomplete option.
|
||||
if (SELF_EXPRESSION.startsWith(expression)) {
|
||||
return accounts.concat([predicate + ':' + SELF_EXPRESSION]);
|
||||
} else if (ME_EXPRESSION.startsWith(expression)) {
|
||||
return accounts.concat([predicate + ':' + ME_EXPRESSION]);
|
||||
} else {
|
||||
return accounts;
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
})();
|
@ -0,0 +1,174 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
@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.
|
||||
-->
|
||||
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-smart-search</title>
|
||||
|
||||
<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
|
||||
<script src="../../../bower_components/web-component-tester/browser.js"></script>
|
||||
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-smart-search.html">
|
||||
|
||||
<script>void(0);</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
<gr-smart-search></gr-smart-search>
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-smart-search tests', () => {
|
||||
let element;
|
||||
let sandbox;
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
|
||||
test('Autocompletes accounts', () => {
|
||||
sandbox.stub(element.$.restAPI, 'getSuggestedAccounts', () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
name: 'fred',
|
||||
email: 'fred@goog.co',
|
||||
},
|
||||
])
|
||||
);
|
||||
return element._fetchAccounts('owner', 'fr').then(s => {
|
||||
assert.equal(s[0], 'owner:fred@goog.co');
|
||||
});
|
||||
});
|
||||
|
||||
test('Inserts self as option when valid', done => {
|
||||
sandbox.stub(element.$.restAPI, 'getSuggestedAccounts', () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
name: 'fred',
|
||||
email: 'fred@goog.co',
|
||||
},
|
||||
])
|
||||
);
|
||||
element._fetchAccounts('owner', 's').then(s => {
|
||||
assert.equal(s[0], 'owner:fred@goog.co');
|
||||
assert.equal(s[1], 'owner:self');
|
||||
}).then(() => {
|
||||
element._fetchAccounts('owner', 'selfs').then(s => {
|
||||
assert.notEqual(s[0], 'owner:self');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Inserts me as option when valid', done => {
|
||||
sandbox.stub(element.$.restAPI, 'getSuggestedAccounts', () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
name: 'fred',
|
||||
email: 'fred@goog.co',
|
||||
},
|
||||
])
|
||||
);
|
||||
element._fetchAccounts('owner', 'm').then(s => {
|
||||
assert.equal(s[0], 'owner:fred@goog.co');
|
||||
assert.equal(s[1], 'owner:me');
|
||||
}).then(() => {
|
||||
element._fetchAccounts('owner', 'meme').then(s => {
|
||||
assert.notEqual(s[0], 'owner:me');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Autocompletes groups', done => {
|
||||
sandbox.stub(element.$.restAPI, 'getSuggestedGroups', () =>
|
||||
Promise.resolve({
|
||||
Polygerrit: 0,
|
||||
gerrit: 0,
|
||||
gerrittest: 0,
|
||||
})
|
||||
);
|
||||
element._fetchGroups('ownerin', 'pol').then(s => {
|
||||
assert.equal(s[0], 'ownerin:Polygerrit');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('Autocompletes projects', done => {
|
||||
sandbox.stub(element.$.restAPI, 'getSuggestedProjects', () =>
|
||||
Promise.resolve({
|
||||
Polygerrit: 0,
|
||||
})
|
||||
);
|
||||
element._fetchProjects('project', 'pol').then(s => {
|
||||
assert.equal(s[0], 'project:Polygerrit');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('Autocomplete doesnt override exact matches to input', done => {
|
||||
sandbox.stub(element.$.restAPI, 'getSuggestedGroups', () =>
|
||||
Promise.resolve({
|
||||
Polygerrit: 0,
|
||||
gerrit: 0,
|
||||
gerrittest: 0,
|
||||
})
|
||||
);
|
||||
element._fetchGroups('ownerin', 'gerrit').then(s => {
|
||||
assert.equal(s[0], 'ownerin:Polygerrit');
|
||||
assert.equal(s[1], 'ownerin:gerrit');
|
||||
assert.equal(s[2], 'ownerin:gerrittest');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('Autocompletes accounts with no email', done => {
|
||||
sandbox.stub(element.$.restAPI, 'getSuggestedAccounts', () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
name: 'fred',
|
||||
},
|
||||
])
|
||||
);
|
||||
element._fetchAccounts('owner', 'fr').then(s => {
|
||||
assert.equal(s[0], 'owner:"fred"');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('Autocompletes accounts with email', done => {
|
||||
sandbox.stub(element.$.restAPI, 'getSuggestedAccounts', () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
email: 'fred@goog.co',
|
||||
},
|
||||
])
|
||||
);
|
||||
element._fetchAccounts('owner', 'fr').then(s => {
|
||||
assert.equal(s[0], 'owner:fred@goog.co');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
@ -91,6 +91,7 @@ limitations under the License.
|
||||
'core/gr-reporting/gr-reporting_test.html',
|
||||
'core/gr-router/gr-router_test.html',
|
||||
'core/gr-search-bar/gr-search-bar_test.html',
|
||||
'core/gr-smart-search/gr-smart-search_test.html',
|
||||
'diff/gr-comment-api/gr-comment-api_test.html',
|
||||
'diff/gr-diff-builder/gr-diff-builder_test.html',
|
||||
'diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group_test.html',
|
||||
|
Loading…
Reference in New Issue
Block a user