Add save keyboard shortcut to gr-editor-view

Bug: Issue 4437
Change-Id: I895222cd0ec7dfdcddb8d385df9b8da64cf70612
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js
index 23f76db..866a62b 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js
@@ -76,6 +76,10 @@
       'content-change': '_handleContentChange',
     },
 
+    keyBindings: {
+      'ctrl+s meta+s': '_handleSaveShortcut',
+    },
+
     attached() {
       this._getEditPrefs().then(prefs => { this._prefs = prefs; });
     },
@@ -179,5 +183,10 @@
     _handleContentChange(e) {
       if (e.detail.value) { this.set('_newContent', e.detail.value); }
     },
+
+    _handleSaveShortcut(e) {
+      e.preventDefault();
+      if (!this._saveDisabled) { this._saveEdit(); }
+    },
   });
 })();
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
index 5f76a17..18ff1f5 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
@@ -299,5 +299,48 @@
     element._viewEditInChangeView();
     assert.equal(navStub.lastCall.args[1], element.EDIT_NAME);
   });
+
+  suite('keyboard shortcuts', () => {
+    // Used as the spy on the handler for each entry in keyBindings.
+    let handleSpy;
+
+    suite('_handleSaveShortcut', () => {
+      let saveStub;
+      setup(() => {
+        handleSpy = sandbox.spy(element, '_handleSaveShortcut');
+        saveStub = sandbox.stub(element, '_saveEdit');
+      });
+
+      test('save enabled', () => {
+        element._content = '';
+        element._newContent = '_test';
+        MockInteractions.pressAndReleaseKeyOn(element, 83, 'ctrl', 's');
+        flushAsynchronousOperations();
+
+        assert.isTrue(handleSpy.calledOnce);
+        assert.isTrue(saveStub.calledOnce);
+
+        MockInteractions.pressAndReleaseKeyOn(element, 83, 'meta', 's');
+        flushAsynchronousOperations();
+
+        assert.equal(handleSpy.callCount, 2);
+        assert.equal(saveStub.callCount, 2);
+      });
+
+      test('save disabled', () => {
+        MockInteractions.pressAndReleaseKeyOn(element, 83, 'ctrl', 's');
+        flushAsynchronousOperations();
+
+        assert.isTrue(handleSpy.calledOnce);
+        assert.isFalse(saveStub.called);
+
+        MockInteractions.pressAndReleaseKeyOn(element, 83, 'meta', 's');
+        flushAsynchronousOperations();
+
+        assert.equal(handleSpy.callCount, 2);
+        assert.isFalse(saveStub.called);
+      });
+    });
+  });
 });
 </script>