Merge "Remove modifier pressed check for bracket key"
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneAccountIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneAccountIndex.java
index fbde7be..be4f917 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneAccountIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneAccountIndex.java
@@ -81,7 +81,7 @@
if (LuceneIndexModule.isInMemoryTest(cfg)) {
return new RAMDirectory();
}
- Path indexDir = LuceneVersionManager.getDir(sitePaths, ACCOUNTS + "_", schema);
+ Path indexDir = LuceneVersionManager.getDir(sitePaths, ACCOUNTS, schema);
return FSDirectory.open(indexDir);
}
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index 3afcb07..dc9f6c1 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -108,7 +108,7 @@
static final String UPDATED_SORT_FIELD = sortFieldName(ChangeField.UPDATED);
static final String ID_SORT_FIELD = sortFieldName(ChangeField.LEGACY_ID);
- private static final String CHANGES_PREFIX = "changes_";
+ private static final String CHANGES = "changes";
private static final String CHANGES_OPEN = "open";
private static final String CHANGES_CLOSED = "closed";
private static final String ADDED_FIELD = ChangeField.ADDED.getName();
@@ -178,7 +178,7 @@
new ChangeSubIndex(
schema, sitePaths, new RAMDirectory(), "ramClosed", closedConfig, searcherFactory);
} else {
- Path dir = LuceneVersionManager.getDir(sitePaths, CHANGES_PREFIX, schema);
+ Path dir = LuceneVersionManager.getDir(sitePaths, CHANGES, schema);
openIndex =
new ChangeSubIndex(
schema, sitePaths, dir.resolve(CHANGES_OPEN), openConfig, searcherFactory);
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneGroupIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneGroupIndex.java
index c4f10ff..daece8c 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneGroupIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneGroupIndex.java
@@ -80,7 +80,7 @@
if (LuceneIndexModule.isInMemoryTest(cfg)) {
return new RAMDirectory();
}
- Path indexDir = LuceneVersionManager.getDir(sitePaths, GROUPS + "_", schema);
+ Path indexDir = LuceneVersionManager.getDir(sitePaths, GROUPS, schema);
return FSDirectory.open(indexDir);
}
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java
index f6f0c28..ad13066 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java
@@ -48,8 +48,8 @@
}
}
- static Path getDir(SitePaths sitePaths, String prefix, Schema<?> schema) {
- return sitePaths.index_dir.resolve(String.format("%s%04d", prefix, schema.getVersion()));
+ static Path getDir(SitePaths sitePaths, String name, Schema<?> schema) {
+ return sitePaths.index_dir.resolve(String.format("%s_%04d", name, schema.getVersion()));
}
@Inject
diff --git a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html
index 72dc0b23..975bb5e 100644
--- a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html
+++ b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html
@@ -20,24 +20,24 @@
(function(window) {
'use strict';
+ // Must be declared outside behavior implementation to be accessed inside
+ // behavior functions.
const getKeyboardEvent = function(e) {
- return Polymer.dom(e.detail ? e.detail.keyboardEvent : e);
+ e = Polymer.dom(e.detail ? e.detail.keyboardEvent : e);
+ // When e is a keyboardEvent, e.event is not null.
+ if (e.event) { e = e.event; }
+ return e;
};
/** @polymerBehavior KeyboardShortcutBehaviorImpl */
const KeyboardShortcutBehaviorImpl = {
modifierPressed(e) {
e = getKeyboardEvent(e);
- // When e is a keyboardEvent, e.event is not null.
- if (e.event) { e = e.event; }
return e.altKey || e.ctrlKey || e.metaKey || e.shiftKey;
},
isModifierPressed(e, modifier) {
- e = getKeyboardEvent(e);
- // When e is a keyboardEvent, e.event is not null.
- if (e.event) { e = e.event; }
- return e[modifier];
+ return getKeyboardEvent(e)[modifier];
},
shouldSuppressKeyboardShortcut(e) {
@@ -50,6 +50,11 @@
}
return false;
},
+
+ // Alias for getKeyboardEvent.
+ getKeyboardEvent(e) {
+ return getKeyboardEvent(e);
+ },
};
window.Gerrit = window.Gerrit || {};
diff --git a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html
index df376ba..da04c37 100644
--- a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html
@@ -21,7 +21,7 @@
<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
<script src="../../bower_components/web-component-tester/browser.js"></script>
-<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../../bower_components/iron-test-helpers/iron-test-helpers.html">
<link rel="import" href="keyboard-shortcut-behavior.html">
<test-fixture id="basic">
@@ -69,7 +69,7 @@
test('doesn’t block kb shortcuts for non-whitelisted els', done => {
const divEl = document.createElement('div');
element.appendChild(divEl);
- element._handleKey = function(e) {
+ element._handleKey = e => {
assert.isFalse(element.shouldSuppressKeyboardShortcut(e));
done();
};
@@ -79,7 +79,7 @@
test('blocks kb shortcuts for input els', done => {
const inputEl = document.createElement('input');
element.appendChild(inputEl);
- element._handleKey = function(e) {
+ element._handleKey = e => {
assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
done();
};
@@ -89,7 +89,7 @@
test('blocks kb shortcuts for textarea els', done => {
const textareaEl = document.createElement('textarea');
element.appendChild(textareaEl);
- element._handleKey = function(e) {
+ element._handleKey = e => {
assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
done();
};
@@ -100,7 +100,7 @@
const divEl = document.createElement('div');
const element = overlay.querySelector('test-element');
element.appendChild(divEl);
- element._handleKey = function(e) {
+ element._handleKey = e => {
assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
done();
};
@@ -109,7 +109,7 @@
test('modifierPressed returns accurate values', () => {
const spy = sandbox.spy(element, 'modifierPressed');
- element._handleKey = function(e) {
+ element._handleKey = e => {
element.modifierPressed(e);
};
MockInteractions.keyDownOn(element, 75, 'shift', 'k');
@@ -130,7 +130,7 @@
test('isModifierPressed returns accurate value', () => {
const spy = sandbox.spy(element, 'isModifierPressed');
- element._handleKey = function(e) {
+ element._handleKey = e => {
element.isModifierPressed(e, 'shiftKey');
};
MockInteractions.keyDownOn(element, 75, 'shift', 'k');
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.html b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.html
new file mode 100644
index 0000000..9ceee68
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.html
@@ -0,0 +1,107 @@
+<!--
+Copyright (C) 2017 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="../../../behaviors/base-url-behavior/base-url-behavior.html">
+<link rel="import" href="../../../behaviors/gr-url-encoding-behavior.html">
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+
+<link rel="import" href="../../../styles/gr-form-styles.html">
+
+<dom-module id="gr-admin-project-list">
+ <template>
+ <style>
+ :host {
+ display: flex;
+ flex-direction: column;
+ }
+ tr.project-table {
+ border-bottom: 1px solid #eee;
+ }
+ #projectList {
+ border-collapse: collapse;
+ width: 100%;
+ }
+ td {
+ flex-shrink: 0;
+ padding: .3em .5em;
+ }
+ th {
+ background-color: #ddd;
+ border-bottom: 1px solid #eee;
+ font-weight: bold;
+ padding: .3em .5em;
+ text-align: left;
+ }
+ a {
+ color: var(--default-text-color);
+ text-decoration: none;
+ }
+ a:hover {
+ text-decoration: underline;
+ }
+ nav {
+ padding: .5em 0;
+ text-align: center;
+ }
+ nav a {
+ display: inline-block;
+ }
+ nav a:first-of-type {
+ margin-right: .5em;
+ }
+ .description {
+ width: 70%;
+ }
+ </style>
+ <table id="projectList">
+ <tr class="headerRow">
+ <th class="name topHeader">Project Name</th>
+ <th class="description topHeader">Project Description</th>
+ <th class="repositoryBrowser topHeader">Repository Browser</th>
+ <th class="readOnly topHeader">Read only</th>
+ </tr>
+ <template is="dom-repeat" items="[[_shownProjects]]">
+ <tr class="project-table">
+ <td class="name">
+ <a href$="[[_getUrl(item.id)]]">[[item.name]]</a>
+ </td>
+ <td class="description">[[item.description]]</td>
+ <td class="repositoryBrowser">
+ <template is="dom-repeat"
+ items="[[_computeWeblink(item)]]" as="link">
+ <a href$="[[link.url]]" class="webLink" rel="noopener" target="_blank">
+ ([[link.name]])
+ </a>
+ </template>
+ </td>
+ <td class="readOnly">[[_readOnly(item)]]</td>
+ </tr>
+ </template>
+ </table>
+ <nav>
+ <a id="prevArrow"
+ href$="[[_computeNavLink(_offset, -1, _projectsPerPage)]]"
+ hidden$="[[_hidePrevArrow(_offset)]]" hidden>← Prev</a>
+ <a id="nextArrow"
+ href$="[[_computeNavLink(_offset, 1, _projectsPerPage)]]"
+ hidden$="[[_hideNextArrow(_loading, _projects)]]" hidden>
+ Next →</a>
+ </nav>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ </template>
+ <script src="gr-admin-project-list.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.js b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.js
new file mode 100644
index 0000000..038b41f
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.js
@@ -0,0 +1,148 @@
+// Copyright (C) 2017 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';
+
+ Polymer({
+ is: 'gr-admin-project-list',
+
+ properties: {
+ /**
+ * URL params passed from the router.
+ */
+ params: {
+ type: Object,
+ observer: '_paramsChanged',
+ },
+
+ /**
+ * Offset of currently visible query results.
+ */
+ _offset: Number,
+
+ _projects: Array,
+
+ /**
+ * Because we request one more than the projectsPerPage, _shownProjects
+ * maybe one less than _projects.
+ * */
+ _shownProjects: {
+ type: Array,
+ computed: '_computeShownProjects(_projects)',
+ },
+
+ _projectsPerPage: {
+ type: Number,
+ value: 25,
+ },
+
+ _loading: {
+ type: Boolean,
+ value: true,
+ },
+ },
+
+ behaviors: [
+ Gerrit.BaseUrlBehavior,
+ Gerrit.URLEncodingBehavior,
+ ],
+
+ listeners: {
+ 'next-page': '_handleNextPage',
+ 'previous-page': '_handlePreviousPage',
+ },
+
+ _paramsChanged(value) {
+ this._loading = true;
+
+ if (value && value.offset) {
+ this._offset = value.offset;
+ } else {
+ this._offset = 0;
+ }
+
+ return this.$.restAPI.getProjects(this._projectsPerPage, this._offset)
+ .then(projects => {
+ if (!projects) {
+ this._projects = [];
+ return;
+ }
+ this._projects = Object.keys(projects)
+ .map(key => {
+ const project = projects[key];
+ project.name = key;
+ return project;
+ });
+ this._loading = false;
+ });
+ },
+
+ _readOnly(item) {
+ return item.state === 'READ_ONLY' ? 'Y' : 'N';
+ },
+
+ _getUrl(item) {
+ return this.getBaseUrl() + '/admin/projects/' +
+ this.encodeURL(item, false);
+ },
+
+
+ _computeWeblink(project) {
+ if (!project.web_links) {
+ return '';
+ }
+ const webLinks = project.web_links;
+ return webLinks.length ? webLinks : null;
+ },
+
+ _computeNavLink(offset, direction, projectsPerPage) {
+ // Offset could be a string when passed from the router.
+ offset = +(offset || 0);
+ const newOffset = Math.max(0, offset + (projectsPerPage * direction));
+ let href = this.getBaseUrl() + '/admin/projects';
+ if (newOffset > 0) {
+ href += ',' + newOffset;
+ }
+ return href;
+ },
+
+ _computeShownProjects(projects) {
+ return projects.slice(0, 25);
+ },
+
+ _hidePrevArrow(offset) {
+ return offset === 0;
+ },
+
+ _hideNextArrow(loading, projects) {
+ let lastPage = false;
+ if (projects.length < this._projectsPerPage + 1) {
+ lastPage = true;
+ }
+ return loading || lastPage || !projects || !projects.length;
+ },
+
+ _handleNextPage() {
+ if (this.$.nextArrow.hidden) { return; }
+ page.show(this._computeNavLink(
+ this._offset, 1, this._projectsPerPage));
+ },
+
+ _handlePreviousPage() {
+ if (this.$.prevArrow.hidden) { return; }
+ page.show(this._computeNavLink(
+ this._offset, -1, this._projectsPerPage));
+ },
+ });
+})();
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list_test.html b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list_test.html
new file mode 100644
index 0000000..f9e08cd
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list_test.html
@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2017 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-admin-project-list</title>
+
+<script src="../../../bower_components/page/page.js"></script>
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+
+<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+
+<link rel="import" href="gr-admin-project-list.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-admin-project-list></gr-admin-project-list>
+ </template>
+</test-fixture>
+
+<script>
+ let counter = 0;
+ const projectGenerator = () => {
+ return {
+ id: `test${++counter}`,
+ state: 'ACTIVE',
+ web_links: [
+ {
+ name: 'diffusion',
+ url: `https://phabricator.example.org/r/project/test${counter}`,
+ },
+ ],
+ };
+ };
+
+ suite('gr-admin-project-list tests', () => {
+ let element;
+ let projects;
+ let value;
+
+ suite('list with projects', () => {
+ setup(done => {
+ projects = _.times(26, projectGenerator);
+
+ stub('gr-rest-api-interface', {
+ getProjects(num, offset) {
+ return Promise.resolve(projects);
+ },
+ });
+
+ element = fixture('basic');
+ element._paramsChanged(value).then(() => { flush(done); });
+ });
+
+ test('test for test project in the list', done => {
+ flush(() => {
+ assert.equal(element._projects[1].id, 'test2');
+ done();
+ });
+ });
+
+ test('test next button', done => {
+ flush(() => {
+ let loading;
+ assert.isFalse(element._hideNextArrow(loading, projects));
+ loading = true;
+ assert.isTrue(element._hideNextArrow(loading, projects));
+ loading = false;
+ assert.isFalse(element._hideNextArrow(loading, projects));
+ element._projects = [];
+ assert.isTrue(element._hideNextArrow(loading, element._projects));
+ projects = _.times(4, projectGenerator);
+ assert.isTrue(element._hideNextArrow(loading, projects));
+ done();
+ });
+ });
+
+ test('test for prev button', () => {
+ flush(() => {
+ let offset = 0;
+ assert.isTrue(element._hidePrevArrow(offset));
+ offset = 5;
+ assert.isFalse(element._hidePrevArrow(offset));
+ });
+ });
+
+ test('_shownProjects', () => {
+ assert.equal(element._shownProjects.length, 25);
+ });
+ });
+
+ suite('test with less then 25 projects', () => {
+ setup(done => {
+ projects = _.times(25, projectGenerator);
+
+ stub('gr-rest-api-interface', {
+ getProjects(num, offset) {
+ return Promise.resolve(projects);
+ },
+ });
+
+ element = fixture('basic');
+ element._paramsChanged(value).then(() => { flush(done); });
+ });
+
+ test('test next button', done => {
+ flush(() => {
+ let loading;
+ assert.isTrue(element._hideNextArrow(loading, projects));
+ projects = _.times(1, projectGenerator);
+ assert.isTrue(element._hideNextArrow(loading, projects));
+ projects = _.times(26, projectGenerator);
+ assert.isFalse(element._hideNextArrow(loading, projects));
+ done();
+ });
+ });
+
+ test('_shownProjects', () => {
+ assert.equal(element._shownProjects.length, 25);
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js
index eb6e213..be66587 100644
--- a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js
+++ b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js
@@ -67,6 +67,10 @@
this.$.input.setText(text);
},
+ getText() {
+ return this.$.input.text;
+ },
+
_handleInputCommit(e) {
this.fire('add', {value: e.detail.value});
},
diff --git a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js
index b2f1b41..4e403e6 100644
--- a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js
+++ b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js
@@ -90,11 +90,13 @@
},
_handleAdd(e) {
- const reviewer = e.detail.value;
+ this._addReviewer(e.detail.value);
+ },
+
+ _addReviewer(reviewer) {
// Append new account or group to the accounts property. We add our own
// internal properties to the account/group here, so we clone the object
// to avoid cluttering up the shared change object.
- // TODO(logan): Polyfill for Object.assign in IE.
if (reviewer.account) {
const account =
Object.assign({}, reviewer.account, {_pendingAdd: true});
@@ -114,12 +116,14 @@
this.$.entry.setText(reviewer);
this.dispatchEvent(new CustomEvent('show-alert',
{detail: {message: VALID_EMAIL_ALERT}, bubbles: true}));
+ return false;
} else {
const account = {email: reviewer, _pendingAdd: true};
this.push('accounts', account);
}
}
this.pendingConfirmation = null;
+ return true;
},
confirmGroup(group) {
@@ -245,6 +249,22 @@
}
},
+ /**
+ * Submit the text of the entry as a reviewer value, if it exists. If it is
+ * a successful submit of the text, clear the entry value.
+ *
+ * @return {boolean} If there is text in the entry, return true if the
+ * submission was successful and false if not. If there is no text,
+ * return true.
+ */
+ submitEntryText() {
+ const text = this.$.entry.getText();
+ if (!text.length) { return true; }
+ const wasSubmitted = this._addReviewer(text);
+ if (wasSubmitted) { this.$.entry.clear(); }
+ return wasSubmitted;
+ },
+
additions() {
return this.accounts.filter(account => {
return account._pendingAdd;
diff --git a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html
index f35bbcd..5520254 100644
--- a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html
@@ -175,6 +175,30 @@
assert.isFalse(element._computeRemovable(newAccount));
});
+ test('submitEntryText', () => {
+ element.allowAnyInput = true;
+ flushAsynchronousOperations();
+
+ const getTextStub = sandbox.stub(element.$.entry, 'getText');
+ getTextStub.onFirstCall().returns('');
+ getTextStub.onSecondCall().returns('test');
+ getTextStub.onThirdCall().returns('test@test');
+
+ // When entry is empty, return true.
+ const clearStub = sandbox.stub(element.$.entry, 'clear');
+ assert.isTrue(element.submitEntryText());
+ assert.isFalse(clearStub.called);
+
+ // When entry is invalid, return false.
+ assert.isFalse(element.submitEntryText());
+ assert.isFalse(clearStub.called);
+
+ // When entry is valid, return true and clear text.
+ assert.isTrue(element.submitEntryText());
+ assert.isTrue(clearStub.called);
+ assert.equal(element.additions()[0].account.email, 'test@test');
+ });
+
test('additions returns sanitized new accounts and groups', () => {
assert.equal(element.additions().length, 0);
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 77c2ca0..05c6c68 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -481,7 +481,7 @@
document.documentElement.scrollTop =
document.body.scrollTop = this.viewState.scrollTop;
} else {
- this._maybeScrollToMessage();
+ this._maybeScrollToMessage(window.location.hash);
}
}, 1);
});
@@ -513,10 +513,9 @@
this.viewState.numFilesShown = numFilesShown;
},
- _maybeScrollToMessage() {
+ _maybeScrollToMessage(hash) {
const msgPrefix = '#message-';
- const hash = window.location.hash;
- if (hash.startsWith(msgPrefix) === 0) {
+ if (hash.startsWith(msgPrefix)) {
this.$.messageList.scrollToMessage(hash.substr(msgPrefix.length));
}
},
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index 7794cd67..5847e73 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -1320,5 +1320,19 @@
'header wip');
});
});
+
+ test('_maybeScrollToMessage', () => {
+ const scrollStub = sandbox.stub(element.$.messageList, 'scrollToMessage');
+
+ element._maybeScrollToMessage('');
+ assert.isFalse(scrollStub.called);
+
+ element._maybeScrollToMessage('message');
+ assert.isFalse(scrollStub.called);
+
+ element._maybeScrollToMessage('#message-TEST');
+ assert.isTrue(scrollStub.called);
+ assert.equal(scrollStub.lastCall.args[0], 'TEST');
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html
new file mode 100644
index 0000000..b4a6171
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2015 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-reply-dialog</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="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="gr-reply-dialog.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-reply-dialog></gr-reply-dialog>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-reply-dialog tests', () => {
+ let element;
+ let changeNum;
+ let patchNum;
+
+ let sandbox;
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+
+ changeNum = 42;
+ patchNum = 1;
+
+ stub('gr-rest-api-interface', {
+ getConfig() { return Promise.resolve({}); },
+ getAccount() { return Promise.resolve({}); },
+ });
+
+ element = fixture('basic');
+ element.change = {
+ _number: changeNum,
+ labels: {
+ 'Verified': {
+ values: {
+ '-1': 'Fails',
+ ' 0': 'No score',
+ '+1': 'Verified',
+ },
+ default_value: 0,
+ },
+ 'Code-Review': {
+ values: {
+ '-2': 'Do not submit',
+ '-1': 'I would prefer that you didn\'t submit this',
+ ' 0': 'No score',
+ '+1': 'Looks good to me, but someone else must approve',
+ '+2': 'Looks good to me, approved',
+ },
+ default_value: 0,
+ },
+ },
+ };
+ element.patchNum = patchNum;
+ element.permittedLabels = {
+ 'Code-Review': [
+ '-1',
+ ' 0',
+ '+1',
+ ],
+ 'Verified': [
+ '-1',
+ ' 0',
+ '+1',
+ ],
+ };
+ element.serverConfig = {note_db_enabled: true};
+
+ sandbox.stub(element, 'fetchIsLatestKnown', () => Promise.resolve(true));
+
+ // Allow the elements created by dom-repeat to be stamped.
+ flushAsynchronousOperations();
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ });
+
+ test('send blocked when invalid email is supplied to ccs', () => {
+ const sendStub = sandbox.stub(element, 'send').returns(Promise.resolve());
+ // Stub the below function to avoid side effects from the send promise
+ // resolving.
+ sandbox.stub(element, '_purgeReviewersPendingRemove');
+
+ element.$$('#ccs').$.entry.setText('test');
+ MockInteractions.tap(element.$$('gr-button.send'));
+ assert.isFalse(sendStub.called);
+ flushAsynchronousOperations();
+
+ element.$$('#ccs').$.entry.setText('test@test.test');
+ MockInteractions.tap(element.$$('gr-button.send'));
+ assert.isTrue(sendStub.called);
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index 168e5b3..8218b43 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -136,6 +136,10 @@
type: String,
computed: '_computeSendButtonLabel(canBeStarted)',
},
+ _ccsEnabled: {
+ type: Boolean,
+ computed: '_computeCCsEnabled(serverConfig)',
+ },
},
FocusTarget,
@@ -472,7 +476,7 @@
}
}
- if (serverConfig.note_db_enabled) {
+ if (this._ccsEnabled) {
this._ccs = ccs;
} else {
this._ccs = [];
@@ -500,9 +504,7 @@
}
const key = this._accountOrGroupKey(entry);
- const finder = function(entry) {
- return this._accountOrGroupKey(entry) === key;
- }.bind(this);
+ const finder = entry => this._accountOrGroupKey(entry) === key;
return this._reviewers.find(finder) === undefined &&
this._ccs.find(finder) === undefined;
@@ -526,6 +528,11 @@
_saveTapHandler(e) {
e.preventDefault();
+ if (this._ccsEnabled && !this.$$('#ccs').submitEntryText()) {
+ // Do not proceed with the save if there is an invalid email entry in
+ // the text field of the CC entry.
+ return;
+ }
this.send(this._includeComments).then(keepReviewers => {
this._purgeReviewersPendingRemove(false, keepReviewers);
});
@@ -533,14 +540,17 @@
_sendTapHandler(e) {
e.preventDefault();
+ if (this._ccsEnabled && !this.$$('#ccs').submitEntryText()) {
+ // Do not proceed with the send if there is an invalid email entry in
+ // the text field of the CC entry.
+ return;
+ }
if (this.canBeStarted) {
- this._startReview()
- .then(() => {
- return this.send(this._includeComments);
- })
- .then(keepReviewers => {
- this._purgeReviewersPendingRemove(false, keepReviewers);
- });
+ this._startReview().then(() => {
+ return this.send(this._includeComments);
+ }).then(keepReviewers => {
+ this._purgeReviewersPendingRemove(false, keepReviewers);
+ });
return;
}
this.send(this._includeComments).then(keepReviewers => {
@@ -636,5 +646,9 @@
_computeSendButtonLabel(canBeStarted) {
return canBeStarted ? 'Start review' : 'Send';
},
+
+ _computeCCsEnabled(serverConfig) {
+ return serverConfig && serverConfig.note_db_enabled;
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index 673a11e..fcdb209 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -108,6 +108,20 @@
});
});
+ // Matches /admin/projects[,<offset>][/].
+ page(/^\/admin\/projects(,(\d+))?(\/)?$/, loadUser, data => {
+ restAPI.getLoggedIn().then(loggedIn => {
+ if (loggedIn) {
+ app.params = {
+ view: 'gr-admin-project-list',
+ offset: data.params[1] || 0,
+ };
+ } else {
+ page.redirect('/login/' + encodeURIComponent(data.canonicalPath));
+ }
+ });
+ });
+
page('/admin/(.*)', loadUser, data => {
restAPI.getLoggedIn().then(loggedIn => {
if (loggedIn) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
index ef00f55..e90dc41d 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
@@ -16,6 +16,7 @@
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
+<link rel="import" href="../../shared/gr-textarea/gr-textarea.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
<link rel="import" href="../../shared/gr-formatted-text/gr-formatted-text.html">
@@ -122,7 +123,6 @@
display: none;
}
.editing .editMessage {
- background-color: #fff;
display: block;
}
.show-hide {
@@ -168,7 +168,7 @@
}
#container.collapsed .actions,
#container.collapsed gr-formatted-text,
- #container.collapsed iron-autogrow-textarea {
+ #container.collapsed gr-textarea {
display: none;
}
.resolve,
@@ -233,14 +233,14 @@
[[comment.robot_id]]
</div>
</template>
- <iron-autogrow-textarea
+ <gr-textarea
id="editTextarea"
class="editMessage"
autocomplete="on"
disabled="{{disabled}}"
rows="4"
- bind-value="{{_messageText}}"
- on-keydown="_handleTextareaKeydown"></iron-autogrow-textarea>
+ text="{{_messageText}}"
+ on-keydown="_handleTextareaKeydown"></gr-textarea>
<gr-formatted-text class="message"
content="[[comment.message]]"
no-trailing-margin="[[!comment.__draft]]"
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
index ac8e299..82ebfbb 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
@@ -129,6 +129,7 @@
detached() {
this.cancelDebouncer('fire-update');
+ this.$.editTextarea.closeDropdown();
},
_computeShowHideText(collapsed) {
@@ -229,13 +230,7 @@
_editingChanged(editing, previousValue) {
this.$.container.classList.toggle('editing', editing);
if (editing) {
- const textarea = this.$.editTextarea.textarea;
- // Put the cursor at the end always.
- textarea.selectionStart = textarea.value.length;
- textarea.selectionEnd = textarea.selectionStart;
- this.async(() => {
- textarea.focus();
- });
+ this.$.editTextarea.putCursorAtEnd();
}
if (this.comment && this.comment.id) {
this.$$('.cancel').hidden = !editing;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
index ce3b233..4a1df77 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
@@ -79,7 +79,7 @@
'gr-formatted-text is not visible');
assert.isFalse(isVisible(element.$$('.actions')),
'actions are not visible');
- assert.isFalse(isVisible(element.$$('iron-autogrow-textarea')),
+ assert.isFalse(isVisible(element.$$('gr-textarea')),
'textarea is not visible');
// The header middle content is only visible when comments are collapsed.
@@ -94,7 +94,7 @@
'gr-formatted-text is visible');
assert.isTrue(isVisible(element.$$('.actions')),
'actions are visible');
- assert.isFalse(isVisible(element.$$('iron-autogrow-textarea')),
+ assert.isFalse(isVisible(element.$$('gr-textarea')),
'textarea is not visible');
assert.isFalse(isVisible(element.$$('.collapsedContent')),
'header middle content is not visible');
@@ -166,7 +166,7 @@
'gr-formatted-text is not visible');
assert.isFalse(isVisible(element.$$('.actions')),
'actions are not visible');
- assert.isFalse(isVisible(element.$$('iron-autogrow-textarea')),
+ assert.isFalse(isVisible(element.$$('gr-textarea')),
'textarea is not visible');
assert.isTrue(isVisible(element.$$('.collapsedContent')),
'header middle content is visible');
@@ -177,7 +177,7 @@
'gr-formatted-text is visible');
assert.isTrue(isVisible(element.$$('.actions')),
'actions are visible');
- assert.isFalse(isVisible(element.$$('iron-autogrow-textarea')),
+ assert.isFalse(isVisible(element.$$('gr-textarea')),
'textarea is not visible');
assert.isFalse(isVisible(element.$$('.collapsedContent')),
'header middle content is is not visible');
@@ -261,7 +261,7 @@
test('delete comment', done => {
sandbox.stub(
- element.$.restAPI, 'deleteComment').returns(Promise.resolve());
+ element.$.restAPI, 'deleteComment').returns(Promise.resolve({}));
sandbox.spy(element.$.overlay, 'open');
element.changeNum = 42;
element.patchNum = 0xDEADBEEF;
@@ -393,7 +393,7 @@
'gr-formatted-text is not visible');
assert.isFalse(isVisible(element.$$('.actions')),
'actions are not visible');
- assert.isFalse(isVisible(element.$$('iron-autogrow-textarea')),
+ assert.isFalse(isVisible(element.$$('gr-textarea')),
'textarea is not visible');
assert.isTrue(isVisible(element.$$('.collapsedContent')),
'header middle content is visible');
@@ -404,7 +404,7 @@
'gr-formatted-text is visible');
assert.isTrue(isVisible(element.$$('.actions')),
'actions are visible');
- assert.isFalse(isVisible(element.$$('iron-autogrow-textarea')),
+ assert.isFalse(isVisible(element.$$('gr-textarea')),
'textarea is not visible');
assert.isFalse(isVisible(element.$$('.collapsedContent')),
'header middle content is is not visible');
@@ -417,7 +417,7 @@
'gr-formatted-text is not visible');
assert.isTrue(isVisible(element.$$('.actions')),
'actions are visible');
- assert.isTrue(isVisible(element.$$('iron-autogrow-textarea')),
+ assert.isTrue(isVisible(element.$$('gr-textarea')),
'textarea is visible');
assert.isFalse(isVisible(element.$$('.collapsedContent')),
'header middle content is not visible');
@@ -430,7 +430,7 @@
'gr-formatted-text is not visible');
assert.isFalse(isVisible(element.$$('.actions')),
'actions are not visible');
- assert.isFalse(isVisible(element.$$('iron-autogrow-textarea')),
+ assert.isFalse(isVisible(element.$$('gr-textarea')),
'textarea is not visible');
assert.isTrue(isVisible(element.$$('.collapsedContent')),
'header middle content is visible');
@@ -442,7 +442,7 @@
'gr-formatted-text is not visible');
assert.isTrue(isVisible(element.$$('.actions')),
'actions are visible');
- assert.isTrue(isVisible(element.$$('iron-autogrow-textarea')),
+ assert.isTrue(isVisible(element.$$('gr-textarea')),
'textarea is visible');
assert.isFalse(isVisible(element.$$('.collapsedContent')),
'header middle content is not visible');
@@ -500,7 +500,7 @@
});
element._messageText = 'is that the horse from horsing around??';
MockInteractions.pressAndReleaseKeyOn(
- element.$.editTextarea.textarea,
+ element.$.editTextarea.$.textarea.textarea,
83, 'ctrl'); // 'ctrl + s'
});
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index 87a4687..0fe48fd 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -18,6 +18,7 @@
<link rel="import" href="../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
<link rel="import" href="../styles/app-theme.html">
+<link rel="import" href="./admin/gr-admin-project-list/gr-admin-project-list.html">
<link rel="import" href="./admin/gr-admin-view/gr-admin-view.html">
<link rel="import" href="./change-list/gr-change-list-view/gr-change-list-view.html">
<link rel="import" href="./change-list/gr-dashboard-view/gr-dashboard-view.html">
@@ -125,6 +126,11 @@
on-account-detail-update="_handleAccountDetailUpdate">
</gr-settings-view>
</template>
+ <template is="dom-if" if="[[_showProjectListView]]" restamp="true">
+ <gr-admin-project-list
+ params="[[params]]"
+ id="projectList"></gr-admin-project-list>
+ </template>
<template is="dom-if" if="[[_showAdminView]]" restamp="true">
<gr-admin-view path="[[_path]]"></gr-admin-view>
</template>
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index 945e010..846c186 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -48,6 +48,7 @@
_showChangeView: Boolean,
_showDiffView: Boolean,
_showSettingsView: Boolean,
+ _showProjectListView: Boolean,
_showAdminView: Boolean,
_showCLAView: Boolean,
_viewState: Object,
@@ -127,6 +128,7 @@
this.set('_showChangeView', view === 'gr-change-view');
this.set('_showDiffView', view === 'gr-diff-view');
this.set('_showSettingsView', view === 'gr-settings-view');
+ this.set('_showProjectListView', view === 'gr-admin-project-list');
this.set('_showAdminView', view === 'gr-admin-view');
this.set('_showCLAView', view === 'gr-cla-view');
if (this.params.justRegistered) {
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js
index 5702795..8be89c4 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js
@@ -30,6 +30,7 @@
*/
properties: {
+ index: Number,
moveToRoot: Boolean,
fixedPosition: Boolean,
suggestions: {
@@ -63,6 +64,8 @@
close() {
if (this.moveToRoot) {
Gerrit.getRootElement().removeChild(this);
+ } else {
+ this.hidden = true;
}
},
@@ -133,7 +136,9 @@
_handleEscape() {
this._fireClose();
- this.close();
+ if (!this.hidden) {
+ this.close();
+ }
},
_handleTapItem(e) {
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html
index 41468d1..c071f0c 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html
@@ -69,12 +69,14 @@
test('escape key', () => {
const listener = sandbox.spy();
+ element.hidden = false;
element.addEventListener('dropdown-closed', listener);
const closeSpy = sandbox.spy(element, 'close');
MockInteractions.pressAndReleaseKeyOn(element, 27);
flushAsynchronousOperations();
assert.isTrue(listener.called);
assert.isTrue(closeSpy.called);
+ assert.isTrue(element.hidden);
});
test('tab key', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
index afbce45..df4df05 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
@@ -49,7 +49,7 @@
on-item-selected="_handleItemSelect"
suggestions="[[_suggestions]]"
role="listbox"
- index="[[index]]"
+ index="[[_index]]"
hidden$="[[_computeSuggestionsHidden(_suggestions, _focused)]]">
</gr-autocomplete-dropdown>
</div>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
index 5c0535b..3cda49b 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
@@ -15,10 +15,10 @@
-->
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-rest-api-interface/gr-rest-api-interface.html">
<dom-module id="gr-js-api-interface">
- <template></template>
<script src="gr-change-actions-js-api.js"></script>
<script src="gr-change-reply-js-api.js"></script>
<script src="gr-js-api-interface.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
index c3013bd..40810b1 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
@@ -36,6 +36,8 @@
let plugin;
let errorStub;
let sandbox;
+ let getResponseObjectStub;
+ let sendStub;
const throwErrFn = function() {
throw Error('Unfortunately, this handler has stopped');
@@ -43,10 +45,16 @@
setup(() => {
sandbox = sinon.sandbox.create();
+ getResponseObjectStub = sandbox.stub().returns(Promise.resolve());
+ sendStub = sandbox.stub().returns(Promise.resolve());
stub('gr-rest-api-interface', {
getAccount() {
return Promise.resolve({name: 'Judy Hopps'});
},
+ getResponseObject: getResponseObjectStub,
+ send(...args) {
+ return sendStub(...args);
+ },
});
element = fixture('basic');
errorStub = sandbox.stub(console, 'error');
@@ -67,6 +75,27 @@
'http://test.com/plugins/testplugin/static/test.js');
});
+ test('get', done => {
+ const response = {foo: 'foo'};
+ getResponseObjectStub.returns(Promise.resolve(response));
+ plugin.get('/url', r => {
+ assert.isTrue(sendStub.calledWith('GET', '/url'));
+ assert.strictEqual(r, response);
+ done();
+ });
+ });
+
+ test('post', done => {
+ const payload = {foo: 'foo'};
+ const response = {bar: 'bar'};
+ getResponseObjectStub.returns(Promise.resolve(response));
+ plugin.post('/url', payload, r => {
+ assert.isTrue(sendStub.calledWith('POST', '/url', payload));
+ assert.strictEqual(r, response);
+ done();
+ });
+ });
+
test('history event', done => {
plugin.on(element.EventType.HISTORY, throwErrFn);
plugin.on(element.EventType.HISTORY, path => {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
index a84eedd..0811935 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
@@ -24,6 +24,14 @@
GWT_PLUGIN_STUB[name] = warnNotSupported.bind(null, name);
}
+ let _restAPI;
+ const getRestAPI = () => {
+ if (!_restAPI) {
+ _restAPI = document.createElement('gr-rest-api-interface');
+ }
+ return _restAPI;
+ };
+
const API_VERSION = '0.1';
// GWT JSNI uses $wnd to refer to window.
@@ -77,6 +85,20 @@
return this._url.origin + '/plugins/' + this._name + (opt_path || '/');
};
+ Plugin.prototype._send = function(method, url, callback, opt_payload) {
+ return getRestAPI().send(method, url, opt_payload)
+ .then(getRestAPI().getResponseObject)
+ .then(callback);
+ };
+
+ Plugin.prototype.get = function(url, callback) {
+ return this._send('GET', url, callback);
+ },
+
+ Plugin.prototype.post = function(url, payload, callback) {
+ return this._send('POST', url, callback, payload);
+ },
+
Plugin.prototype.changeActions = function() {
return new GrChangeActionsInterface(Plugin._sharedAPIElement.getElement(
Plugin._sharedAPIElement.Element.CHANGE_ACTIONS));
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index 43e2cf1..e371201 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -559,6 +559,13 @@
});
},
+ getProjects(projectsPerPage, opt_offset) {
+ const offset = opt_offset || 0;
+ return this._fetchSharedCacheURL(
+ `/projects/?d&n=${projectsPerPage + 1}&S=${offset}`
+ );
+ },
+
getSuggestedGroups(inputVal, opt_n, opt_errFn, opt_ctx) {
const params = {s: inputVal};
if (opt_n) { params.n = opt_n; }
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html
index 51e349b..8d0b1e3 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html
@@ -38,8 +38,11 @@
background-color: var(--background-color, none);
width: 100%;
}
+ /*This is needed to not add a scroll bar on the side of gr-textarea
+ since there is 2px of padding in iron-autogrow-textarea for the
+ native textarea*/
iron-autogrow-textarea {
- padding: 0;
+ padding: 2px;
}
#textarea.noBorder {
border: none;
@@ -54,8 +57,7 @@
</style>
<gr-autocomplete-dropdown id="emojiSuggestions"
suggestions="[[_suggestions]]"
- index="[[index]]"
- position="[[position]]"
+ index="[[_index]]"
move-to-root
fixed-position="[[fixedPositionDropdown]]"
hidden>
@@ -72,4 +74,4 @@
on-bind-value-changed="_onValueChanged"></iron-autogrow-textarea>
</template>
<script src="gr-textarea.js"></script>
-</dom-module>
\ No newline at end of file
+</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js
index 6dbe47e..9158a57 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js
@@ -133,6 +133,20 @@
}
},
+ getNativeTextarea() {
+ return this.$.textarea.textarea;
+ },
+
+ putCursorAtEnd() {
+ const textarea = this.getNativeTextarea();
+ // Put the cursor at the end always.
+ textarea.selectionStart = textarea.value.length;
+ textarea.selectionEnd = textarea.selectionStart;
+ this.async(() => {
+ textarea.focus();
+ });
+ },
+
_handleEscKey(e) {
if (this._hideAutocomplete) { return; }
e.preventDefault();
@@ -192,6 +206,10 @@
10);
},
+ _getScrollTop() {
+ return document.body.scrollTop;
+ },
+
/**
* This positions the dropdown to be just below the cursor position. It is
* calculated by having a hidden element with the same width and styling of
@@ -205,7 +223,12 @@
const caratPosition = this._getPositionOfCursor();
const fontSize = this._getFontSize();
- const top = caratPosition.top + fontSize + VERTICAL_OFFSET + 'px';
+ let top = caratPosition.top + fontSize + VERTICAL_OFFSET;
+
+ if (!this.fixedPositionDropdown) {
+ top += this._getScrollTop();
+ }
+ top += 'px';
const left = caratPosition.left + 'px';
this.$.emojiSuggestions.setPosition(top, left);
},
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html
index c92a479..1c5dea3 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html
@@ -181,9 +181,12 @@
sandbox.stub(element, '_getPositionOfCursor', () => {
return {top: 100, left: 30};
});
- sandbox.stub(element, '_getFontSize', () => {
- return 12;
- });
+ sandbox.stub(element, '_getFontSize', () => 12);
+ sandbox.stub(element, '_getScrollTop', () => 100);
+ element._updateSelectorPosition();
+ assert.isTrue(setPositionSpy.lastCall.calledWithExactly('219px', '30px'));
+
+ element.fixedPositionDropdown = true;
element._updateSelectorPosition();
assert.isTrue(setPositionSpy.lastCall.calledWithExactly('119px', '30px'));
});
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index a44cf27..9a2e404 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -30,6 +30,7 @@
// This seemed to be flakey when it was farther down the list. Keep at the
// beginning.
'gr-app_test.html',
+ 'admin/gr-admin-project-list/gr-admin-project-list_test.html',
'change-list/gr-change-list-item/gr-change-list-item_test.html',
'change-list/gr-change-list-view/gr-change-list-view_test.html',
'change-list/gr-change-list/gr-change-list_test.html',
@@ -51,6 +52,7 @@
'change/gr-messages-list/gr-messages-list_test.html',
'change/gr-related-changes-list/gr-related-changes-list_test.html',
'change/gr-reply-dialog/gr-reply-dialog_test.html',
+ 'change/gr-reply-dialog/gr-reply-dialog-it_test.html',
'change/gr-reviewer-list/gr-reviewer-list_test.html',
'core/gr-account-dropdown/gr-account-dropdown_test.html',
'core/gr-error-manager/gr-error-manager_test.html',
@@ -123,6 +125,7 @@
// Behaviors tests.
const behaviors = [
'base-url-behavior/base-url-behavior_test.html',
+ 'keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html',
'rest-client-behavior/rest-client-behavior_test.html',
'gr-change-table-behavior/gr-change-table-behavior_test.html',
'gr-patch-set-behavior/gr-patch-set-behavior_test.html',