Convert files to typescript

The change converts the following files to typescript:

* elements/settings/gr-settings-view/gr-settings-view.ts

Change-Id: I758f919418b98d077efeb2ea42d23908e064e216
diff --git a/polygerrit-ui/app/constants/constants.ts b/polygerrit-ui/app/constants/constants.ts
index 2db076f..b326001 100644
--- a/polygerrit-ui/app/constants/constants.ts
+++ b/polygerrit-ui/app/constants/constants.ts
@@ -296,6 +296,16 @@
 }
 
 /**
+ * The type of email format to use.
+ * Doesn't mentioned in doc, but exists in Java class GeneralPreferencesInfo.
+ */
+
+export enum EmailFormat {
+  PLAINTEXT = 'PLAINTEXT',
+  HTML_PLAINTEXT = 'HTML_PLAINTEXT',
+}
+
+/**
  * The base which should be pre-selected in the 'Diff Against' drop-down list when the change screen is opened for a merge commit
  * https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#preferences-input
  */
diff --git a/polygerrit-ui/app/elements/gr-app-types.ts b/polygerrit-ui/app/elements/gr-app-types.ts
index ef382ad..2af925e 100644
--- a/polygerrit-ui/app/elements/gr-app-types.ts
+++ b/polygerrit-ui/app/elements/gr-app-types.ts
@@ -87,6 +87,12 @@
 }
 
 export interface AppElementJustRegisteredParams {
+  // We use params.view === ... as a type guard.
+  // The view?: never tells to the compiler that
+  // AppElementJustRegisteredParams can't have view property.
+  // Otherwise, the compiler reports an error when the code tries to use
+  // the property 'view' of AppElementParams.
+  view?: never;
   justRegistered: true;
 }
 
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
index 06d9183..cd0d7b8 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
@@ -14,40 +14,62 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import '@polymer/iron-input/iron-input.js';
-import '@polymer/paper-toggle-button/paper-toggle-button.js';
-import '../../../styles/gr-form-styles.js';
-import '../../../styles/gr-menu-page-styles.js';
-import '../../../styles/gr-page-nav-styles.js';
-import '../../../styles/shared-styles.js';
-import {applyTheme as applyDarkTheme, removeTheme as removeDarkTheme} from '../../../styles/themes/dark-theme.js';
-import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.js';
-import '../gr-change-table-editor/gr-change-table-editor.js';
-import '../../shared/gr-button/gr-button.js';
-import '../../shared/gr-date-formatter/gr-date-formatter.js';
-import '../../shared/gr-diff-preferences/gr-diff-preferences.js';
-import '../../shared/gr-page-nav/gr-page-nav.js';
-import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
-import '../../shared/gr-select/gr-select.js';
-import '../gr-account-info/gr-account-info.js';
-import '../gr-agreements-list/gr-agreements-list.js';
-import '../gr-edit-preferences/gr-edit-preferences.js';
-import '../gr-email-editor/gr-email-editor.js';
-import '../gr-gpg-editor/gr-gpg-editor.js';
-import '../gr-group-list/gr-group-list.js';
-import '../gr-http-password/gr-http-password.js';
-import '../gr-identities/gr-identities.js';
-import '../gr-menu-editor/gr-menu-editor.js';
-import '../gr-ssh-editor/gr-ssh-editor.js';
-import '../gr-watched-projects-editor/gr-watched-projects-editor.js';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
-import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
-import {PolymerElement} from '@polymer/polymer/polymer-element.js';
-import {htmlTemplate} from './gr-settings-view_html.js';
-import {getDocsBaseUrl} from '../../../utils/url-util.js';
-import {ChangeTableMixin} from '../../../mixins/gr-change-table-mixin/gr-change-table-mixin.js';
+import '@polymer/iron-input/iron-input';
+import '@polymer/paper-toggle-button/paper-toggle-button';
+import '../../../styles/gr-form-styles';
+import '../../../styles/gr-menu-page-styles';
+import '../../../styles/gr-page-nav-styles';
+import '../../../styles/shared-styles';
+import {
+  applyTheme as applyDarkTheme,
+  removeTheme as removeDarkTheme,
+} from '../../../styles/themes/dark-theme';
+import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
+import '../gr-change-table-editor/gr-change-table-editor';
+import '../../shared/gr-button/gr-button';
+import '../../shared/gr-date-formatter/gr-date-formatter';
+import '../../shared/gr-diff-preferences/gr-diff-preferences';
+import '../../shared/gr-page-nav/gr-page-nav';
+import '../../shared/gr-rest-api-interface/gr-rest-api-interface';
+import '../../shared/gr-select/gr-select';
+import '../gr-account-info/gr-account-info';
+import '../gr-agreements-list/gr-agreements-list';
+import '../gr-edit-preferences/gr-edit-preferences';
+import '../gr-email-editor/gr-email-editor';
+import '../gr-gpg-editor/gr-gpg-editor';
+import '../gr-group-list/gr-group-list';
+import '../gr-http-password/gr-http-password';
+import '../gr-identities/gr-identities';
+import '../gr-menu-editor/gr-menu-editor';
+import '../gr-ssh-editor/gr-ssh-editor';
+import '../gr-watched-projects-editor/gr-watched-projects-editor';
+import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
+import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
+import {PolymerElement} from '@polymer/polymer/polymer-element';
+import {htmlTemplate} from './gr-settings-view_html';
+import {getDocsBaseUrl} from '../../../utils/url-util';
+import {ChangeTableMixin} from '../../../mixins/gr-change-table-mixin/gr-change-table-mixin';
+import {customElement, property, observe} from '@polymer/decorators';
+import {AppElementParams} from '../../gr-app-types';
+import {GrAccountInfo} from '../gr-account-info/gr-account-info';
+import {GrWatchedProjectsEditor} from '../gr-watched-projects-editor/gr-watched-projects-editor';
+import {GrGroupList} from '../gr-group-list/gr-group-list';
+import {GrIdentities} from '../gr-identities/gr-identities';
+import {GrEditPreferences} from '../gr-edit-preferences/gr-edit-preferences';
+import {GrDiffPreferences} from '../../shared/gr-diff-preferences/gr-diff-preferences';
+import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api';
+import {
+  PreferencesInput,
+  ServerInfo,
+  TopMenuItemInfo,
+} from '../../../types/common';
+import {GrSshEditor} from '../gr-ssh-editor/gr-ssh-editor';
+import {GrGpgEditor} from '../gr-gpg-editor/gr-gpg-editor';
+import {GerritView} from '../../core/gr-navigation/gr-navigation';
+import {GrEmailEditor} from '../gr-email-editor/gr-email-editor';
+import {CustomKeyboardEvent} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
 
-const PREFS_SECTION_FIELDS = [
+const PREFS_SECTION_FIELDS: Array<keyof PreferencesInput> = [
   'changes_per_page',
   'date_format',
   'time_format',
@@ -62,26 +84,49 @@
   'relative_date_in_change_table',
 ];
 
-const GERRIT_DOCS_BASE_URL = 'https://gerrit-review.googlesource.com/' +
-    'Documentation';
+const GERRIT_DOCS_BASE_URL =
+  'https://gerrit-review.googlesource.com/' + 'Documentation';
 const GERRIT_DOCS_FILTER_PATH = '/user-notify.html';
 const ABSOLUTE_URL_PATTERN = /^https?:/;
 const TRAILING_SLASH_PATTERN = /\/$/;
 
-const HTTP_AUTH = [
-  'HTTP',
-  'HTTP_LDAP',
-];
+const HTTP_AUTH = ['HTTP', 'HTTP_LDAP'];
 
-/**
- * @extends PolymerElement
- */
-class GrSettingsView extends ChangeTableMixin(GestureEventListeners(
-    LegacyElementMixin(
-        PolymerElement))) {
-  static get template() { return htmlTemplate; }
+enum CopyPrefsDirection {
+  PrefsToLocalPrefs,
+  LocalPrefsToPrefs,
+}
 
-  static get is() { return 'gr-settings-view'; }
+type LocalMenuItemInfo = Omit<TopMenuItemInfo, 'id'>;
+
+export interface GrSettingsView {
+  $: {
+    restAPI: RestApiService & Element;
+    accountInfo: GrAccountInfo;
+    watchedProjectsEditor: GrWatchedProjectsEditor;
+    groupList: GrGroupList;
+    identities: GrIdentities;
+    editPrefs: GrEditPreferences;
+    diffPrefs: GrDiffPreferences;
+    sshEditor: GrSshEditor;
+    gpgEditor: GrGpgEditor;
+    emailEditor: GrEmailEditor;
+    insertSignedOff: HTMLInputElement;
+    workInProgressByDefault: HTMLInputElement;
+    showSizeBarsInFileList: HTMLInputElement;
+    publishCommentsOnPush: HTMLInputElement;
+    relativeDateInChangeTable: HTMLInputElement;
+  };
+}
+
+@customElement('gr-settings-view')
+export class GrSettingsView extends ChangeTableMixin(
+  GestureEventListeners(LegacyElementMixin(PolymerElement))
+) {
+  static get template() {
+    return htmlTemplate;
+  }
+
   /**
    * Fired when the title of the page should change.
    *
@@ -94,99 +139,79 @@
    * @event show-alert
    */
 
-  static get properties() {
-    return {
-      prefs: {
-        type: Object,
-        value() { return {}; },
-      },
-      params: {
-        type: Object,
-        value() { return {}; },
-      },
-      _accountInfoChanged: Boolean,
-      _changeTableColumnsNotDisplayed: Array,
-      /** @type {?} */
-      _localPrefs: {
-        type: Object,
-        value() { return {}; },
-      },
-      _localChangeTableColumns: {
-        type: Array,
-        value() { return []; },
-      },
-      _localMenu: {
-        type: Array,
-        value() { return []; },
-      },
-      _loading: {
-        type: Boolean,
-        value: true,
-      },
-      _changeTableChanged: {
-        type: Boolean,
-        value: false,
-      },
-      _prefsChanged: {
-        type: Boolean,
-        value: false,
-      },
-      /** @type {?} */
-      _diffPrefsChanged: Boolean,
-      /** @type {?} */
-      _editPrefsChanged: Boolean,
-      _menuChanged: {
-        type: Boolean,
-        value: false,
-      },
-      _watchedProjectsChanged: {
-        type: Boolean,
-        value: false,
-      },
-      _keysChanged: {
-        type: Boolean,
-        value: false,
-      },
-      _gpgKeysChanged: {
-        type: Boolean,
-        value: false,
-      },
-      _newEmail: String,
-      _addingEmail: {
-        type: Boolean,
-        value: false,
-      },
-      _lastSentVerificationEmail: {
-        type: String,
-        value: null,
-      },
-      /** @type {?} */
-      _serverConfig: Object,
-      /** @type {?string} */
-      _docsBaseUrl: String,
-      _emailsChanged: Boolean,
+  @property({type: Object})
+  prefs: PreferencesInput = {};
 
-      /**
-       * For testing purposes.
-       */
-      _loadingPromise: Object,
+  @property({type: Object})
+  params?: AppElementParams;
 
-      _showNumber: Boolean,
+  @property({type: Boolean})
+  _accountInfoChanged?: boolean;
 
-      _isDark: {
-        type: Boolean,
-        value: false,
-      },
-    };
-  }
+  @property({type: Array})
+  _changeTableColumnsNotDisplayed?: string[];
 
-  static get observers() {
-    return [
-      '_handlePrefsChanged(_localPrefs.*)',
-      '_handleMenuChanged(_localMenu.splices)',
-      '_handleChangeTableChanged(_localChangeTableColumns, _showNumber)',
-    ];
-  }
+  @property({type: Object})
+  _localPrefs: PreferencesInput = {};
+
+  @property({type: Array})
+  _localChangeTableColumns: string[] = [];
+
+  @property({type: Array})
+  _localMenu: LocalMenuItemInfo[] = [];
+
+  @property({type: Boolean})
+  _loading = true;
+
+  @property({type: Boolean})
+  _changeTableChanged = false;
+
+  @property({type: Boolean})
+  _prefsChanged = false;
+
+  @property({type: Boolean})
+  _diffPrefsChanged?: boolean;
+
+  @property({type: Boolean})
+  _editPrefsChanged?: boolean;
+
+  @property({type: Boolean})
+  _menuChanged = false;
+
+  @property({type: Boolean})
+  _watchedProjectsChanged = false;
+
+  @property({type: Boolean})
+  _keysChanged = false;
+
+  @property({type: Boolean})
+  _gpgKeysChanged = false;
+
+  @property({type: String})
+  _newEmail?: string;
+
+  @property({type: Boolean})
+  _addingEmail = false;
+
+  @property({type: String})
+  _lastSentVerificationEmail?: string | null = null;
+
+  @property({type: Object})
+  _serverConfig?: ServerInfo;
+
+  @property({type: String})
+  _docsBaseUrl?: string | null;
+
+  @property({type: Boolean})
+  _emailsChanged?: boolean;
+
+  @property({type: Boolean})
+  _showNumber?: boolean;
+
+  @property({type: Boolean})
+  _isDark = false;
+
+  public _testOnly_loadingPromise?: Promise<void>;
 
   /** @override */
   attached() {
@@ -194,14 +219,17 @@
     // Polymer 2: anchor tag won't work on shadow DOM
     // we need to manually calling scrollIntoView when hash changed
     this.listen(window, 'location-change', '_handleLocationChange');
-    this.dispatchEvent(new CustomEvent('title-change', {
-      detail: {title: 'Settings'},
-      composed: true, bubbles: true,
-    }));
+    this.dispatchEvent(
+      new CustomEvent('title-change', {
+        detail: {title: 'Settings'},
+        composed: true,
+        bubbles: true,
+      })
+    );
 
     this._isDark = !!window.localStorage.getItem('dark-theme');
 
-    const promises = [
+    const promises: Array<Promise<unknown>> = [
       this.$.accountInfo.loadData(),
       this.$.watchedProjectsEditor.loadData(),
       this.$.groupList.loadData(),
@@ -210,51 +238,70 @@
       this.$.diffPrefs.loadData(),
     ];
 
-    promises.push(this.$.restAPI.getPreferences().then(prefs => {
-      this.prefs = prefs;
-      this._showNumber = !!prefs.legacycid_in_change_table;
-      this._copyPrefs('_localPrefs', 'prefs');
-      this._cloneMenu(prefs.my);
-      this._cloneChangeTableColumns();
-    }));
+    promises.push(
+      this.$.restAPI.getPreferences().then(prefs => {
+        if (!prefs) {
+          throw new Error('getPreferences returned undefined');
+        }
+        this.prefs = prefs;
+        this._showNumber = !!prefs.legacycid_in_change_table;
+        this._copyPrefs(CopyPrefsDirection.PrefsToLocalPrefs);
+        this._cloneMenu(prefs.my);
+        this._cloneChangeTableColumns(prefs.change_table);
+      })
+    );
 
-    promises.push(this.$.restAPI.getConfig().then(config => {
-      this._serverConfig = config;
-      const configPromises = [];
+    promises.push(
+      this.$.restAPI.getConfig().then(config => {
+        this._serverConfig = config;
+        const configPromises: Array<Promise<void>> = [];
 
-      if (this._serverConfig && this._serverConfig.sshd) {
-        configPromises.push(this.$.sshEditor.loadData());
-      }
+        if (this._serverConfig && this._serverConfig.sshd) {
+          configPromises.push(this.$.sshEditor.loadData());
+        }
 
-      if (this._serverConfig &&
+        if (
+          this._serverConfig &&
           this._serverConfig.receive &&
-          this._serverConfig.receive.enable_signed_push) {
-        configPromises.push(this.$.gpgEditor.loadData());
-      }
+          this._serverConfig.receive.enable_signed_push
+        ) {
+          configPromises.push(this.$.gpgEditor.loadData());
+        }
 
-      configPromises.push(
-          getDocsBaseUrl(config, this.$.restAPI)
-              .then(baseUrl => { this._docsBaseUrl = baseUrl; }));
+        configPromises.push(
+          getDocsBaseUrl(config, this.$.restAPI).then(baseUrl => {
+            this._docsBaseUrl = baseUrl;
+          })
+        );
 
-      return Promise.all(configPromises);
-    }));
+        return Promise.all(configPromises);
+      })
+    );
 
-    if (this.params.emailToken) {
-      promises.push(this.$.restAPI.confirmEmail(this.params.emailToken).then(
-          message => {
-            if (message) {
-              this.dispatchEvent(new CustomEvent('show-alert', {
+    if (
+      this.params &&
+      this.params.view === GerritView.SETTINGS &&
+      this.params.emailToken
+    ) {
+      promises.push(
+        this.$.restAPI.confirmEmail(this.params.emailToken).then(message => {
+          if (message) {
+            this.dispatchEvent(
+              new CustomEvent('show-alert', {
                 detail: {message},
-                composed: true, bubbles: true,
-              }));
-            }
-            this.$.emailEditor.loadData();
-          }));
+                composed: true,
+                bubbles: true,
+              })
+            );
+          }
+          this.$.emailEditor.loadData();
+        })
+      );
     } else {
       promises.push(this.$.emailEditor.loadData());
     }
 
-    this._loadingPromise = Promise.all(promises).then(() => {
+    this._testOnly_loadingPromise = Promise.all(promises).then(() => {
       this._loading = false;
 
       // Handle anchor tag for initial load
@@ -262,7 +309,6 @@
     });
   }
 
-  /** @override */
   detached() {
     super.detached();
     this.unlisten(window, 'location-change', '_handleLocationChange');
@@ -281,24 +327,29 @@
   }
 
   reloadAccountDetail() {
-    Promise.all([
-      this.$.accountInfo.loadData(),
-      this.$.emailEditor.loadData(),
-    ]);
+    Promise.all([this.$.accountInfo.loadData(), this.$.emailEditor.loadData()]);
   }
 
   _isLoading() {
     return this._loading || this._loading === undefined;
   }
 
-  _copyPrefs(to, from) {
+  _copyPrefs(direction: CopyPrefsDirection) {
+    let to;
+    let from;
+    if (direction === CopyPrefsDirection.LocalPrefsToPrefs) {
+      from = this._localPrefs;
+      to = 'prefs';
+    } else {
+      from = this.prefs;
+      to = '_localPrefs';
+    }
     for (let i = 0; i < PREFS_SECTION_FIELDS.length; i++) {
-      this.set([to, PREFS_SECTION_FIELDS[i]],
-          this[from][PREFS_SECTION_FIELDS[i]]);
+      this.set([to, PREFS_SECTION_FIELDS[i]], from[PREFS_SECTION_FIELDS[i]]);
     }
   }
 
-  _cloneMenu(prefs) {
+  _cloneMenu(prefs: TopMenuItemInfo[]) {
     const menu = [];
     for (const item of prefs) {
       menu.push({
@@ -310,61 +361,73 @@
     this._localMenu = menu;
   }
 
-  _cloneChangeTableColumns() {
-    let columns = this.getVisibleColumns(this.prefs.change_table);
+  _cloneChangeTableColumns(changeTable: string[]) {
+    let columns = this.getVisibleColumns(changeTable);
 
     if (columns.length === 0) {
       columns = this.columnNames;
       this._changeTableColumnsNotDisplayed = [];
     } else {
       this._changeTableColumnsNotDisplayed = this.getComplementColumns(
-          this.prefs.change_table);
+        changeTable
+      );
     }
     this._localChangeTableColumns = columns;
   }
 
-  _formatChangeTableColumns(changeTableArray) {
-    return changeTableArray.map(item => {
-      return {column: item};
-    });
-  }
-
+  @observe('_localChangeTableColumns', '_showNumber')
   _handleChangeTableChanged() {
-    if (this._isLoading()) { return; }
+    if (this._isLoading()) {
+      return;
+    }
     this._changeTableChanged = true;
   }
 
-  _handlePrefsChanged(prefs) {
-    if (this._isLoading()) { return; }
+  @observe('_localPrefs.*')
+  _handlePrefsChanged() {
+    if (this._isLoading()) {
+      return;
+    }
     this._prefsChanged = true;
   }
 
   _handleRelativeDateInChangeTable() {
-    this.set('_localPrefs.relative_date_in_change_table',
-        this.$.relativeDateInChangeTable.checked);
+    this.set(
+      '_localPrefs.relative_date_in_change_table',
+      this.$.relativeDateInChangeTable.checked
+    );
   }
 
   _handleShowSizeBarsInFileListChanged() {
-    this.set('_localPrefs.size_bar_in_change_table',
-        this.$.showSizeBarsInFileList.checked);
+    this.set(
+      '_localPrefs.size_bar_in_change_table',
+      this.$.showSizeBarsInFileList.checked
+    );
   }
 
   _handlePublishCommentsOnPushChanged() {
-    this.set('_localPrefs.publish_comments_on_push',
-        this.$.publishCommentsOnPush.checked);
+    this.set(
+      '_localPrefs.publish_comments_on_push',
+      this.$.publishCommentsOnPush.checked
+    );
   }
 
   _handleWorkInProgressByDefault() {
-    this.set('_localPrefs.work_in_progress_by_default',
-        this.$.workInProgressByDefault.checked);
+    this.set(
+      '_localPrefs.work_in_progress_by_default',
+      this.$.workInProgressByDefault.checked
+    );
   }
 
   _handleInsertSignedOff() {
     this.set('_localPrefs.signed_off_by', this.$.insertSignedOff.checked);
   }
 
+  @observe('_localMenu.splices')
   _handleMenuChanged() {
-    if (this._isLoading()) { return; }
+    if (this._isLoading()) {
+      return;
+    }
     this._menuChanged = true;
   }
 
@@ -373,7 +436,7 @@
   }
 
   _handleSavePreferences() {
-    this._copyPrefs('prefs', '_localPrefs');
+    this._copyPrefs(CopyPrefsDirection.LocalPrefsToPrefs);
 
     return this.$.restAPI.savePreferences(this.prefs).then(() => {
       this._prefsChanged = false;
@@ -383,7 +446,7 @@
   _handleSaveChangeTable() {
     this.set('prefs.change_table', this._localChangeTableColumns);
     this.set('prefs.legacycid_in_change_table', this._showNumber);
-    this._cloneChangeTableColumns();
+    this._cloneChangeTableColumns(this._localChangeTableColumns);
     return this.$.restAPI.savePreferences(this.prefs).then(() => {
       this._changeTableChanged = false;
     });
@@ -399,7 +462,7 @@
 
   _handleSaveMenu() {
     this.set('prefs.my', this._localMenu);
-    this._cloneMenu(this.prefs.my);
+    this._cloneMenu(this._localMenu);
     return this.$.restAPI.savePreferences(this.prefs).then(() => {
       this._menuChanged = false;
     });
@@ -407,7 +470,7 @@
 
   _handleResetMenuButton() {
     return this.$.restAPI.getDefaultPreferences().then(data => {
-      if (data && data.my) {
+      if (data?.my) {
         this._cloneMenu(data.my);
       }
     });
@@ -417,7 +480,7 @@
     this.$.watchedProjectsEditor.save();
   }
 
-  _computeHeaderClass(changed) {
+  _computeHeaderClass(changed?: boolean) {
     return changed ? 'edited' : '';
   }
 
@@ -425,37 +488,40 @@
     this.$.emailEditor.save();
   }
 
-  _handleNewEmailKeydown(e) {
-    if (e.keyCode === 13) { // Enter
+  _handleNewEmailKeydown(e: CustomKeyboardEvent) {
+    if (e.keyCode === 13) {
+      // Enter
       e.stopPropagation();
       this._handleAddEmailButton();
     }
   }
 
-  _isNewEmailValid(newEmail) {
-    return newEmail && newEmail.includes('@');
+  _isNewEmailValid(newEmail?: string): newEmail is string {
+    return !!newEmail && newEmail.includes('@');
   }
 
-  _computeAddEmailButtonEnabled(newEmail, addingEmail) {
+  _computeAddEmailButtonEnabled(newEmail?: string, addingEmail?: boolean) {
     return this._isNewEmailValid(newEmail) && !addingEmail;
   }
 
   _handleAddEmailButton() {
-    if (!this._isNewEmailValid(this._newEmail)) { return; }
+    if (!this._isNewEmailValid(this._newEmail)) return;
 
     this._addingEmail = true;
     this.$.restAPI.addAccountEmail(this._newEmail).then(response => {
       this._addingEmail = false;
 
       // If it was unsuccessful.
-      if (response.status < 200 || response.status >= 300) { return; }
+      if (response.status < 200 || response.status >= 300) {
+        return;
+      }
 
       this._lastSentVerificationEmail = this._newEmail;
       this._newEmail = '';
     });
   }
 
-  _getFilterDocsLink(docsBaseUrl) {
+  _getFilterDocsLink(docsBaseUrl?: string) {
     let base = docsBaseUrl;
     if (!base || !ABSOLUTE_URL_PATTERN.test(base)) {
       base = GERRIT_DOCS_BASE_URL;
@@ -476,20 +542,22 @@
       applyDarkTheme();
     }
     this._isDark = !!window.localStorage.getItem('dark-theme');
-    this.dispatchEvent(new CustomEvent('show-alert', {
-      detail: {
-        message: `Theme changed to ${this._isDark ? 'dark' : 'light'}.`,
-      },
-      bubbles: true,
-      composed: true,
-    }));
+    this.dispatchEvent(
+      new CustomEvent('show-alert', {
+        detail: {
+          message: `Theme changed to ${this._isDark ? 'dark' : 'light'}.`,
+        },
+        bubbles: true,
+        composed: true,
+      })
+    );
   }
 
-  _showHttpAuth(config) {
-    if (config && config.auth &&
-        config.auth.git_basic_auth_policy) {
+  _showHttpAuth(config?: ServerInfo) {
+    if (config && config.auth && config.auth.git_basic_auth_policy) {
       return HTTP_AUTH.includes(
-          config.auth.git_basic_auth_policy.toUpperCase());
+        config.auth.git_basic_auth_policy.toUpperCase()
+      );
     }
 
     return false;
@@ -498,9 +566,13 @@
   /**
    * Work around a issue on iOS when clicking turns into double tap
    */
-  _onTapDarkToggle(e) {
+  _onTapDarkToggle(e: Event) {
     e.preventDefault();
   }
 }
 
-customElements.define(GrSettingsView.is, GrSettingsView);
+declare global {
+  interface HTMLElementTagNameMap {
+    'gr-settings-view': GrSettingsView;
+  }
+}
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.js b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.js
index 1929f4e..0535e15 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.js
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.js
@@ -19,6 +19,7 @@
 import {getComputedStyleValue} from '../../../utils/dom-util.js';
 import './gr-settings-view.js';
 import {flush} from '@polymer/polymer/lib/legacy/polymer.dom.js';
+import {GerritView} from '../../core/gr-navigation/gr-navigation.js';
 
 const basicFixture = fixtureFromElement('gr-settings-view');
 const blankFixture = fixtureFromElement('div');
@@ -95,7 +96,7 @@
     element = basicFixture.instantiate();
 
     // Allow the element to render.
-    element._loadingPromise.then(done);
+    element._testOnly_loadingPromise.then(done);
   });
 
   test('theme changing', () => {
@@ -485,7 +486,7 @@
           .callsFake(
               () => new Promise(
                   resolve => { resolveConfirm = resolve; }));
-      element.params = {emailToken: 'foo'};
+      element.params = {view: GerritView.SETTINGS, emailToken: 'foo'};
       element.attached();
     });
 
@@ -499,7 +500,7 @@
     });
 
     test('user emails are loaded after email confirmed', done => {
-      element._loadingPromise.then(() => {
+      element._testOnly_loadingPromise.then(() => {
         assert.isTrue(element.$.emailEditor.loadData.calledOnce);
         done();
       });
@@ -508,7 +509,7 @@
 
     test('show-alert is fired when email is confirmed', done => {
       sinon.spy(element, 'dispatchEvent');
-      element._loadingPromise.then(() => {
+      element._testOnly_loadingPromise.then(() => {
         assert.equal(
             element.dispatchEvent.lastCall.args[0].type, 'show-alert');
         assert.deepEqual(
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts
index 8cc9c79..f4acf7a 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts
@@ -1200,11 +1200,11 @@
       );
   }
 
-  getDefaultPreferences() {
+  getDefaultPreferences(): Promise<PreferencesInfo | undefined> {
     return this._fetchSharedCacheURL({
       url: '/config/server/preferences',
       reportUrlAsIs: true,
-    });
+    }) as Promise<PreferencesInfo | undefined>;
   }
 
   getPreferences(): Promise<PreferencesInfo | undefined> {
@@ -3180,7 +3180,7 @@
     });
   }
 
-  confirmEmail(token: string) {
+  confirmEmail(token: string): Promise<string | null> {
     const req = {
       method: HttpMethod.PUT,
       url: '/config/server/email.confirm',
diff --git a/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts b/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts
index 836082b..0b0ad9e 100644
--- a/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts
+++ b/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts
@@ -766,4 +766,15 @@
     changeNum: NumericChangeId,
     patchNum: PatchSetNum
   ): Promise<ActionNameToActionInfoMap | undefined>;
+
+  confirmEmail(token: string): Promise<string | null>;
+
+  getDefaultPreferences(): Promise<PreferencesInfo | undefined>;
+
+  addAccountEmail(email: string): Promise<Response>;
+
+  addAccountEmail(
+    email: string,
+    errFn?: ErrorCallback
+  ): Promise<Response | undefined>;
 }
diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts
index a3f9962..481828a 100644
--- a/polygerrit-ui/app/types/common.ts
+++ b/polygerrit-ui/app/types/common.ts
@@ -42,6 +42,7 @@
   DiffViewMode,
   DraftsAction,
   NotifyType,
+  EmailFormat,
 } from '../constants/constants';
 import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
 
@@ -1036,15 +1037,24 @@
   download: DownloadInfo;
   gerrit: GerritInfo;
   index: IndexConfigInfo;
-  note_db_enabled: boolean;
+  note_db_enabled?: boolean;
   plugin: PluginConfigInfo;
   receive?: ReceiveInfo;
+  sshd?: SshdInfo;
   suggest: SuggestInfo;
   user: UserConfigInfo;
   default_theme?: string;
 }
 
 /**
+ * The SshdInfo entity contains information about Gerrit configuration from the sshd section.
+ * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#sshd-info
+ * This entity doesn’t contain any data, but the presence of this (empty) entity
+ * in the ServerInfo entity means that SSHD is enabled on the server.
+ */
+export type SshdInfo = {};
+
+/**
  * The SuggestInfo entity contains information about Gerritconfiguration from
  * the suggest section.
  * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#suggest-info
@@ -1737,25 +1747,11 @@
 /**
  * The PreferencesInput entity contains information for setting the user preferences. Fields which are not set will not be updated
  * https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#preferences-input
+ *
+ * Note: the doc missed several properties. Java code uses the same class (GeneralPreferencesInfo)
+ * both for input data and for response data.
  */
-export interface PreferencesInput {
-  changes_per_page?: 10 | 25 | 50 | 100;
-  theme?: AppTheme;
-  expand_inline_diffs?: boolean;
-  download_scheme?: string;
-  date_format?: DateFormat;
-  time_format?: TimeFormat;
-  relative_date_in_change_table?: boolean;
-  diff_view?: DiffViewMode;
-  size_bar_in_change_table?: boolean;
-  legacycid_in_change_table?: boolean;
-  mute_common_path_prefixes?: boolean;
-  signed_off_by?: boolean;
-  my?: TopMenuItemInfo[];
-  change_table?: string[];
-  email_strategy?: EmailStrategy;
-  default_base_for_merges?: DefaultBase;
-}
+export type PreferencesInput = Partial<PreferencesInfo>;
 
 /**
  * The DiffPreferencesInput entity contains information for setting the diff preferences of a user. Fields which are not set will not be updated
@@ -1852,6 +1848,8 @@
   default_base_for_merges: DefaultBase;
   publish_comments_on_push?: boolean;
   work_in_progress_by_default?: boolean;
+  // The email_format doesn't mentioned in doc, but exists in Java class GeneralPreferencesInfo
+  email_format?: EmailFormat;
   // The following property doesn't exist in RestAPI, it is added by GrRestApiInterface
   default_diff_view?: DiffViewMode;
 }
diff --git a/polygerrit-ui/app/utils/url-util.ts b/polygerrit-ui/app/utils/url-util.ts
index 612f05c..0c6fabc 100644
--- a/polygerrit-ui/app/utils/url-util.ts
+++ b/polygerrit-ui/app/utils/url-util.ts
@@ -29,13 +29,10 @@
 /**
  * Get the docs base URL from either the server config or by probing.
  *
- * @param config The server config.
- * @param restApi A REST API instance
- * @return A promise that resolves with the docs base
- *     URL.
+ * @return A promise that resolves with the docs base URL.
  */
 export function getDocsBaseUrl(
-  config: ServerInfo,
+  config: ServerInfo | undefined,
   restApi: RestApiService
 ): Promise<string | null> {
   if (!getDocsBaseUrlCachedPromise) {