Adds diff preferences to the PolyGerrit settings view

Adds a section to the Settings screen for editing a user's diff
preferences. These controls duplicate the functionality found in the
diff preferences modal that appears in the gr-diff-view.

Bug: Issue 3911
Change-Id: Idb41b30852199f981e78671a6f1cd327a0265916
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html
index 08fee30..9014db3 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html
@@ -172,6 +172,63 @@
               on-tap="_handleSavePreferences"
               disabled="[[!_prefsChanged]]">Save Changes</gr-button>
         </fieldset>
+        <h2 class$="[[_computeHeaderClass(_diffPrefsChanged)]]">
+          Diff Preferences
+        </h2>
+        <fieldset id="diffPreferences">
+          <section>
+            <span class="title">Context</span>
+            <span class="value">
+              <select
+                  is="gr-select"
+                  bind-value="{{_diffPrefs.context}}">
+                <option value="3">3 lines</option>
+                <option value="10">10 lines</option>
+                <option value="25">25 lines</option>
+                <option value="50">50 lines</option>
+                <option value="75">75 lines</option>
+                <option value="100">100 lines</option>
+                <option value="-1">Whole file</option>
+              </select>
+            </span>
+          </section>
+          <section>
+            <span class="title">Columns</span>
+            <span class="value">
+              <input
+                  is="iron-input"
+                  type="number"
+                  prevent-invalid-input
+                  allowed-pattern="[0-9]"
+                  bind-value="{{_diffPrefs.line_length}}">
+            </span>
+          </section>
+          <section>
+            <span class="title">Tab Width</span>
+            <span class="value">
+              <input
+                  is="iron-input"
+                  type="number"
+                  prevent-invalid-input
+                  allowed-pattern="[0-9]"
+                  bind-value="{{_diffPrefs.tab_size}}">
+            </span>
+          </section>
+          <section>
+            <span class="title">Show Tabs</span>
+            <span class="value">
+              <input
+                  id="showTabs"
+                  type="checkbox"
+                  checked$="[[_diffPrefs.show_tabs]]"
+                  on-change="_handleShowTabsChanged">
+            </span>
+          </section>
+          <gr-button
+              id="saveDiffPrefs"
+              on-tap="_handleSaveDiffPreferences"
+              disabled$="[[!_diffPrefsChanged]]">Save Changes</gr-button>
+        </fieldset>
         <h2 class$="[[_computeHeaderClass(_menuChanged)]]">Menu</h2>
         <fieldset id="menu">
           <gr-menu-editor menu-items="{{_localMenu}}"></gr-menu-editor>
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
index 255565e..231ccc0 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
@@ -40,6 +40,7 @@
         type: Object,
         value: function() { return {}; },
       },
+      _diffPrefs: Object,
       _localPrefs: {
         type: Object,
         value: function() { return {}; },
@@ -57,6 +58,10 @@
         type: Boolean,
         value: false,
       },
+      _diffPrefsChanged: {
+        type: Boolean,
+        value: false,
+      },
       _menuChanged: {
         type: Boolean,
         value: false,
@@ -73,6 +78,7 @@
 
     observers: [
       '_handlePrefsChanged(_localPrefs.*)',
+      '_handleDiffPrefsChanged(_diffPrefs.*)',
       '_handleMenuChanged(_localMenu.splices)',
       '_handleProjectsChanged(_watchedProjects.*)',
     ],
@@ -92,6 +98,10 @@
         this._cloneMenu();
       }.bind(this)));
 
+      promises.push(this.$.restAPI.getDiffPreferences().then(function(prefs) {
+        this._diffPrefs = prefs;
+      }.bind(this)));
+
       promises.push(this.$.restAPI.getWatchedProjects().then(function(projs) {
         this._watchedProjects = projs;
       }.bind(this)));
@@ -130,6 +140,11 @@
       this._prefsChanged = true;
     },
 
+    _handleDiffPrefsChanged: function() {
+      if (this._loading || this._loading === undefined) { return; }
+      this._diffPrefsChanged = true;
+    },
+
     _handleMenuChanged: function () {
       if (this._loading || this._loading === undefined) { return; }
       this._menuChanged = true;
@@ -143,6 +158,17 @@
       }.bind(this));
     },
 
+    _handleShowTabsChanged: function() {
+      this.set('_diffPrefs.show_tabs', this.$.showTabs.checked);
+    },
+
+    _handleSaveDiffPreferences: function() {
+      return this.$.restAPI.saveDiffPreferences(this._diffPrefs)
+          .then(function() {
+            this._diffPrefsChanged = false;
+          }.bind(this));
+    },
+
     _handleSaveMenu: function() {
       this.set('prefs.my', this._localMenu);
       this._cloneMenu();
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
index f0c5369..8c063df 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
@@ -41,6 +41,7 @@
     var element;
     var account;
     var preferences;
+    var diffPreferences;
 
     function valueOf(title, fieldsetid) {
       var sections = element.$[fieldsetid].querySelectorAll('section');
@@ -82,12 +83,29 @@
           {url: '/second/url', name: 'second name', target: '_blank'},
         ],
       };
+      diffPreferences = {
+        context: 10,
+        tab_size: 8,
+        line_length: 100,
+        cursor_blink_rate: 0,
+        intraline_difference: true,
+        show_line_endings: true,
+        show_tabs: true,
+        show_whitespace_errors: true,
+        syntax_highlighting: true,
+        auto_hide_diff_table_header: true,
+        theme: 'DEFAULT',
+        ignore_whitespace: 'IGNORE_NONE'
+      };
       watchedProjects = [];
 
       stub('gr-rest-api-interface', {
         getLoggedIn: function() { return Promise.resolve(true); },
         getAccount: function() { return Promise.resolve(account); },
         getPreferences: function() { return Promise.resolve(preferences); },
+        getDiffPreferences: function() {
+          return Promise.resolve(diffPreferences);
+        },
         getWatchedProjects: function() {
           return Promise.resolve(watchedProjects);
         },
@@ -121,6 +139,8 @@
       var blank = fixture('blank');
       blank.appendChild(newElement);
 
+      Polymer.dom.flush();
+
       assert.isTrue(titleChangedStub.called);
       assert.equal(titleChangedStub.getCall(0).args[0].detail.title,
           'Settings');
@@ -166,6 +186,40 @@
       });
     });
 
+    test('diff preferences', function(done) {
+      // Rendered with the expected preferences selected.
+      assert.equal(valueOf('Context', 'diffPreferences')
+          .firstElementChild.bindValue, diffPreferences.context);
+      assert.equal(valueOf('Columns', 'diffPreferences')
+          .firstElementChild.bindValue, diffPreferences.line_length);
+      assert.equal(valueOf('Tab Width', 'diffPreferences')
+          .firstElementChild.bindValue, diffPreferences.tab_size);
+      assert.equal(valueOf('Show Tabs', 'diffPreferences')
+          .firstElementChild.checked, diffPreferences.show_tabs);
+
+      assert.isFalse(element._diffPrefsChanged);
+
+      var showTabsCheckbox = valueOf('Show Tabs', 'diffPreferences')
+          .firstElementChild;
+      showTabsCheckbox.checked = false;
+      element._handleShowTabsChanged();
+
+      assert.isTrue(element._diffPrefsChanged);
+
+      stub('gr-rest-api-interface', {
+        saveDiffPreferences: function(prefs) {
+          assert.equal(prefs.show_tabs, false);
+          return Promise.resolve();
+        }
+      });
+
+      // Save the change.
+      element._handleSaveDiffPreferences().then(function() {
+        assert.isFalse(element._diffPrefsChanged);
+        done();
+      });
+    });
+
     test('menu', function(done) {
       assert.isFalse(element._menuChanged);
       assert.isFalse(element._prefsChanged);