Open reply dialog when "Add reviewer" is tapped

The account entry for the change's reviewers will automatically focus
when the dialog opens in this case. Opening the reply dialog using
the "Reply" button should continue to focus on the main textarea.

Change-Id: I993d96633a6349a14bdb10354b8949bf35240352
This commit is contained in:
Logan Hanks
2016-07-20 13:22:51 -07:00
parent f2b1ce5356
commit add2bce42d
6 changed files with 40 additions and 219 deletions

View File

@@ -251,7 +251,9 @@ limitations under the License.
change="{{_change}}"
commit-info="[[_commitInfo]]"
server-config="[[serverConfig]]"
mutable="[[_loggedIn]]"></gr-change-metadata>
mutable="[[_loggedIn]]"
on-show-reply-dialog="_handleShowReplyDialog">
</gr-change-metadata>
<gr-change-actions id="actions"
actions="[[_change.actions]]"
change-num="[[_changeNum]]"

View File

@@ -316,6 +316,10 @@
this.$.replyOverlay.close();
},
_handleShowReplyDialog: function(e) {
this._openReplyDialog(this.$.replyDialog.FocusTarget.REVIEWERS);
},
_paramsChanged: function(value) {
if (value.view !== this.tagName.toLowerCase()) { return; }
@@ -502,9 +506,10 @@
});
},
_openReplyDialog: function() {
_openReplyDialog: function(opt_section) {
this.$.replyOverlay.open().then(function() {
this.$.replyOverlay.setFocusStops(this.$.replyDialog.getFocusStops());
this.$.replyDialog.focusOn(opt_section);
}.bind(this));
},

View File

@@ -14,6 +14,11 @@
(function() {
'use strict';
var FocusTarget = {
BODY: 'body',
REVIEWERS: 'reviewers',
};
Polymer({
is: 'gr-reply-dialog',
@@ -51,6 +56,8 @@
_reviewers: Array,
},
FocusTarget: FocusTarget,
behaviors: [
Gerrit.RESTClientBehavior,
],
@@ -70,9 +77,17 @@
},
focus: function() {
this.async(function() {
this.$.textarea.textarea.focus();
}.bind(this));
this.focusOn(FocusTarget.BODY);
},
focusOn: function(section) {
if (section === FocusTarget.BODY) {
var textarea = this.$.textarea;
textarea.async(textarea.textarea.focus.bind(textarea.textarea));
} else if (section === FocusTarget.REVIEWERS) {
var reviewerEntry = this.$.reviewers.focusStart;
reviewerEntry.async(reviewerEntry.focus);
}
},
getFocusStops: function() {

View File

@@ -19,7 +19,6 @@ limitations under the License.
<link rel="import" href="../../shared/gr-account-chip/gr-account-chip.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-account-entry/gr-account-entry.html">
<dom-module id="gr-reviewer-list">
<template>
@@ -45,19 +44,12 @@ limitations under the License.
gr-account-chip {
margin-top: .3em;
}
.remove,
.cancel {
.remove {
color: #999;
}
.remove {
font-size: .9em;
}
.cancel {
font-size: 2em;
line-height: 1;
padding: 0 .15em;
text-decoration: none;
}
@media screen and (max-width: 50em), screen and (min-width: 75em) {
gr-account-chip:first-of-type {
margin-top: 0;
@@ -72,28 +64,11 @@ limitations under the License.
</gr-account-chip>
</template>
<div class="controlsContainer" hidden$="[[!mutable]]">
<div class="autocompleteContainer" hidden$="[[!_showInput]]">
<div class="inputContainer">
<gr-account-entry
id="accountEntry"
suggestFrom="[[suggestFrom]]"
clear-on-commit
change="[[change]]"
disabled="[[disabled]]"
on-commit="_sendAddRequest"
on-cancel="_handleCancelTap"></gr-account-entry>
<gr-button
link
class="cancel"
on-tap="_handleCancelTap">×</gr-button>
</div>
</div>
<gr-button
link
id="addReviewer"
class="addReviewer"
on-tap="_handleAddTap"
hidden$="[[_showInput]]">Add reviewer</gr-button>
on-tap="_handleAddTap">Add reviewer</gr-button>
</div>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>

View File

@@ -17,6 +17,12 @@
Polymer({
is: 'gr-reviewer-list',
/**
* Fired when the "Add reviewer..." button is tapped.
*
* @event show-reply-dialog
*/
properties: {
change: Object,
disabled: {
@@ -104,52 +110,7 @@
_handleAddTap: function(e) {
e.preventDefault();
this._showInput = true;
this.$.accountEntry.focus();
},
_handleCancelTap: function(e) {
e.preventDefault();
this.$.accountEntry.clear();
this._cancel();
},
_cancel: function() {
this._showInput = false;
this.$.accountEntry.clear();
this.$.addReviewer.focus();
},
_sendAddRequest: function(e, detail) {
var reviewer = detail.value;
var reviewerID;
if (reviewer.account) {
reviewerID = reviewer.account._account_id;
} else if (reviewer.group) {
reviewerID = reviewer.group.id;
}
this.disabled = true;
this._xhrPromise = this._addReviewer(reviewerID).then(function(response) {
this.change.reviewers.CC = this.change.reviewers.CC || [];
this.disabled = false;
if (!response.ok) { return response; }
return this.$.restAPI.getResponseObject(response).then(function(obj) {
obj.reviewers.forEach(function(r) {
this.push('change.removable_reviewers', r);
this.push('change.reviewers.CC', r);
}, this);
this.$.accountEntry.focus();
}.bind(this));
}.bind(this)).catch(function(err) {
this.disabled = false;
throw err;
}.bind(this));
},
_addReviewer: function(id) {
return this.$.restAPI.addChangeReviewer(this.change._number, id);
this.fire('show-reply-dialog');
},
_removeReviewer: function(id) {

View File

@@ -34,57 +34,10 @@ limitations under the License.
<script>
suite('gr-reviewer-list tests', function() {
var element;
var autocompleteInput;
setup(function() {
element = fixture('basic');
// TODO(logan): Rewrite this test to not delve so deeply into internals.
autocompleteInput = element.$.accountEntry.$.input.$.input;
stub('gr-rest-api-interface', {
getChangeSuggestedReviewers: function() {
return Promise.resolve([
{
account: {
_account_id: 1021482,
name: 'Andrew Bonventre',
email: 'andybons@chromium.org',
}
},
{
account: {
_account_id: 1021863,
name: 'Andrew Bonventre',
email: 'andybons@google.com',
}
},
{
group: {
id: 'c7af6dd375c092ff3b23c0937aa910693dc0c41b',
name: 'andy',
}
}
]);
},
addChangeReviewer: function() {
return Promise.resolve({
ok: true,
text: function() {
return Promise.resolve(
')]}\'\n' +
JSON.stringify({
reviewers: [{
_account_id: 1021482,
approvals: {
'Code-Review': ' 0'
},
email: 'andybons@chromium.org',
name: 'Andrew Bonventre',
}]
})
);
},
});
},
removeChangeReviewer: function() {
return Promise.resolve({ok: true});
},
@@ -98,37 +51,11 @@ limitations under the License.
assert.isFalse(element.$$('.controlsContainer').hasAttribute('hidden'));
});
function getActiveElement() {
var root = document;
while (root && root.activeElement.shadowRoot) {
var shadowRoot = root.activeElement.shadowRoot;
if (!shadowRoot.activeElement) {
break;
}
root = shadowRoot;
}
return root.activeElement;
}
test('show/hide accountEntry', function() {
element.mutable = true;
assert.isFalse(element.$$('.addReviewer').hasAttribute('hidden'));
assert.isTrue(
element.$$('.autocompleteContainer').hasAttribute('hidden'));
assert.notEqual(getActiveElement().id, 'input');
test('add reviewer button opens reply dialog', function(done) {
element.addEventListener('show-reply-dialog', function() {
done();
});
MockInteractions.tap(element.$$('.addReviewer'));
assert.isTrue(element.$$('.addReviewer').hasAttribute('hidden'));
assert.isFalse(
element.$$('.autocompleteContainer').hasAttribute('hidden'));
assert.equal(getActiveElement().id, 'input');
MockInteractions.pressAndReleaseKeyOn(autocompleteInput, 27); // 'esc'
assert.isFalse(element.$$('.addReviewer').hasAttribute('hidden'));
assert.isTrue(
element.$$('.autocompleteContainer').hasAttribute('hidden'));
assert.equal(getActiveElement().id, 'addReviewer');
});
test('only show remove for removable reviewers', function() {
@@ -186,69 +113,5 @@ limitations under the License.
}
});
});
test('autocomplete starts at >= 3 chars', function() {
element._inputRequestTimeout = 0;
element._mutable = true;
element.change = {_number: 123};
element.$.accountEntry.text = 'fo';
flushAsynchronousOperations();
assert.isFalse(element.$.restAPI.getChangeSuggestedReviewers.called);
});
test('add/remove reviewer flow', function(done) {
element.change = {
_number: 42,
reviewers: {},
removable_reviewers: [],
owner: {_account_id: 0},
};
element._inputRequestTimeout = 0;
element._mutable = true;
MockInteractions.tap(element.$$('.addReviewer'));
flushAsynchronousOperations();
element.$.accountEntry.$.input.text = 'andy';
var stub = element.$.restAPI.getChangeSuggestedReviewers;
stub.lastCall.returnValue.then(function() {
flushAsynchronousOperations();
MockInteractions.pressAndReleaseKeyOn(autocompleteInput, 27); // 'esc'
assert.isTrue(element.$$('.autocompleteContainer')
.hasAttribute('hidden'));
MockInteractions.tap(element.$$('.addReviewer'));
element.$.accountEntry.text = 'andyb';
stub.lastCall.returnValue.then(function() {
MockInteractions.pressAndReleaseKeyOn(
autocompleteInput, 13); // 'enter'
assert.isTrue(element.disabled);
element._xhrPromise.then(function() {
assert.isFalse(element.disabled);
flushAsynchronousOperations();
var reviewerEls =
Polymer.dom(element.root).querySelectorAll('.reviewer');
assert.equal(reviewerEls.length, 1);
MockInteractions.tap(element.$$('.reviewer').$$('gr-button'));
flushAsynchronousOperations();
assert.isTrue(element.disabled);
element._xhrPromise.then(function() {
flushAsynchronousOperations();
assert.isFalse(element.disabled);
var reviewerEls =
Polymer.dom(element.root).querySelectorAll('.reviewer');
assert.equal(reviewerEls.length, 0);
done();
});
});
});
});
});
});
</script>