Cleanup: use iron-a11y-keys-behavior for keyboard shortcuts

+ This does not cover on-keydown handlers within elements.
  A follow-up change will account for those.
+ Keyboard shortcuts are disabled within gr-overlay, input,
  and textarea elements.
+ Added tests for new behavior (plus some missing ones covering
  broken behavior).
+ Removed blur hacks used on elements to placate the kb
  shortcuts due to restrictions that have been removed.

Bug: Issue 4198
Change-Id: Ide8009a3bfc340a35a8ec8b9189a85b49c8a95aa
This commit is contained in:
Andrew Bonventre
2016-11-15 17:01:15 -08:00
parent 7959720c79
commit 4d22c7e835
27 changed files with 561 additions and 511 deletions

View File

@@ -14,69 +14,30 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<link rel="import" href="../bower_components/polymer/polymer.html"> <link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
<script> <script>
(function(window) { (function(window) {
'use strict'; 'use strict';
/** @polymerBehavior Gerrit.KeyboardShortcutBehavior */ var KeyboardShortcutBehaviorImpl = {
var KeyboardShortcutBehavior = {
// Set of identifiers currently blocking keyboard shortcuts. Stored as
// a map of string to the value of true.
_disablers: {},
properties: {
keyEventTarget: {
type: Object,
value: function() { return this; },
},
_boundKeyHandler: {
type: Function,
readonly: true,
value: function() { return this._handleKey.bind(this); },
},
},
attached: function() {
this.keyEventTarget.addEventListener('keydown', this._boundKeyHandler);
},
detached: function() {
this.keyEventTarget.removeEventListener('keydown', this._boundKeyHandler);
},
shouldSuppressKeyboardShortcut: function(e) { shouldSuppressKeyboardShortcut: function(e) {
for (var c in KeyboardShortcutBehavior._disablers) { e = Polymer.dom(e.detail ? e.detail.keyboardEvent : e);
if (KeyboardShortcutBehavior._disablers[c] === true) { if (e.path[0].tagName === 'INPUT' || e.path[0].tagName === 'TEXTAREA') {
return true; return true;
} }
for (var i = 0; i < e.path.length; i++) {
if (e.path[i].tagName === 'GR-OVERLAY') { return true; }
} }
var getModifierState = e.getModifierState ? return false;
e.getModifierState.bind(e) :
function() { return false; };
var target = e.detail ? e.detail.keyboardEvent : e.target;
return getModifierState('Control') ||
getModifierState('Alt') ||
getModifierState('Meta') ||
getModifierState('Fn') ||
target.tagName == 'INPUT' ||
target.tagName == 'TEXTAREA' ||
target.tagName == 'SELECT' ||
target.tagName == 'BUTTON' ||
target.tagName == 'A' ||
target.tagName == 'GR-BUTTON';
},
disable: function(id) {
KeyboardShortcutBehavior._disablers[id] = true;
},
enable: function(id) {
delete KeyboardShortcutBehavior._disablers[id];
}, },
}; };
window.Gerrit = window.Gerrit || {}; window.Gerrit = window.Gerrit || {};
window.Gerrit.KeyboardShortcutBehavior = KeyboardShortcutBehavior; /** @polymerBehavior Gerrit.KeyboardShortcutBehavior */
window.Gerrit.KeyboardShortcutBehavior = [
Polymer.IronA11yKeysBehavior,
KeyboardShortcutBehaviorImpl,
];
})(window); })(window);
</script> </script>

View File

@@ -30,49 +30,76 @@ limitations under the License.
</template> </template>
</test-fixture> </test-fixture>
<test-fixture id="within-overlay">
<template>
<gr-overlay>
<test-element></test-element>
</gr-overlay>
</template>
</test-fixture>
<script> <script>
suite('keyboard-shortcut-behavior tests', function() { suite('keyboard-shortcut-behavior tests', function() {
var element; var element;
var overlay;
suiteSetup(function() { suiteSetup(function() {
// Define a Polymer element that uses this behavior. // Define a Polymer element that uses this behavior.
Polymer({ Polymer({
is: 'test-element', is: 'test-element',
behaviors: [Gerrit.KeyboardShortcutBehavior], behaviors: [Gerrit.KeyboardShortcutBehavior],
properties: { keyBindings: {
keyEventTarget: { 'k': '_handleKey'
value: function() { return document.body; },
},
log: {
value: function() { return []; },
},
},
_handleKey: function(e) {
if (!this.shouldSuppressKeyboardShortcut(e)) {
this.log.push(e.keyCode);
}
}, },
_handleKey: function() {},
}); });
}); });
setup(function() { setup(function() {
element = fixture('basic'); element = fixture('basic');
overlay = fixture('within-overlay');
}); });
test('blocks keydown events iff one or more disablers', function() { test('doesnt block kb shortcuts for non-whitelisted els', function(done) {
MockInteractions.pressAndReleaseKeyOn(document.body, 97); // 'a' var divEl = document.createElement('div');
Gerrit.KeyboardShortcutBehavior.enable('x'); // should have no effect element.appendChild(divEl);
MockInteractions.pressAndReleaseKeyOn(document.body, 98); // 'b' element._handleKey = function(e) {
Gerrit.KeyboardShortcutBehavior.disable('x'); // blocking starts here assert.isFalse(element.shouldSuppressKeyboardShortcut(e));
MockInteractions.pressAndReleaseKeyOn(document.body, 99); // 'c' done();
Gerrit.KeyboardShortcutBehavior.disable('y'); };
MockInteractions.pressAndReleaseKeyOn(document.body, 100); // 'd' MockInteractions.keyDownOn(divEl, 75, null, 'k');
Gerrit.KeyboardShortcutBehavior.enable('x');
MockInteractions.pressAndReleaseKeyOn(document.body, 101); // 'e'
Gerrit.KeyboardShortcutBehavior.enable('y'); // blocking ends here
MockInteractions.pressAndReleaseKeyOn(document.body, 102); // 'f'
assert.deepEqual(element.log, [97, 98, 102]);
}); });
test('blocks kb shortcuts for input els', function(done) {
var inputEl = document.createElement('input');
element.appendChild(inputEl);
element._handleKey = function(e) {
assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
done();
};
MockInteractions.keyDownOn(inputEl, 75, null, 'k');
});
test('blocks kb shortcuts for textarea els', function(done) {
var textareaEl = document.createElement('textarea');
element.appendChild(textareaEl);
element._handleKey = function(e) {
assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
done();
};
MockInteractions.keyDownOn(textareaEl, 75, null, 'k');
});
test('blocks kb shortcuts for anything in a gr-overlay', function(done) {
var divEl = document.createElement('div');
var element = overlay.querySelector('test-element');
element.appendChild(divEl);
element._handleKey = function(e) {
assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
done();
};
MockInteractions.keyDownOn(divEl, 75, null, 'k');
});
}); });
</script> </script>

View File

@@ -78,6 +78,12 @@
Gerrit.RESTClientBehavior, Gerrit.RESTClientBehavior,
], ],
keyBindings: {
'j': '_handleJKey',
'k': '_handleKKey',
'o enter': '_handleEnterKey',
},
attached: function() { attached: function() {
this._loadPreferences(); this._loadPreferences();
}, },
@@ -149,31 +155,37 @@
account._account_id != change.owner._account_id; account._account_id != change.owner._account_id;
}, },
_handleKey: function(e) { _getAggregateGroupsLen: function(groups) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; } groups = groups || [];
if (this.groups == null) { return; }
var len = 0; var len = 0;
this.groups.forEach(function(group) { this.groups.forEach(function(group) {
len += group.length; len += group.length;
}); });
switch (e.keyCode) { return len;
case 74: // 'j' },
_handleJKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault(); e.preventDefault();
if (this.selectedIndex == len - 1) { return; } var len = this._getAggregateGroupsLen(this.groups);
if (this.selectedIndex === len - 1) { return; }
this.selectedIndex += 1; this.selectedIndex += 1;
break; },
case 75: // 'k'
_handleKKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault(); e.preventDefault();
if (this.selectedIndex == 0) { return; } if (this.selectedIndex === 0) { return; }
this.selectedIndex -= 1; this.selectedIndex -= 1;
break; },
case 79: // 'o'
case 13: // 'enter' _handleEnterKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault(); e.preventDefault();
page.show(this._changeURLForIndex(this.selectedIndex)); page.show(this._changeURLForIndex(this.selectedIndex));
break;
}
}, },
_changeURLForIndex: function(index) { _changeURLForIndex: function(index) {

View File

@@ -131,25 +131,25 @@ limitations under the License.
flush(function() { flush(function() {
assert.isTrue(elementItems[0].selected); assert.isTrue(elementItems[0].selected);
MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j' MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
assert.equal(element.selectedIndex, 1); assert.equal(element.selectedIndex, 1);
MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j' MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
var showStub = sinon.stub(page, 'show'); var showStub = sinon.stub(page, 'show');
assert.equal(element.selectedIndex, 2); assert.equal(element.selectedIndex, 2);
MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter' MockInteractions.pressAndReleaseKeyOn(element, 13, null, 'enter');
assert(showStub.lastCall.calledWithExactly('/c/2/'), assert(showStub.lastCall.calledWithExactly('/c/2/'),
'Should navigate to /c/2/'); 'Should navigate to /c/2/');
MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k' MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
assert.equal(element.selectedIndex, 1); assert.equal(element.selectedIndex, 1);
MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter' MockInteractions.pressAndReleaseKeyOn(element, 13, null, 'enter');
assert(showStub.lastCall.calledWithExactly('/c/1/'), assert(showStub.lastCall.calledWithExactly('/c/1/'),
'Should navigate to /c/1/'); 'Should navigate to /c/1/');
MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k' MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k' MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k' MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
assert.equal(element.selectedIndex, 0); assert.equal(element.selectedIndex, 0);
showStub.restore(); showStub.restore();

View File

@@ -110,6 +110,13 @@
'_paramsAndChangeChanged(params, _change)', '_paramsAndChangeChanged(params, _change)',
], ],
keyBindings: {
'shift+r': '_handleCapitalRKey',
'a': '_handleAKey',
'd': '_handleDKey',
'u': '_handleUKey',
},
attached: function() { attached: function() {
this._getLoggedIn().then(function(loggedIn) { this._getLoggedIn().then(function(loggedIn) {
this._loggedIn = loggedIn; this._loggedIn = loggedIn;
@@ -579,30 +586,29 @@
}.bind(this)); }.bind(this));
}, },
_handleKey: function(e) { _handleAKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; } if (this.shouldSuppressKeyboardShortcut(e)) { return; }
switch (e.keyCode) { if (!this._loggedIn || e.detail.keyboardEvent.shiftKey) { return; }
case 65: // 'a'
if (this._loggedIn && !e.shiftKey) {
e.preventDefault(); e.preventDefault();
this._openReplyDialog(); this._openReplyDialog();
} },
break;
case 68: // 'd' _handleDKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault(); e.preventDefault();
this.$.downloadOverlay.open(); this.$.downloadOverlay.open();
break; },
case 82: // 'r'
if (e.shiftKey) { _handleCapitalRKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault(); e.preventDefault();
this._switchToMostRecentPatchNum(); this._switchToMostRecentPatchNum();
} },
break;
case 85: // 'u' _handleUKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault(); e.preventDefault();
this._determinePageBack(); this._determinePageBack();
break;
}
}, },
_determinePageBack: function() { _determinePageBack: function() {

View File

@@ -53,27 +53,27 @@ limitations under the License.
suite('keyboard shortcuts', function() { suite('keyboard shortcuts', function() {
test('U should navigate to / if no backPage set', function() { test('U should navigate to / if no backPage set', function() {
var showStub = sandbox.stub(page, 'show'); var showStub = sandbox.stub(page, 'show');
MockInteractions.pressAndReleaseKeyOn(element, 85); // 'U' MockInteractions.pressAndReleaseKeyOn(element, 85, null, 'u');
assert(showStub.lastCall.calledWithExactly('/')); assert(showStub.lastCall.calledWithExactly('/'));
}); });
test('U should navigate to backPage if set', function() { test('U should navigate to backPage if set', function() {
element.backPage = '/dashboard/self'; element.backPage = '/dashboard/self';
var showStub = sandbox.stub(page, 'show'); var showStub = sandbox.stub(page, 'show');
MockInteractions.pressAndReleaseKeyOn(element, 85); // 'U' MockInteractions.pressAndReleaseKeyOn(element, 85, null, 'u');
assert(showStub.lastCall.calledWithExactly('/dashboard/self')); assert(showStub.lastCall.calledWithExactly('/dashboard/self'));
}); });
test('A should toggle overlay', function() { test('A should toggle overlay', function() {
MockInteractions.pressAndReleaseKeyOn(element, 65); // 'A' MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
var overlayEl = element.$.replyOverlay; var overlayEl = element.$.replyOverlay;
assert.isFalse(overlayEl.opened); assert.isFalse(overlayEl.opened);
element._loggedIn = true; element._loggedIn = true;
MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift'); // 'A' MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift', 'a');
assert.isFalse(overlayEl.opened); assert.isFalse(overlayEl.opened);
MockInteractions.pressAndReleaseKeyOn(element, 65); // 'A' MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
assert.isTrue(overlayEl.opened); assert.isTrue(overlayEl.opened);
overlayEl.close(); overlayEl.close();
assert.isFalse(overlayEl.opened); assert.isFalse(overlayEl.opened);
@@ -117,13 +117,12 @@ limitations under the License.
done(); done();
}); });
// 'shift + R' MockInteractions.pressAndReleaseKeyOn(element, 82, 'shift', 'r');
MockInteractions.pressAndReleaseKeyOn(element, 82, 'shift');
}); });
test('d should open download overlay', function() { test('d should open download overlay', function() {
var stub = sandbox.stub(element.$.downloadOverlay, 'open'); var stub = sandbox.stub(element.$.downloadOverlay, 'open');
MockInteractions.pressAndReleaseKeyOn(element, 68); // 'd' MockInteractions.pressAndReleaseKeyOn(element, 68, null, 'd');
assert.isTrue(stub.called); assert.isTrue(stub.called);
}); });
}); });

View File

@@ -179,8 +179,7 @@ limitations under the License.
<select <select
id="modeSelect" id="modeSelect"
is="gr-select" is="gr-select"
bind-value="{{diffViewMode}}" bind-value="{{diffViewMode}}">
on-change="_handleDropdownChange">
<option value="SIDE_BY_SIDE">Side By Side</option> <option value="SIDE_BY_SIDE">Side By Side</option>
<option value="UNIFIED_DIFF">Unified</option> <option value="UNIFIED_DIFF">Unified</option>
</select> </select>

View File

@@ -106,6 +106,22 @@
Gerrit.URLEncodingBehavior, Gerrit.URLEncodingBehavior,
], ],
keyBindings: {
'shift+left': '_handleShiftLeftKey',
'shift+right': '_handleShiftRightKey',
'i': '_handleIKey',
'shift+i': '_handleCapitalIKey',
'down j': '_handleDownKey',
'up k': '_handleUpKey',
'c': '_handleCKey',
'[': '_handleLeftBracketKey',
']': '_handleRightBracketKey',
'o enter': '_handleEnterKey',
'n': '_handleNKey',
'p': '_handlePKey',
'shift+a': '_handleCapitalAKey',
},
reload: function() { reload: function() {
if (!this.changeNum || !this.patchRange.patchNum) { if (!this.changeNum || !this.patchRange.patchNum) {
return Promise.resolve(); return Promise.resolve();
@@ -216,9 +232,6 @@
this.set(['_shownFiles', i, '__expanded'], true); this.set(['_shownFiles', i, '__expanded'], true);
this.set(['_files', i, '__expanded'], true); this.set(['_files', i, '__expanded'], true);
} }
if (e && e.target) {
e.target.blur();
}
}, },
_collapseAllDiffs: function(e) { _collapseAllDiffs: function(e) {
@@ -228,9 +241,6 @@
this.set(['_files', i, '__expanded'], false); this.set(['_files', i, '__expanded'], false);
} }
this.$.cursor.handleDiffUpdate(); this.$.cursor.handleDiffUpdate();
if (e && e.target) {
e.target.blur();
}
}, },
_computeCommentsString: function(comments, patchNum, path) { _computeCommentsString: function(comments, patchNum, path) {
@@ -298,26 +308,26 @@
}); });
}, },
_handleKey: function(e) { _handleShiftLeftKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; } if (this.shouldSuppressKeyboardShortcut(e)) { return; }
switch (e.keyCode) { if (!this._showInlineDiffs) { return; }
case 37: // left
if (e.shiftKey && this._showInlineDiffs) {
e.preventDefault(); e.preventDefault();
this.$.cursor.moveLeft(); this.$.cursor.moveLeft();
} },
break;
case 39: // right _handleShiftRightKey: function(e) {
if (e.shiftKey && this._showInlineDiffs) { if (this.shouldSuppressKeyboardShortcut(e)) { return; }
if (!this._showInlineDiffs) { return; }
e.preventDefault(); e.preventDefault();
this.$.cursor.moveRight(); this.$.cursor.moveRight();
} },
break;
case 73: // 'i' _handleIKey: function(e) {
if (e.shiftKey) { if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault(); if (this.selectedIndex === undefined) { return; }
this._toggleInlineDiffs();
} else if (this.selectedIndex !== undefined) {
e.preventDefault(); e.preventDefault();
var expanded = this._files[this.selectedIndex].__expanded; var expanded = this._files[this.selectedIndex].__expanded;
// Until Polymer 2.0, manual management of reflection between _files // Until Polymer 2.0, manual management of reflection between _files
@@ -325,10 +335,18 @@
this.set(['_shownFiles', this.selectedIndex, '__expanded'], this.set(['_shownFiles', this.selectedIndex, '__expanded'],
!expanded); !expanded);
this.set(['_files', this.selectedIndex, '__expanded'], !expanded); this.set(['_files', this.selectedIndex, '__expanded'], !expanded);
} },
break;
case 40: // down _handleCapitalIKey: function(e) {
case 74: // 'j' if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault();
this._toggleInlineDiffs();
},
_handleDownKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault(); e.preventDefault();
if (this._showInlineDiffs) { if (this._showInlineDiffs) {
this.$.cursor.moveDown(); this.$.cursor.moveDown();
@@ -337,9 +355,11 @@
Math.min(this._numFilesShown, this.selectedIndex + 1); Math.min(this._numFilesShown, this.selectedIndex + 1);
this._scrollToSelectedFile(); this._scrollToSelectedFile();
} }
break; },
case 38: // up
case 75: // 'k' _handleUpKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault(); e.preventDefault();
if (this._showInlineDiffs) { if (this._showInlineDiffs) {
this.$.cursor.moveUp(); this.$.cursor.moveUp();
@@ -347,8 +367,11 @@
this.selectedIndex = Math.max(0, this.selectedIndex - 1); this.selectedIndex = Math.max(0, this.selectedIndex - 1);
this._scrollToSelectedFile(); this._scrollToSelectedFile();
} }
break; },
case 67: // 'c'
_handleCKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
var isRangeSelected = this.diffs.some(function(diff) { var isRangeSelected = this.diffs.some(function(diff) {
return diff.isRangeSelected(); return diff.isRangeSelected();
}, this); }, this);
@@ -356,53 +379,64 @@
e.preventDefault(); e.preventDefault();
this._addDraftAtTarget(); this._addDraftAtTarget();
} }
break; },
case 219: // '['
_handleLeftBracketKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault(); e.preventDefault();
this._openSelectedFile(this._files.length - 1); this._openSelectedFile(this._files.length - 1);
break; },
case 221: // ']'
_handleRightBracketKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault(); e.preventDefault();
this._openSelectedFile(0); this._openSelectedFile(0);
break; },
case 13: // <enter>
case 79: // 'o' _handleEnterKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault(); e.preventDefault();
if (this._showInlineDiffs) { if (this._showInlineDiffs) {
this._openCursorFile(); this._openCursorFile();
} else { } else {
this._openSelectedFile(); this._openSelectedFile();
} }
break; },
case 78: // 'n'
if (this._showInlineDiffs) { _handleNKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
if (!this._showInlineDiffs) { return; }
e.preventDefault(); e.preventDefault();
if (e.shiftKey) { if (e.shiftKey) {
this.$.cursor.moveToNextCommentThread(); this.$.cursor.moveToNextCommentThread();
} else { } else {
this.$.cursor.moveToNextChunk(); this.$.cursor.moveToNextChunk();
} }
} },
break;
case 80: // 'p' _handlePKey: function(e) {
if (this._showInlineDiffs) { if (this.shouldSuppressKeyboardShortcut(e)) { return; }
if (!this._showInlineDiffs) { return; }
e.preventDefault(); e.preventDefault();
if (e.shiftKey) { if (e.shiftKey) {
this.$.cursor.moveToPreviousCommentThread(); this.$.cursor.moveToPreviousCommentThread();
} else { } else {
this.$.cursor.moveToPreviousChunk(); this.$.cursor.moveToPreviousChunk();
} }
} },
break;
case 65: // 'a' _handleCapitalAKey: function(e) {
if (e.shiftKey) { // Hide left diff. if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault(); e.preventDefault();
this._forEachDiff(function(diff) { this._forEachDiff(function(diff) {
diff.toggleLeftDiff(); diff.toggleLeftDiff();
}); });
}
break;
}
}, },
_toggleInlineDiffs: function() { _toggleInlineDiffs: function() {
@@ -555,10 +589,6 @@
return DiffViewMode.SIDE_BY_SIDE; return DiffViewMode.SIDE_BY_SIDE;
}, },
_handleDropdownChange: function(e) {
e.target.blur();
},
_fileListActionsVisible: function(numFilesShown, maxFilesForBulkActions) { _fileListActionsVisible: function(numFilesShown, maxFilesForBulkActions) {
return numFilesShown <= maxFilesForBulkActions; return numFilesShown <= maxFilesForBulkActions;
}, },

View File

@@ -155,7 +155,7 @@ limitations under the License.
return [{toggleLeftDiff: toggleLeftDiffStub}]; return [{toggleLeftDiff: toggleLeftDiffStub}];
}, },
}); });
MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift'); // 'A' MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift', 'a');
assert.isTrue(toggleLeftDiffStub.calledOnce); assert.isTrue(toggleLeftDiffStub.calledOnce);
diffsStub.restore(); diffsStub.restore();
}); });
@@ -168,25 +168,25 @@ limitations under the License.
assert.isTrue(elementItems[0].hasAttribute('selected')); assert.isTrue(elementItems[0].hasAttribute('selected'));
assert.isFalse(elementItems[1].hasAttribute('selected')); assert.isFalse(elementItems[1].hasAttribute('selected'));
assert.isFalse(elementItems[2].hasAttribute('selected')); assert.isFalse(elementItems[2].hasAttribute('selected'));
MockInteractions.pressAndReleaseKeyOn(element, 74); // 'J' MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
assert.equal(element.selectedIndex, 1); assert.equal(element.selectedIndex, 1);
MockInteractions.pressAndReleaseKeyOn(element, 74); // 'J' MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
var showStub = sandbox.stub(page, 'show'); var showStub = sandbox.stub(page, 'show');
assert.equal(element.selectedIndex, 2); assert.equal(element.selectedIndex, 2);
MockInteractions.pressAndReleaseKeyOn(element, 13); // 'ENTER' MockInteractions.pressAndReleaseKeyOn(element, 13, null, 'enter');
assert(showStub.lastCall.calledWith('/c/42/2/myfile.txt'), assert(showStub.lastCall.calledWith('/c/42/2/myfile.txt'),
'Should navigate to /c/42/2/myfile.txt'); 'Should navigate to /c/42/2/myfile.txt');
MockInteractions.pressAndReleaseKeyOn(element, 75); // 'K' MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
assert.equal(element.selectedIndex, 1); assert.equal(element.selectedIndex, 1);
MockInteractions.pressAndReleaseKeyOn(element, 79); // 'O' MockInteractions.pressAndReleaseKeyOn(element, 79, null, 'o');
assert(showStub.lastCall.calledWith('/c/42/2/file_added_in_rev2.txt'), assert(showStub.lastCall.calledWith('/c/42/2/file_added_in_rev2.txt'),
'Should navigate to /c/42/2/file_added_in_rev2.txt'); 'Should navigate to /c/42/2/file_added_in_rev2.txt');
MockInteractions.pressAndReleaseKeyOn(element, 75); // 'K' MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
MockInteractions.pressAndReleaseKeyOn(element, 75); // 'K' MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
MockInteractions.pressAndReleaseKeyOn(element, 75); // 'K' MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
assert.equal(element.selectedIndex, 0); assert.equal(element.selectedIndex, 0);
showStub.restore(); showStub.restore();
@@ -194,23 +194,23 @@ limitations under the License.
test('i key shows/hides selected inline diff', function() { test('i key shows/hides selected inline diff', function() {
element.selectedIndex = 0; element.selectedIndex = 0;
MockInteractions.pressAndReleaseKeyOn(element, 73); // 'I' MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
flushAsynchronousOperations(); flushAsynchronousOperations();
assert.isFalse(element.diffs[0].hasAttribute('hidden')); assert.isFalse(element.diffs[0].hasAttribute('hidden'));
MockInteractions.pressAndReleaseKeyOn(element, 73); // 'I' MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
flushAsynchronousOperations(); flushAsynchronousOperations();
assert.isTrue(element.diffs[0].hasAttribute('hidden')); assert.isTrue(element.diffs[0].hasAttribute('hidden'));
element.selectedIndex = 1; element.selectedIndex = 1;
MockInteractions.pressAndReleaseKeyOn(element, 73); // 'I' MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
flushAsynchronousOperations(); flushAsynchronousOperations();
assert.isFalse(element.diffs[1].hasAttribute('hidden')); assert.isFalse(element.diffs[1].hasAttribute('hidden'));
MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift'); // 'I' MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift', 'i');
flushAsynchronousOperations(); flushAsynchronousOperations();
for (var index in element.diffs) { for (var index in element.diffs) {
assert.isFalse(element.diffs[index].hasAttribute('hidden')); assert.isFalse(element.diffs[index].hasAttribute('hidden'));
} }
MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift'); // 'I' MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift', 'i');
flushAsynchronousOperations(); flushAsynchronousOperations();
for (var index in element.diffs) { for (var index in element.diffs) {
assert.isTrue(element.diffs[index].hasAttribute('hidden')); assert.isTrue(element.diffs[index].hasAttribute('hidden'));

View File

@@ -113,17 +113,11 @@
for (var i = 0; i < messageEls.length; i++) { for (var i = 0; i < messageEls.length; i++) {
messageEls[i].expanded = this._expanded; messageEls[i].expanded = this._expanded;
} }
if (e && e.target) {
e.target.blur();
}
}, },
_handleAutomatedMessageToggleTap: function(e) { _handleAutomatedMessageToggleTap: function(e) {
e.preventDefault(); e.preventDefault();
this._hideAutomated = !this._hideAutomated; this._hideAutomated = !this._hideAutomated;
if (e && e.target) {
e.target.blur();
}
}, },
_handleScrollTo: function(e) { _handleScrollTo: function(e) {

View File

@@ -94,6 +94,10 @@
'searchButton.tap': '_preventDefaultAndNavigateToInputVal', 'searchButton.tap': '_preventDefaultAndNavigateToInputVal',
}, },
keyBindings: {
'/': '_handleForwardSlashKey',
},
properties: { properties: {
value: { value: {
type: String, type: String,
@@ -292,18 +296,12 @@
}); });
}, },
_handleKey: function(e) { _handleForwardSlashKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; } if (this.shouldSuppressKeyboardShortcut(e)) { return; }
switch (e.keyCode) {
case 191: // '/' or '?' with shift key.
// TODO(andybons): Localization using e.key/keypress event.
if (e.shiftKey) { break; }
e.preventDefault(); e.preventDefault();
var s = this.$.searchInput; this.$.searchInput.focus();
s.focus(); this.$.searchInput.selectAll();
s.setSelectionRange(0, s.value.length);
break;
}
}, },
}); });
})(); })();

View File

@@ -70,13 +70,15 @@ limitations under the License.
done(); done();
}); });
element.value = 'test'; element.value = 'test';
MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13); MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13,
null, 'enter');
}); });
test('search query should be double-escaped', function() { test('search query should be double-escaped', function() {
var showStub = sinon.stub(page, 'show'); var showStub = sinon.stub(page, 'show');
element.$.searchInput.text = 'fate/stay'; element.$.searchInput.text = 'fate/stay';
MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13); MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13,
null, 'enter');
assert.equal(showStub.lastCall.args[0], '/q/fate%252Fstay'); assert.equal(showStub.lastCall.args[0], '/q/fate%252Fstay');
showStub.restore(); showStub.restore();
}); });
@@ -85,7 +87,8 @@ limitations under the License.
var showStub = sinon.stub(page, 'show'); var showStub = sinon.stub(page, 'show');
var blurSpy = sinon.spy(element.$.searchInput.$.input, 'blur'); var blurSpy = sinon.spy(element.$.searchInput.$.input, 'blur');
element.$.searchInput.text = 'fate/stay'; element.$.searchInput.text = 'fate/stay';
MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13); MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13,
null, 'enter');
assert.isTrue(blurSpy.called); assert.isTrue(blurSpy.called);
showStub.restore(); showStub.restore();
blurSpy.restore(); blurSpy.restore();
@@ -94,10 +97,19 @@ limitations under the License.
test('empty search query does not trigger nav', function() { test('empty search query does not trigger nav', function() {
var showSpy = sinon.spy(page, 'show'); var showSpy = sinon.spy(page, 'show');
element.value = ''; element.value = '';
MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13); MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13,
null, 'enter');
assert.isFalse(showSpy.called); assert.isFalse(showSpy.called);
}); });
test('keyboard shortcuts', function() {
var focusSpy = sinon.spy(element.$.searchInput, 'focus');
var selectAllSpy = sinon.spy(element.$.searchInput, 'selectAll');
MockInteractions.pressAndReleaseKeyOn(document.body, 191, null, '/');
assert.isTrue(focusSpy.called);
assert.isTrue(selectAllSpy.called);
});
suite('_getSearchSuggestions', suite('_getSearchSuggestions',
function() { function() {
setup(function() { setup(function() {

View File

@@ -57,6 +57,10 @@
'_commentsChanged(comments.splices)', '_commentsChanged(comments.splices)',
], ],
keyBindings: {
'e shift+e': '_handleEKey',
},
attached: function() { attached: function() {
this._getLoggedIn().then(function(loggedIn) { this._getLoggedIn().then(function(loggedIn) {
this._showActions = loggedIn; this._showActions = loggedIn;
@@ -88,12 +92,12 @@
this._orderedComments = this._sortedComments(this.comments); this._orderedComments = this._sortedComments(this.comments);
}, },
_handleKey: function(e) { _handleEKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; } if (this.shouldSuppressKeyboardShortcut(e)) { return; }
if (e.keyCode === 69) { // 'e'
e.preventDefault(); // Dont preventDefault in this case because it will render the event
this._expandCollapseComments(e.shiftKey); // useless for other handlers (other gr-diff-comment-thread elements).
} this._expandCollapseComments(e.detail.keyboardEvent.shiftKey);
}, },
_expandCollapseComments: function(actionIsCollapse) { _expandCollapseComments: function(actionIsCollapse) {

View File

@@ -280,10 +280,10 @@ limitations under the License.
}]; }];
element.comments = comments; element.comments = comments;
var expandCollapseStub = sinon.stub(element, '_expandCollapseComments'); var expandCollapseStub = sinon.stub(element, '_expandCollapseComments');
MockInteractions.pressAndReleaseKeyOn(element, 69); // 'e' MockInteractions.pressAndReleaseKeyOn(element, 69, null, 'e');
assert.isTrue(expandCollapseStub.lastCall.calledWith(false)); assert.isTrue(expandCollapseStub.lastCall.calledWith(false));
MockInteractions.pressAndReleaseKeyOn(element, 69, 'shift'); // 'e' MockInteractions.pressAndReleaseKeyOn(element, 69, 'shift', 'e');
assert.isTrue(expandCollapseStub.lastCall.calledWith(true)); assert.isTrue(expandCollapseStub.lastCall.calledWith(true));
expandCollapseStub.restore(); expandCollapseStub.restore();
}); });

View File

@@ -279,34 +279,34 @@
}, },
_handleReply: function(e) { _handleReply: function(e) {
this._preventDefaultAndBlur(e); e.preventDefault();
this.fire('reply', this._getEventPayload(), {bubbles: false}); this.fire('reply', this._getEventPayload(), {bubbles: false});
}, },
_handleQuote: function(e) { _handleQuote: function(e) {
this._preventDefaultAndBlur(e); e.preventDefault();
this.fire( this.fire(
'reply', this._getEventPayload({quote: true}), {bubbles: false}); 'reply', this._getEventPayload({quote: true}), {bubbles: false});
}, },
_handleDone: function(e) { _handleDone: function(e) {
this._preventDefaultAndBlur(e); e.preventDefault();
this.fire('done', this._getEventPayload(), {bubbles: false}); this.fire('done', this._getEventPayload(), {bubbles: false});
}, },
_handleEdit: function(e) { _handleEdit: function(e) {
this._preventDefaultAndBlur(e); e.preventDefault();
this._messageText = this.comment.message; this._messageText = this.comment.message;
this.editing = true; this.editing = true;
}, },
_handleSave: function(e) { _handleSave: function(e) {
this._preventDefaultAndBlur(e); e.preventDefault();
this.save(); this.save();
}, },
_handleCancel: function(e) { _handleCancel: function(e) {
this._preventDefaultAndBlur(e); e.preventDefault();
if (this.comment.message == null || this.comment.message.length == 0) { if (this.comment.message == null || this.comment.message.length == 0) {
this._fireDiscard(); this._fireDiscard();
return; return;
@@ -321,7 +321,7 @@
}, },
_handleDiscard: function(e) { _handleDiscard: function(e) {
this._preventDefaultAndBlur(e); e.preventDefault();
if (!this.comment.__draft) { if (!this.comment.__draft) {
throw Error('Cannot discard a non-draft comment.'); throw Error('Cannot discard a non-draft comment.');
} }
@@ -345,11 +345,6 @@
}.bind(this)); }.bind(this));
}, },
_preventDefaultAndBlur: function(e) {
e.preventDefault();
Polymer.dom(e).rootTarget.blur();
},
_saveDraft: function(draft) { _saveDraft: function(draft) {
return this.$.restAPI.saveDiffDraft(this.changeNum, this.patchNum, draft); return this.$.restAPI.saveDiffDraft(this.changeNum, this.patchNum, draft);
}, },

View File

@@ -217,8 +217,7 @@ limitations under the License.
id="modeSelect" id="modeSelect"
is="gr-select" is="gr-select"
bind-value="{{changeViewState.diffMode}}" bind-value="{{changeViewState.diffMode}}"
hidden$="[[_computeModeSelectHidden(_isImageDiff)]]" hidden$="[[_computeModeSelectHidden(_isImageDiff)]]">
on-change="_handleDropdownChange">
<option value="SIDE_BY_SIDE">Side By Side</option> <option value="SIDE_BY_SIDE">Side By Side</option>
<option value="UNIFIED_DIFF">Unified</option> <option value="UNIFIED_DIFF">Unified</option>
</select> </select>

View File

@@ -100,6 +100,22 @@
'_getFiles(_changeNum, _patchRange.*)', '_getFiles(_changeNum, _patchRange.*)',
], ],
keyBindings: {
'esc': '_handleEscKey',
'shift+left': '_handleShiftLeftKey',
'shift+right': '_handleShiftRightKey',
'up k': '_handleUpKey',
'down j': '_handleDownKey',
'c': '_handleCKey',
'[': '_handleLeftBracketKey',
']': '_handleRightBracketKey',
'n shift+n': '_handleNKey',
'p shift+p': '_handlePKey',
'a shift+a': '_handleAKey',
'u': '_handleUKey',
',': '_handleCommaKey',
},
attached: function() { attached: function() {
this._getLoggedIn().then(function(loggedIn) { this._getLoggedIn().then(function(loggedIn) {
this._loggedIn = loggedIn; this._loggedIn = loggedIn;
@@ -185,101 +201,127 @@
this._patchRange.patchNum, this._path, reviewed); this._patchRange.patchNum, this._path, reviewed);
}, },
_checkForModifiers: function(e) { _handleEscKey: function(e) {
return e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || false;
},
_handleKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; } if (this.shouldSuppressKeyboardShortcut(e)) { return; }
switch (e.keyCode) {
case 27: // escape
e.preventDefault(); e.preventDefault();
this.$.diff.displayLine = false; this.$.diff.displayLine = false;
break; },
case 37: // left
if (e.shiftKey) { _handleShiftLeftKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault(); e.preventDefault();
this.$.cursor.moveLeft(); this.$.cursor.moveLeft();
} },
break;
case 39: // right _handleShiftRightKey: function(e) {
if (e.shiftKey) { if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault(); e.preventDefault();
this.$.cursor.moveRight(); this.$.cursor.moveRight();
} },
break;
case 40: // down _handleUpKey: function(e) {
case 74: // 'j' if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault();
this.$.diff.displayLine = true;
this.$.cursor.moveDown();
break;
case 38: // up
case 75: // 'k'
e.preventDefault(); e.preventDefault();
this.$.diff.displayLine = true; this.$.diff.displayLine = true;
this.$.cursor.moveUp(); this.$.cursor.moveUp();
break; },
case 67: // 'c'
if (this._checkForModifiers(e)) { return; } _handleDownKey: function(e) {
if (!this.$.diff.isRangeSelected()) { if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault();
this.$.diff.displayLine = true;
this.$.cursor.moveDown();
},
_handleCKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
if (this.$.diff.isRangeSelected()) { return; }
e.preventDefault(); e.preventDefault();
var line = this.$.cursor.getTargetLineElement(); var line = this.$.cursor.getTargetLineElement();
if (line) { if (line) {
this.$.diff.addDraftAtLine(line); this.$.diff.addDraftAtLine(line);
} }
} },
break;
case 219: // '[' _handleLeftBracketKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault(); e.preventDefault();
this._navToFile(this._path, this._fileList, -1); this._navToFile(this._path, this._fileList, -1);
break; },
case 221: // ']'
_handleRightBracketKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault(); e.preventDefault();
this._navToFile(this._path, this._fileList, 1); this._navToFile(this._path, this._fileList, 1);
break; },
case 78: // 'n'
_handleNKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault(); e.preventDefault();
if (e.shiftKey) { if (e.detail.keyboardEvent.shiftKey) {
this.$.cursor.moveToNextCommentThread(); this.$.cursor.moveToNextCommentThread();
} else { } else {
this.$.cursor.moveToNextChunk(); this.$.cursor.moveToNextChunk();
} }
break; },
case 80: // 'p'
_handlePKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault(); e.preventDefault();
if (e.shiftKey) { if (e.detail.keyboardEvent.shiftKey) {
this.$.cursor.moveToPreviousCommentThread(); this.$.cursor.moveToPreviousCommentThread();
} else { } else {
this.$.cursor.moveToPreviousChunk(); this.$.cursor.moveToPreviousChunk();
} }
break; },
case 65: // 'a'
if (e.shiftKey) { // Hide left diff. _handleAKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
if (e.detail.keyboardEvent.shiftKey) { // Hide left diff.
e.preventDefault(); e.preventDefault();
this.$.diff.toggleLeftDiff(); this.$.diff.toggleLeftDiff();
break; return;
} }
if (!this._loggedIn) { break; } if (!this._loggedIn) { return; }
this.set('changeViewState.showReplyDialog', true); this.set('changeViewState.showReplyDialog', true);
/* falls through */ // required by JSHint
case 85: // 'u'
if (this._changeNum && this._patchRange.patchNum) {
e.preventDefault(); e.preventDefault();
this._navToChangeView();
},
_handleUKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault();
this._navToChangeView();
},
_handleCommaKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault();
this._openPrefs();
},
_navToChangeView: function() {
if (!this._changeNum || !this._patchRange.patchNum) { return; }
page.show(this._getChangePath( page.show(this._getChangePath(
this._changeNum, this._changeNum,
this._patchRange, this._patchRange,
this._change && this._change.revisions)); this._change && this._change.revisions));
}
break;
case 188: // ','
e.preventDefault();
this._openPrefs();
break;
}
}, },
_navToFile: function(path, fileList, direction) { _navToFile: function(path, fileList, direction) {
@@ -556,10 +598,6 @@
history.replaceState(null, null, '#' + this.$.cursor.getAddress()); history.replaceState(null, null, '#' + this.$.cursor.getAddress());
}, },
_handleDropdownChange: function(e) {
e.target.blur();
},
_computeDownloadLink: function(changeNum, patchRange, path) { _computeDownloadLink: function(changeNum, patchRange, path) {
var url = this.changeBaseURL(changeNum, patchRange.patchNum); var url = this.changeBaseURL(changeNum, patchRange.patchNum);
url += '/patch?zip&path=' + encodeURIComponent(path); url += '/patch?zip&path=' + encodeURIComponent(path);

View File

@@ -62,7 +62,7 @@ limitations under the License.
test('toggle left diff with a hotkey', function() { test('toggle left diff with a hotkey', function() {
var toggleLeftDiffStub = sandbox.stub(element.$.diff, 'toggleLeftDiff'); var toggleLeftDiffStub = sandbox.stub(element.$.diff, 'toggleLeftDiff');
MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift'); // 'a' MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift', 'a');
assert.isTrue(toggleLeftDiffStub.calledOnce); assert.isTrue(toggleLeftDiffStub.calledOnce);
}); });
@@ -82,29 +82,29 @@ limitations under the License.
element.changeViewState.selectedFileIndex = 1; element.changeViewState.selectedFileIndex = 1;
var showStub = sandbox.stub(page, 'show'); var showStub = sandbox.stub(page, 'show');
MockInteractions.pressAndReleaseKeyOn(element, 85); // 'u' MockInteractions.pressAndReleaseKeyOn(element, 85, null, 'u');
assert(showStub.lastCall.calledWithExactly('/c/42/'), assert(showStub.lastCall.calledWithExactly('/c/42/'),
'Should navigate to /c/42/'); 'Should navigate to /c/42/');
MockInteractions.pressAndReleaseKeyOn(element, 221); // ']' MockInteractions.pressAndReleaseKeyOn(element, 221, null, ']');
assert(showStub.lastCall.calledWithExactly('/c/42/10/wheatley.md'), assert(showStub.lastCall.calledWithExactly('/c/42/10/wheatley.md'),
'Should navigate to /c/42/10/wheatley.md'); 'Should navigate to /c/42/10/wheatley.md');
element._path = 'wheatley.md'; element._path = 'wheatley.md';
assert.equal(element.changeViewState.selectedFileIndex, 2); assert.equal(element.changeViewState.selectedFileIndex, 2);
MockInteractions.pressAndReleaseKeyOn(element, 219); // '[' MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/10/glados.txt'), assert(showStub.lastCall.calledWithExactly('/c/42/10/glados.txt'),
'Should navigate to /c/42/10/glados.txt'); 'Should navigate to /c/42/10/glados.txt');
element._path = 'glados.txt'; element._path = 'glados.txt';
assert.equal(element.changeViewState.selectedFileIndex, 1); assert.equal(element.changeViewState.selectedFileIndex, 1);
MockInteractions.pressAndReleaseKeyOn(element, 219); // '[' MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/10/chell.go'), assert(showStub.lastCall.calledWithExactly('/c/42/10/chell.go'),
'Should navigate to /c/42/10/chell.go'); 'Should navigate to /c/42/10/chell.go');
element._path = 'chell.go'; element._path = 'chell.go';
assert.equal(element.changeViewState.selectedFileIndex, 0); assert.equal(element.changeViewState.selectedFileIndex, 0);
MockInteractions.pressAndReleaseKeyOn(element, 219); // '[' MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/'), assert(showStub.lastCall.calledWithExactly('/c/42/'),
'Should navigate to /c/42/'); 'Should navigate to /c/42/');
assert.equal(element.changeViewState.selectedFileIndex, 0); assert.equal(element.changeViewState.selectedFileIndex, 0);
@@ -112,33 +112,33 @@ limitations under the License.
var showPrefsStub = sandbox.stub(element.$.prefsOverlay, 'open', var showPrefsStub = sandbox.stub(element.$.prefsOverlay, 'open',
function() { return Promise.resolve({}); }); function() { return Promise.resolve({}); });
MockInteractions.pressAndReleaseKeyOn(element, 188); // ',' MockInteractions.pressAndReleaseKeyOn(element, 188, null, ',');
assert(showPrefsStub.calledOnce); assert(showPrefsStub.calledOnce);
var scrollStub = sandbox.stub(element.$.cursor, 'moveToNextChunk'); var scrollStub = sandbox.stub(element.$.cursor, 'moveToNextChunk');
MockInteractions.pressAndReleaseKeyOn(element, 78); // 'n' MockInteractions.pressAndReleaseKeyOn(element, 78, null, 'n');
assert(scrollStub.calledOnce); assert(scrollStub.calledOnce);
scrollStub = sandbox.stub(element.$.cursor, 'moveToPreviousChunk'); scrollStub = sandbox.stub(element.$.cursor, 'moveToPreviousChunk');
MockInteractions.pressAndReleaseKeyOn(element, 80); // 'p' MockInteractions.pressAndReleaseKeyOn(element, 80, null, 'p');
assert(scrollStub.calledOnce); assert(scrollStub.calledOnce);
scrollStub = sandbox.stub(element.$.cursor, 'moveToNextCommentThread'); scrollStub = sandbox.stub(element.$.cursor, 'moveToNextCommentThread');
MockInteractions.pressAndReleaseKeyOn(element, 78, ['shift']); // 'N' MockInteractions.pressAndReleaseKeyOn(element, 78, 'shift', 'n');
assert(scrollStub.calledOnce); assert(scrollStub.calledOnce);
scrollStub = sandbox.stub(element.$.cursor, scrollStub = sandbox.stub(element.$.cursor,
'moveToPreviousCommentThread'); 'moveToPreviousCommentThread');
MockInteractions.pressAndReleaseKeyOn(element, 80, ['shift']); // 'P' MockInteractions.pressAndReleaseKeyOn(element, 80, 'shift', 'p');
assert(scrollStub.calledOnce); assert(scrollStub.calledOnce);
var computeContainerClassStub = sandbox.stub(element.$.diff, var computeContainerClassStub = sandbox.stub(element.$.diff,
'_computeContainerClass'); '_computeContainerClass');
MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j' MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
assert(computeContainerClassStub.lastCall.calledWithExactly( assert(computeContainerClassStub.lastCall.calledWithExactly(
false, 'SIDE_BY_SIDE', true)); false, 'SIDE_BY_SIDE', true));
MockInteractions.pressAndReleaseKeyOn(element, 27); // 'escape' MockInteractions.pressAndReleaseKeyOn(element, 27, null, 'esc');
assert(computeContainerClassStub.lastCall.calledWithExactly( assert(computeContainerClassStub.lastCall.calledWithExactly(
false, 'SIDE_BY_SIDE', false)); false, 'SIDE_BY_SIDE', false));
}); });
@@ -175,39 +175,39 @@ limitations under the License.
var showStub = sandbox.stub(page, 'show'); var showStub = sandbox.stub(page, 'show');
MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a' MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
assert.isTrue(showStub.notCalled, 'The `a` keyboard shortcut should ' + assert.isTrue(showStub.notCalled, 'The `a` keyboard shortcut should ' +
'only work when the user is logged in.'); 'only work when the user is logged in.');
assert.isNull(window.sessionStorage.getItem( assert.isNull(window.sessionStorage.getItem(
'changeView.showReplyDialog')); 'changeView.showReplyDialog'));
element._loggedIn = true; element._loggedIn = true;
MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a' MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
assert.isTrue(element.changeViewState.showReplyDialog); assert.isTrue(element.changeViewState.showReplyDialog);
assert(showStub.lastCall.calledWithExactly('/c/42/5..10'), assert(showStub.lastCall.calledWithExactly('/c/42/5..10'),
'Should navigate to /c/42/5..10'); 'Should navigate to /c/42/5..10');
MockInteractions.pressAndReleaseKeyOn(element, 85); // 'u' MockInteractions.pressAndReleaseKeyOn(element, 85, null, 'u');
assert(showStub.lastCall.calledWithExactly('/c/42/5..10'), assert(showStub.lastCall.calledWithExactly('/c/42/5..10'),
'Should navigate to /c/42/5..10'); 'Should navigate to /c/42/5..10');
MockInteractions.pressAndReleaseKeyOn(element, 221); // ']' MockInteractions.pressAndReleaseKeyOn(element, 221, null, ']');
assert(showStub.lastCall.calledWithExactly('/c/42/5..10/wheatley.md'), assert(showStub.lastCall.calledWithExactly('/c/42/5..10/wheatley.md'),
'Should navigate to /c/42/5..10/wheatley.md'); 'Should navigate to /c/42/5..10/wheatley.md');
element._path = 'wheatley.md'; element._path = 'wheatley.md';
MockInteractions.pressAndReleaseKeyOn(element, 219); // '[' MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/5..10/glados.txt'), assert(showStub.lastCall.calledWithExactly('/c/42/5..10/glados.txt'),
'Should navigate to /c/42/5..10/glados.txt'); 'Should navigate to /c/42/5..10/glados.txt');
element._path = 'glados.txt'; element._path = 'glados.txt';
MockInteractions.pressAndReleaseKeyOn(element, 219); // '[' MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/5..10/chell.go'), assert(showStub.lastCall.calledWithExactly('/c/42/5..10/chell.go'),
'Should navigate to /c/42/5..10/chell.go'); 'Should navigate to /c/42/5..10/chell.go');
element._path = 'chell.go'; element._path = 'chell.go';
MockInteractions.pressAndReleaseKeyOn(element, 219); // '[' MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/5..10'), assert(showStub.lastCall.calledWithExactly('/c/42/5..10'),
'Should navigate to /c/42/5..10'); 'Should navigate to /c/42/5..10');
}); });
@@ -229,39 +229,39 @@ limitations under the License.
var showStub = sandbox.stub(page, 'show'); var showStub = sandbox.stub(page, 'show');
MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a' MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
assert.isTrue(showStub.notCalled, 'The `a` keyboard shortcut should ' + assert.isTrue(showStub.notCalled, 'The `a` keyboard shortcut should ' +
'only work when the user is logged in.'); 'only work when the user is logged in.');
assert.isNull(window.sessionStorage.getItem( assert.isNull(window.sessionStorage.getItem(
'changeView.showReplyDialog')); 'changeView.showReplyDialog'));
element._loggedIn = true; element._loggedIn = true;
MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a' MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
assert.isTrue(element.changeViewState.showReplyDialog); assert.isTrue(element.changeViewState.showReplyDialog);
assert(showStub.lastCall.calledWithExactly('/c/42/1'), assert(showStub.lastCall.calledWithExactly('/c/42/1'),
'Should navigate to /c/42/1'); 'Should navigate to /c/42/1');
MockInteractions.pressAndReleaseKeyOn(element, 85); // 'u' MockInteractions.pressAndReleaseKeyOn(element, 85, null, 'u');
assert(showStub.lastCall.calledWithExactly('/c/42/1'), assert(showStub.lastCall.calledWithExactly('/c/42/1'),
'Should navigate to /c/42/1'); 'Should navigate to /c/42/1');
MockInteractions.pressAndReleaseKeyOn(element, 221); // ']' MockInteractions.pressAndReleaseKeyOn(element, 221, null, ']');
assert(showStub.lastCall.calledWithExactly('/c/42/1/wheatley.md'), assert(showStub.lastCall.calledWithExactly('/c/42/1/wheatley.md'),
'Should navigate to /c/42/1/wheatley.md'); 'Should navigate to /c/42/1/wheatley.md');
element._path = 'wheatley.md'; element._path = 'wheatley.md';
MockInteractions.pressAndReleaseKeyOn(element, 219); // '[' MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/1/glados.txt'), assert(showStub.lastCall.calledWithExactly('/c/42/1/glados.txt'),
'Should navigate to /c/42/1/glados.txt'); 'Should navigate to /c/42/1/glados.txt');
element._path = 'glados.txt'; element._path = 'glados.txt';
MockInteractions.pressAndReleaseKeyOn(element, 219); // '[' MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/1/chell.go'), assert(showStub.lastCall.calledWithExactly('/c/42/1/chell.go'),
'Should navigate to /c/42/1/chell.go'); 'Should navigate to /c/42/1/chell.go');
element._path = 'chell.go'; element._path = 'chell.go';
MockInteractions.pressAndReleaseKeyOn(element, 219); // '[' MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/1'), assert(showStub.lastCall.calledWithExactly('/c/42/1'),
'Should navigate to /c/42/1'); 'Should navigate to /c/42/1');
}); });
@@ -278,39 +278,39 @@ limitations under the License.
var showStub = sandbox.stub(page, 'show'); var showStub = sandbox.stub(page, 'show');
MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a' MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
assert.isTrue(showStub.notCalled, 'The `a` keyboard shortcut should ' + assert.isTrue(showStub.notCalled, 'The `a` keyboard shortcut should ' +
'only work when the user is logged in.'); 'only work when the user is logged in.');
assert.isNull(window.sessionStorage.getItem( assert.isNull(window.sessionStorage.getItem(
'changeView.showReplyDialog')); 'changeView.showReplyDialog'));
element._loggedIn = true; element._loggedIn = true;
MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a' MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
assert.isTrue(element.changeViewState.showReplyDialog); assert.isTrue(element.changeViewState.showReplyDialog);
assert(showStub.lastCall.calledWithExactly('/c/42/1'), assert(showStub.lastCall.calledWithExactly('/c/42/1'),
'Should navigate to /c/42/1'); 'Should navigate to /c/42/1');
MockInteractions.pressAndReleaseKeyOn(element, 85); // 'u' MockInteractions.pressAndReleaseKeyOn(element, 85, null, 'u');
assert(showStub.lastCall.calledWithExactly('/c/42/1'), assert(showStub.lastCall.calledWithExactly('/c/42/1'),
'Should navigate to /c/42/1'); 'Should navigate to /c/42/1');
MockInteractions.pressAndReleaseKeyOn(element, 221); // ']' MockInteractions.pressAndReleaseKeyOn(element, 221, null, ']');
assert(showStub.lastCall.calledWithExactly('/c/42/1/wheatley.md'), assert(showStub.lastCall.calledWithExactly('/c/42/1/wheatley.md'),
'Should navigate to /c/42/1/wheatley.md'); 'Should navigate to /c/42/1/wheatley.md');
element._path = 'wheatley.md'; element._path = 'wheatley.md';
MockInteractions.pressAndReleaseKeyOn(element, 219); // '[' MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/1/glados.txt'), assert(showStub.lastCall.calledWithExactly('/c/42/1/glados.txt'),
'Should navigate to /c/42/1/glados.txt'); 'Should navigate to /c/42/1/glados.txt');
element._path = 'glados.txt'; element._path = 'glados.txt';
MockInteractions.pressAndReleaseKeyOn(element, 219); // '[' MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/1/chell.go'), assert(showStub.lastCall.calledWithExactly('/c/42/1/chell.go'),
'Should navigate to /c/42/1/chell.go'); 'Should navigate to /c/42/1/chell.go');
element._path = 'chell.go'; element._path = 'chell.go';
MockInteractions.pressAndReleaseKeyOn(element, 219); // '[' MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(showStub.lastCall.calledWithExactly('/c/42/1'), assert(showStub.lastCall.calledWithExactly('/c/42/1'),
'Should navigate to /c/42/1'); 'Should navigate to /c/42/1');
}); });
@@ -457,7 +457,6 @@ limitations under the License.
test('diff mode selector correctly toggles the diff', function() { test('diff mode selector correctly toggles the diff', function() {
var select = element.$.modeSelect; var select = element.$.modeSelect;
var diffDisplay = element.$.diff; var diffDisplay = element.$.diff;
var blurSpy = sandbox.spy(select, 'blur');
element._userPrefs = {diff_view: 'SIDE_BY_SIDE'}; element._userPrefs = {diff_view: 'SIDE_BY_SIDE'};
// The mode selected in the view state reflects the selected option. // The mode selected in the view state reflects the selected option.
@@ -477,7 +476,6 @@ limitations under the License.
assert.equal(element._getDiffViewMode(), newMode); assert.equal(element._getDiffViewMode(), newMode);
assert.equal(element._getDiffViewMode(), select.value); assert.equal(element._getDiffViewMode(), select.value);
assert.equal(element._getDiffViewMode(), diffDisplay.viewMode); assert.equal(element._getDiffViewMode(), diffDisplay.viewMode);
assert(blurSpy.called, 'select should be blurred after selection');
}); });
test('diff mode selector initializes from preferences', function() { test('diff mode selector initializes from preferences', function() {
@@ -557,14 +555,6 @@ limitations under the License.
assert.equal(element.$.cursor.side, 'left'); assert.equal(element.$.cursor.side, 'left');
}); });
test('_checkForModifiers', function() {
assert.isTrue(element._checkForModifiers({altKey: true}));
assert.isTrue(element._checkForModifiers({ctrlKey: true}));
assert.isTrue(element._checkForModifiers({metaKey: true}));
assert.isTrue(element._checkForModifiers({shiftKey: true}));
assert.isFalse(element._checkForModifiers({}));
});
test('_shortenPath with long path should add ellipsis', function() { test('_shortenPath with long path should add ellipsis', function() {
var path = var path =
'level1/level2/level3/level4/file.js'; 'level1/level2/level3/level4/file.js';

View File

@@ -51,6 +51,10 @@
'mousedown': '_handleMouseDown', // See https://crbug.com/gerrit/4767 'mousedown': '_handleMouseDown', // See https://crbug.com/gerrit/4767
}, },
keyBindings: {
'c': '_handleCKey',
},
placeAbove: function(el) { placeAbove: function(el) {
var rect = this._getTargetBoundingRect(el); var rect = this._getTargetBoundingRect(el);
var boxRect = this.getBoundingClientRect(); var boxRect = this.getBoundingClientRect();
@@ -74,17 +78,11 @@
return rect; return rect;
}, },
_checkForModifiers: function(e) { _handleCKey: function(e) {
return e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || false;
},
_handleKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; } if (this.shouldSuppressKeyboardShortcut(e)) { return; }
if (e.keyCode === 67) { // 'c'
if (this._checkForModifiers(e)) { return; }
e.preventDefault(); e.preventDefault();
this._fireCreateComment(); this._fireCreateComment();
}
}, },
_handleMouseDown: function(e) { _handleMouseDown: function(e) {

View File

@@ -49,12 +49,12 @@ limitations under the License.
}); });
test('ignores regular keys', function() { test('ignores regular keys', function() {
MockInteractions.pressAndReleaseKeyOn(document.body, 27); // 'esc' MockInteractions.pressAndReleaseKeyOn(document.body, 27, null, 'esc');
assert.isFalse(element.fire.called); assert.isFalse(element.fire.called);
}); });
test('reacts to hotkey', function() { test('reacts to hotkey', function() {
MockInteractions.pressAndReleaseKeyOn(document.body, 67); // 'c' MockInteractions.pressAndReleaseKeyOn(document.body, 67, null, 'c');
assert.isTrue(element.fire.called); assert.isTrue(element.fire.called);
}); });
@@ -68,7 +68,7 @@ limitations under the License.
}; };
element.side = 'left'; element.side = 'left';
element.range = range; element.range = range;
MockInteractions.pressAndReleaseKeyOn(document.body, 67); // 'c' MockInteractions.pressAndReleaseKeyOn(document.body, 67, null, 'c');
assert(element.fire.calledWithExactly( assert(element.fire.calledWithExactly(
'create-comment', 'create-comment',
{ {
@@ -117,13 +117,5 @@ limitations under the License.
document.createRange.restore(); document.createRange.restore();
}); });
}); });
test('_checkForModifiers', function() {
assert.isTrue(element._checkForModifiers({altKey: true}));
assert.isTrue(element._checkForModifiers({ctrlKey: true}));
assert.isTrue(element._checkForModifiers({metaKey: true}));
assert.isTrue(element._checkForModifiers({shiftKey: true}));
assert.isFalse(element._checkForModifiers({}));
});
}); });
</script> </script>

View File

@@ -62,6 +62,10 @@
Gerrit.KeyboardShortcutBehavior, Gerrit.KeyboardShortcutBehavior,
], ],
keyBindings: {
'?': '_showKeyboardShortcuts',
},
attached: function() { attached: function() {
this.$.restAPI.getAccount().then(function(account) { this.$.restAPI.getAccount().then(function(account) {
this._account = account; this._account = account;
@@ -194,12 +198,9 @@
} }
}, },
_handleKey: function(e) { _showKeyboardShortcuts(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; } if (this.shouldSuppressKeyboardShortcut(e)) { return; }
if (e.keyCode === 191 && e.shiftKey) { // '/' or '?' with shift key.
this.$.keyboardShortcuts.open(); this.$.keyboardShortcuts.open();
}
}, },
_handleKeyboardShortcutDialogClose: function() { _handleKeyboardShortcutDialogClose: function() {

View File

@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<link rel="import" href="../../../bower_components/polymer/polymer.html"> <link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../bower_components/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
<link rel="import" href="../../../bower_components/iron-input/iron-input.html"> <link rel="import" href="../../../bower_components/iron-input/iron-input.html">
<link rel="import" href="../../shared/gr-cursor-manager/gr-cursor-manager.html"> <link rel="import" href="../../shared/gr-cursor-manager/gr-cursor-manager.html">

View File

@@ -140,6 +140,10 @@
this.$.input.focus(); this.$.input.focus();
}, },
selectAll: function() {
this.$.input.setSelectionRange(0, this.$.input.value.length);
},
clear: function() { clear: function() {
this.text = ''; this.text = '';
}, },

View File

@@ -94,7 +94,7 @@ limitations under the License.
var cancelHandler = sinon.spy(); var cancelHandler = sinon.spy();
element.addEventListener('cancel', cancelHandler); element.addEventListener('cancel', cancelHandler);
MockInteractions.pressAndReleaseKeyOn(element.$.input, 27); // Esc MockInteractions.pressAndReleaseKeyOn(element.$.input, 27, null, 'esc');
assert.isTrue(cancelHandler.called); assert.isTrue(cancelHandler.called);
assert.isTrue(element.$.suggestions.hasAttribute('hidden')); assert.isTrue(element.$.suggestions.hasAttribute('hidden'));
@@ -128,19 +128,22 @@ limitations under the License.
assert.equal(element.$.cursor.index, 0); assert.equal(element.$.cursor.index, 0);
MockInteractions.pressAndReleaseKeyOn(element.$.input, 40); // Down MockInteractions.pressAndReleaseKeyOn(element.$.input, 40, null,
'down');
assert.equal(element.$.cursor.index, 1); assert.equal(element.$.cursor.index, 1);
MockInteractions.pressAndReleaseKeyOn(element.$.input, 40); // Down MockInteractions.pressAndReleaseKeyOn(element.$.input, 40, null,
'down');
assert.equal(element.$.cursor.index, 2); assert.equal(element.$.cursor.index, 2);
MockInteractions.pressAndReleaseKeyOn(element.$.input, 38); // Up MockInteractions.pressAndReleaseKeyOn(element.$.input, 38, null, 'up');
assert.equal(element.$.cursor.index, 1); assert.equal(element.$.cursor.index, 1);
MockInteractions.pressAndReleaseKeyOn(element.$.input, 13); // Enter MockInteractions.pressAndReleaseKeyOn(element.$.input, 13, null,
'enter');
assert.equal(element.value, 1); assert.equal(element.value, 1);
assert.isTrue(commitHandler.called); assert.isTrue(commitHandler.called);
@@ -163,7 +166,8 @@ limitations under the License.
var commitHandler = sinon.spy(); var commitHandler = sinon.spy();
element.addEventListener('commit', commitHandler); element.addEventListener('commit', commitHandler);
MockInteractions.pressAndReleaseKeyOn(element.$.input, 13); // Enter MockInteractions.pressAndReleaseKeyOn(element.$.input, 13, null,
'enter');
assert.isTrue(commitHandler.called); assert.isTrue(commitHandler.called);
assert.equal(element.text, 'suggestion'); assert.equal(element.text, 'suggestion');
@@ -184,7 +188,8 @@ limitations under the License.
var commitHandler = sinon.spy(); var commitHandler = sinon.spy();
element.addEventListener('commit', commitHandler); element.addEventListener('commit', commitHandler);
MockInteractions.pressAndReleaseKeyOn(element.$.input, 13); // Enter MockInteractions.pressAndReleaseKeyOn(element.$.input, 13, null,
'enter');
assert.isTrue(commitHandler.called); assert.isTrue(commitHandler.called);
assert.equal(element.text, ''); assert.equal(element.text, '');
@@ -234,7 +239,8 @@ limitations under the License.
var commitHandler = sinon.spy(); var commitHandler = sinon.spy();
element.addEventListener('commit', commitHandler); element.addEventListener('commit', commitHandler);
MockInteractions.pressAndReleaseKeyOn(element.$.input, 13); // Enter MockInteractions.pressAndReleaseKeyOn(element.$.input, 13, null,
'enter');
assert.isTrue(commitHandler.called); assert.isTrue(commitHandler.called);
assert.equal(element.text, 'blah 0'); assert.equal(element.text, 'blah 0');
@@ -245,10 +251,10 @@ limitations under the License.
test('tab key completes only when suggestions exist', function() { test('tab key completes only when suggestions exist', function() {
var commitStub = sinon.stub(element, '_commit'); var commitStub = sinon.stub(element, '_commit');
element._suggestions = []; element._suggestions = [];
MockInteractions.pressAndReleaseKeyOn(element.$.input, 9); // tab MockInteractions.pressAndReleaseKeyOn(element.$.input, 9, null, 'tab');
assert.isFalse(commitStub.called); assert.isFalse(commitStub.called);
element._suggestions = ['tunnel snakes rule!']; element._suggestions = ['tunnel snakes rule!'];
MockInteractions.pressAndReleaseKeyOn(element.$.input, 9); // tab MockInteractions.pressAndReleaseKeyOn(element.$.input, 9, null, 'tab');
assert.isTrue(commitStub.called); assert.isTrue(commitStub.called);
commitStub.restore(); commitStub.restore();
}); });
@@ -258,11 +264,11 @@ limitations under the License.
element.addEventListener('commit', commitHandler); element.addEventListener('commit', commitHandler);
element._suggestions = ['tunnel snakes rule!']; element._suggestions = ['tunnel snakes rule!'];
element.tabCompleteWithoutCommit = true; element.tabCompleteWithoutCommit = true;
MockInteractions.pressAndReleaseKeyOn(element.$.input, 9); // tab MockInteractions.pressAndReleaseKeyOn(element.$.input, 9, null, 'tab');
assert.isFalse(commitHandler.called); assert.isFalse(commitHandler.called);
element.tabCompleteWithoutCommit = false; element.tabCompleteWithoutCommit = false;
element._suggestions = ['tunnel snakes rule!']; element._suggestions = ['tunnel snakes rule!'];
MockInteractions.pressAndReleaseKeyOn(element.$.input, 9); // tab MockInteractions.pressAndReleaseKeyOn(element.$.input, 9, null, 'tab');
assert.isTrue(commitHandler.called); assert.isTrue(commitHandler.called);
}); });
@@ -301,7 +307,7 @@ limitations under the License.
test('input-keydown event fired', function() { test('input-keydown event fired', function() {
var listener = sinon.spy(); var listener = sinon.spy();
element.addEventListener('input-keydown', listener); element.addEventListener('input-keydown', listener);
MockInteractions.pressAndReleaseKeyOn(element.$.input, 9); // tab MockInteractions.pressAndReleaseKeyOn(element.$.input, 9, null, 'tab');
flushAsynchronousOperations(); flushAsynchronousOperations();
assert.isTrue(listener.called); assert.isTrue(listener.called);
}); });

View File

@@ -39,6 +39,10 @@
tabindex: '0', tabindex: '0',
}, },
keyBindings: {
'space enter': '_handleCommitKey',
},
_disabledChanged: function(disabled) { _disabledChanged: function(disabled) {
if (disabled) { if (disabled) {
this._enabledTabindex = this.getAttribute('tabindex'); this._enabledTabindex = this.getAttribute('tabindex');
@@ -46,13 +50,9 @@
this.setAttribute('tabindex', disabled ? '-1' : this._enabledTabindex); this.setAttribute('tabindex', disabled ? '-1' : this._enabledTabindex);
}, },
_handleKey: function(e) { _handleCommitKey: function(e) {
switch (e.keyCode) {
case 32: // 'spacebar'
case 13: // 'enter'
e.preventDefault(); e.preventDefault();
this.click(); this.click();
}
}, },
}); });
})(); })();

View File

@@ -16,7 +16,6 @@ limitations under the License.
<link rel="import" href="../../../bower_components/polymer/polymer.html"> <link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../bower_components/iron-overlay-behavior/iron-overlay-behavior.html"> <link rel="import" href="../../../bower_components/iron-overlay-behavior/iron-overlay-behavior.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior.html">
<dom-module id="gr-overlay"> <dom-module id="gr-overlay">
<template> <template>

View File

@@ -24,28 +24,13 @@
Polymer.IronOverlayBehavior, Polymer.IronOverlayBehavior,
], ],
detached: function() {
Gerrit.KeyboardShortcutBehavior.enable(this._id());
},
open: function() { open: function() {
return new Promise(function(resolve) { return new Promise(function(resolve) {
Gerrit.KeyboardShortcutBehavior.disable(this._id());
Polymer.IronOverlayBehaviorImpl.open.apply(this, arguments); Polymer.IronOverlayBehaviorImpl.open.apply(this, arguments);
this._awaitOpen(resolve); this._awaitOpen(resolve);
}.bind(this)); }.bind(this));
}, },
close: function() {
Gerrit.KeyboardShortcutBehavior.enable(this._id());
Polymer.IronOverlayBehaviorImpl.close.apply(this, arguments);
},
cancel: function() {
Gerrit.KeyboardShortcutBehavior.enable(this._id());
Polymer.IronOverlayBehaviorImpl.cancel.apply(this, arguments);
},
/** /**
* Override the focus stops that iron-overlay-behavior tries to find. * Override the focus stops that iron-overlay-behavior tries to find.
*/ */