Merge "Fix documentation for disable_keyboard_shortcuts"
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index 4d0bb1f..20d66a5 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -108,7 +108,6 @@
     "elements/change/gr-thread-list/gr-thread-list_html.ts",
     "elements/checks/gr-hovercard-run_html.ts",
     "elements/core/gr-main-header/gr-main-header_html.ts",
-    "elements/core/gr-search-bar/gr-search-bar_html.ts",
     "elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_html.ts",
     "elements/diff/gr-diff-builder/gr-diff-builder-element_html.ts",
     "elements/diff/gr-diff-host/gr-diff-host_html.ts",
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
index e30e75e..841089e 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
@@ -180,7 +180,7 @@
   accountSuggestions: SuggestionProvider = () => Promise.resolve([]);
 
   @property({type: String})
-  _inputVal?: string;
+  _inputVal = '';
 
   @property({type: Number})
   _threshold = 1;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts
index c8b3901..f418cfa 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts
@@ -23,7 +23,7 @@
   lineNumberToNumber,
 } from '../gr-diff/gr-diff-utils';
 
-const tokenMatcher = new RegExp(/[a-zA-Z0-9_-]+/g);
+const tokenMatcher = new RegExp(/[\w]+/g);
 
 /** CSS class for all tokens. */
 const CSS_TOKEN = 'token';
diff --git a/polygerrit-ui/app/utils/async-util_test.js b/polygerrit-ui/app/utils/async-util_test.js
deleted file mode 100644
index df29e97..0000000
--- a/polygerrit-ui/app/utils/async-util_test.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * @license
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import '../test/common-test-setup-karma.js';
-import {asyncForeach} from './async-util.js';
-
-suite('async-util tests', () => {
-  test('loops over each item', () => {
-    const fn = sinon.stub().returns(Promise.resolve());
-    return asyncForeach([1, 2, 3], fn)
-        .then(() => {
-          assert.isTrue(fn.calledThrice);
-          assert.equal(fn.getCall(0).args[0], 1);
-          assert.equal(fn.getCall(1).args[0], 2);
-          assert.equal(fn.getCall(2).args[0], 3);
-        });
-  });
-
-  test('halts on stop condition', () => {
-    const stub = sinon.stub();
-    const fn = (e, stop) => {
-      stub(e);
-      stop();
-      return Promise.resolve();
-    };
-    return asyncForeach([1, 2, 3], fn)
-        .then(() => {
-          assert.isTrue(stub.calledOnce);
-          assert.equal(stub.lastCall.args[0], 1);
-        });
-  });
-});
diff --git a/polygerrit-ui/app/utils/async-util_test.ts b/polygerrit-ui/app/utils/async-util_test.ts
new file mode 100644
index 0000000..5c8f610
--- /dev/null
+++ b/polygerrit-ui/app/utils/async-util_test.ts
@@ -0,0 +1,46 @@
+/**
+ * @license
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import '../test/common-test-setup-karma';
+import {asyncForeach} from './async-util';
+
+suite('async-util tests', () => {
+  test('loops over each item', async () => {
+    const fn = sinon.stub().resolves();
+
+    await asyncForeach([1, 2, 3], fn);
+
+    assert.isTrue(fn.calledThrice);
+    assert.equal(fn.firstCall.firstArg, 1);
+    assert.equal(fn.secondCall.firstArg, 2);
+    assert.equal(fn.thirdCall.firstArg, 3);
+  });
+
+  test('halts on stop condition', async () => {
+    const stub = sinon.stub();
+    const fn = (item: number, stopCallback: () => void) => {
+      stub(item);
+      stopCallback();
+      return Promise.resolve();
+    };
+
+    await asyncForeach([1, 2, 3], fn);
+
+    assert.isTrue(stub.calledOnce);
+    assert.equal(stub.lastCall.firstArg, 1);
+  });
+});
diff --git a/polygerrit-ui/app/utils/date-util_test.js b/polygerrit-ui/app/utils/date-util_test.js
deleted file mode 100644
index 96d5bc1..0000000
--- a/polygerrit-ui/app/utils/date-util_test.js
+++ /dev/null
@@ -1,144 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import '../test/common-test-setup-karma.js';
-import {isValidDate, parseDate, fromNow, isWithinDay, isWithinHalfYear, formatDate, wasYesterday} from './date-util.js';
-
-suite('date-util tests', () => {
-  suite('parseDate', () => {
-    test('parseDate server date', () => {
-      const parsed = parseDate('2015-09-15 20:34:00.000000000');
-      assert.equal('2015-09-15T20:34:00.000Z', parsed.toISOString());
-    });
-  });
-
-  suite('isValidDate', () => {
-    test('date is valid', () => {
-      assert.isTrue(isValidDate(new Date()));
-    });
-    test('broken date is invalid', () => {
-      assert.isFalse(isValidDate(new Date('xxx')));
-    });
-  });
-
-  suite('fromNow', () => {
-    test('test all variants', () => {
-      const fakeNow = new Date('May 08 2020 12:00:00');
-      sinon.useFakeTimers(fakeNow.getTime());
-      assert.equal('just now', fromNow(new Date('May 08 2020 11:59:30')));
-      assert.equal('1 minute ago', fromNow(new Date('May 08 2020 11:59:00')));
-      assert.equal('5 minutes ago', fromNow(new Date('May 08 2020 11:55:00')));
-      assert.equal('1 hour ago', fromNow(new Date('May 08 2020 11:00:00')));
-      assert.equal(
-          '1 hour 5 min ago', fromNow(new Date('May 08 2020 10:55:00')));
-      assert.equal('3 hours ago', fromNow(new Date('May 08 2020 9:00:00')));
-      assert.equal('1 day ago', fromNow(new Date('May 07 2020 12:00:00')));
-      assert.equal('1 day 2 hr ago', fromNow(new Date('May 07 2020 10:00:00')));
-      assert.equal('3 days ago', fromNow(new Date('May 05 2020 12:00:00')));
-      assert.equal('1 month ago', fromNow(new Date('Apr 05 2020 12:00:00')));
-      assert.equal('2 months ago', fromNow(new Date('Mar 05 2020 12:00:00')));
-      assert.equal('1 year ago', fromNow(new Date('May 05 2019 12:00:00')));
-      assert.equal('10 years ago', fromNow(new Date('May 05 2010 12:00:00')));
-    });
-    test('rounding error', () => {
-      const fakeNow = new Date('May 08 2020 12:00:00');
-      sinon.useFakeTimers(fakeNow.getTime());
-      assert.equal('2 hours ago', fromNow(new Date('May 08 2020 9:30:00')));
-    });
-  });
-
-  suite('isWithinDay', () => {
-    test('basics works', () => {
-      assert.isTrue(isWithinDay(new Date('May 08 2020 12:00:00'),
-          new Date('May 08 2020 02:00:00')));
-      assert.isFalse(isWithinDay(new Date('May 08 2020 12:00:00'),
-          new Date('May 07 2020 12:00:00')));
-    });
-  });
-
-  suite('wasYesterday', () => {
-    test('less 24 hours', () => {
-      assert.isFalse(wasYesterday(new Date('May 08 2020 12:00:00'),
-          new Date('May 08 2020 02:00:00')));
-      assert.isTrue(wasYesterday(new Date('May 08 2020 12:00:00'),
-          new Date('May 07 2020 12:00:00')));
-    });
-    test('more 24 hours', () => {
-      assert.isTrue(wasYesterday(new Date('May 08 2020 12:00:00'),
-          new Date('May 07 2020 2:00:00')));
-      assert.isFalse(wasYesterday(new Date('May 08 2020 12:00:00'),
-          new Date('May 06 2020 14:00:00')));
-    });
-  });
-
-  suite('isWithinHalfYear', () => {
-    test('basics works', () => {
-      assert.isTrue(isWithinHalfYear(new Date('May 08 2020 12:00:00'),
-          new Date('Feb 08 2020 12:00:00')));
-      assert.isFalse(isWithinHalfYear(new Date('May 08 2020 12:00:00'),
-          new Date('Nov 07 2019 12:00:00')));
-    });
-  });
-
-  suite('formatDate', () => {
-    test('works for standard format', () => {
-      const stdFormat = 'MMM DD, YYYY';
-      assert.equal('May 08, 2020',
-          formatDate(new Date('May 08 2020 12:00:00'), stdFormat));
-      assert.equal('Feb 28, 2020',
-          formatDate(new Date('Feb 28 2020 12:00:00'), stdFormat));
-
-      const time24Format = 'HH:mm:ss';
-      assert.equal('Feb 28, 2020 12:01:12',
-          formatDate(new Date('Feb 28 2020 12:01:12'), stdFormat + ' '
-          + time24Format));
-    });
-    test('works for euro format', () => {
-      const euroFormat = 'DD.MM.YYYY';
-      assert.equal('01.12.2019',
-          formatDate(new Date('Dec 01 2019 12:00:00'), euroFormat));
-      assert.equal('20.01.2002',
-          formatDate(new Date('Jan 20 2002 12:00:00'), euroFormat));
-
-      const time24Format = 'HH:mm:ss';
-      assert.equal('28.02.2020 00:01:12',
-          formatDate(new Date('Feb 28 2020 00:01:12'), euroFormat + ' '
-          + time24Format));
-    });
-    test('works for iso format', () => {
-      const isoFormat = 'YYYY-MM-DD';
-      assert.equal('2015-01-01',
-          formatDate(new Date('Jan 01 2015 12:00:00'), isoFormat));
-      assert.equal('2013-07-03',
-          formatDate(new Date('Jul 03 2013 12:00:00'), isoFormat));
-
-      const timeFormat = 'h:mm:ss A';
-      assert.equal('2013-07-03 5:00:00 AM',
-          formatDate(new Date('Jul 03 2013 05:00:00'), isoFormat + ' '
-          + timeFormat));
-      assert.equal('2013-07-03 5:00:00 PM',
-          formatDate(new Date('Jul 03 2013 17:00:00'), isoFormat + ' '
-          + timeFormat));
-    });
-    test('h:mm:ss A shows correctly midnight and midday', () => {
-      const timeFormat = 'h:mm A';
-      assert.equal('12:14 PM',
-          formatDate(new Date('Jul 03 2013 12:14:00'), timeFormat));
-      assert.equal('12:15 AM',
-          formatDate(new Date('Jul 03 2013 00:15:00'), timeFormat));
-    });
-  });
-});
diff --git a/polygerrit-ui/app/utils/date-util_test.ts b/polygerrit-ui/app/utils/date-util_test.ts
new file mode 100644
index 0000000..f17ced3
--- /dev/null
+++ b/polygerrit-ui/app/utils/date-util_test.ts
@@ -0,0 +1,219 @@
+/**
+ * @license
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {Timestamp} from '../types/common';
+import '../test/common-test-setup-karma';
+import {
+  isValidDate,
+  parseDate,
+  fromNow,
+  isWithinDay,
+  isWithinHalfYear,
+  formatDate,
+  wasYesterday,
+} from './date-util';
+
+suite('date-util tests', () => {
+  suite('parseDate', () => {
+    test('parseDate server date', () => {
+      const parsed = parseDate('2015-09-15 20:34:00.000000000' as Timestamp);
+      assert.equal('2015-09-15T20:34:00.000Z', parsed.toISOString());
+    });
+  });
+
+  suite('isValidDate', () => {
+    test('date is valid', () => {
+      assert.isTrue(isValidDate(new Date()));
+    });
+    test('broken date is invalid', () => {
+      assert.isFalse(isValidDate(new Date('xxx')));
+    });
+  });
+
+  suite('fromNow', () => {
+    test('test all variants', () => {
+      const fakeNow = new Date('May 08 2020 12:00:00');
+      sinon.useFakeTimers(fakeNow.getTime());
+      assert.equal('just now', fromNow(new Date('May 08 2020 11:59:30')));
+      assert.equal('1 minute ago', fromNow(new Date('May 08 2020 11:59:00')));
+      assert.equal('5 minutes ago', fromNow(new Date('May 08 2020 11:55:00')));
+      assert.equal('1 hour ago', fromNow(new Date('May 08 2020 11:00:00')));
+      assert.equal(
+        '1 hour 5 min ago',
+        fromNow(new Date('May 08 2020 10:55:00'))
+      );
+      assert.equal('3 hours ago', fromNow(new Date('May 08 2020 9:00:00')));
+      assert.equal('1 day ago', fromNow(new Date('May 07 2020 12:00:00')));
+      assert.equal('1 day 2 hr ago', fromNow(new Date('May 07 2020 10:00:00')));
+      assert.equal('3 days ago', fromNow(new Date('May 05 2020 12:00:00')));
+      assert.equal('1 month ago', fromNow(new Date('Apr 05 2020 12:00:00')));
+      assert.equal('2 months ago', fromNow(new Date('Mar 05 2020 12:00:00')));
+      assert.equal('1 year ago', fromNow(new Date('May 05 2019 12:00:00')));
+      assert.equal('10 years ago', fromNow(new Date('May 05 2010 12:00:00')));
+    });
+    test('rounding error', () => {
+      const fakeNow = new Date('May 08 2020 12:00:00');
+      sinon.useFakeTimers(fakeNow.getTime());
+      assert.equal('2 hours ago', fromNow(new Date('May 08 2020 9:30:00')));
+    });
+  });
+
+  suite('isWithinDay', () => {
+    test('basics works', () => {
+      assert.isTrue(
+        isWithinDay(
+          new Date('May 08 2020 12:00:00'),
+          new Date('May 08 2020 02:00:00')
+        )
+      );
+      assert.isFalse(
+        isWithinDay(
+          new Date('May 08 2020 12:00:00'),
+          new Date('May 07 2020 12:00:00')
+        )
+      );
+    });
+  });
+
+  suite('wasYesterday', () => {
+    test('less 24 hours', () => {
+      assert.isFalse(
+        wasYesterday(
+          new Date('May 08 2020 12:00:00'),
+          new Date('May 08 2020 02:00:00')
+        )
+      );
+      assert.isTrue(
+        wasYesterday(
+          new Date('May 08 2020 12:00:00'),
+          new Date('May 07 2020 12:00:00')
+        )
+      );
+    });
+    test('more 24 hours', () => {
+      assert.isTrue(
+        wasYesterday(
+          new Date('May 08 2020 12:00:00'),
+          new Date('May 07 2020 2:00:00')
+        )
+      );
+      assert.isFalse(
+        wasYesterday(
+          new Date('May 08 2020 12:00:00'),
+          new Date('May 06 2020 14:00:00')
+        )
+      );
+    });
+  });
+
+  suite('isWithinHalfYear', () => {
+    test('basics works', () => {
+      assert.isTrue(
+        isWithinHalfYear(
+          new Date('May 08 2020 12:00:00'),
+          new Date('Feb 08 2020 12:00:00')
+        )
+      );
+      assert.isFalse(
+        isWithinHalfYear(
+          new Date('May 08 2020 12:00:00'),
+          new Date('Nov 07 2019 12:00:00')
+        )
+      );
+    });
+  });
+
+  suite('formatDate', () => {
+    test('works for standard format', () => {
+      const stdFormat = 'MMM DD, YYYY';
+      assert.equal(
+        'May 08, 2020',
+        formatDate(new Date('May 08 2020 12:00:00'), stdFormat)
+      );
+      assert.equal(
+        'Feb 28, 2020',
+        formatDate(new Date('Feb 28 2020 12:00:00'), stdFormat)
+      );
+
+      const time24Format = 'HH:mm:ss';
+      assert.equal(
+        'Feb 28, 2020 12:01:12',
+        formatDate(
+          new Date('Feb 28 2020 12:01:12'),
+          stdFormat + ' ' + time24Format
+        )
+      );
+    });
+    test('works for euro format', () => {
+      const euroFormat = 'DD.MM.YYYY';
+      assert.equal(
+        '01.12.2019',
+        formatDate(new Date('Dec 01 2019 12:00:00'), euroFormat)
+      );
+      assert.equal(
+        '20.01.2002',
+        formatDate(new Date('Jan 20 2002 12:00:00'), euroFormat)
+      );
+
+      const time24Format = 'HH:mm:ss';
+      assert.equal(
+        '28.02.2020 00:01:12',
+        formatDate(
+          new Date('Feb 28 2020 00:01:12'),
+          euroFormat + ' ' + time24Format
+        )
+      );
+    });
+    test('works for iso format', () => {
+      const isoFormat = 'YYYY-MM-DD';
+      assert.equal(
+        '2015-01-01',
+        formatDate(new Date('Jan 01 2015 12:00:00'), isoFormat)
+      );
+      assert.equal(
+        '2013-07-03',
+        formatDate(new Date('Jul 03 2013 12:00:00'), isoFormat)
+      );
+
+      const timeFormat = 'h:mm:ss A';
+      assert.equal(
+        '2013-07-03 5:00:00 AM',
+        formatDate(
+          new Date('Jul 03 2013 05:00:00'),
+          isoFormat + ' ' + timeFormat
+        )
+      );
+      assert.equal(
+        '2013-07-03 5:00:00 PM',
+        formatDate(
+          new Date('Jul 03 2013 17:00:00'),
+          isoFormat + ' ' + timeFormat
+        )
+      );
+    });
+    test('h:mm:ss A shows correctly midnight and midday', () => {
+      const timeFormat = 'h:mm A';
+      assert.equal(
+        '12:14 PM',
+        formatDate(new Date('Jul 03 2013 12:14:00'), timeFormat)
+      );
+      assert.equal(
+        '12:15 AM',
+        formatDate(new Date('Jul 03 2013 00:15:00'), timeFormat)
+      );
+    });
+  });
+});
diff --git a/polygerrit-ui/app/utils/display-name-util_test.js b/polygerrit-ui/app/utils/display-name-util_test.js
deleted file mode 100644
index 9bb68dc..0000000
--- a/polygerrit-ui/app/utils/display-name-util_test.js
+++ /dev/null
@@ -1,200 +0,0 @@
-/**
- * @license
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import '../test/common-test-setup-karma.js';
-import {getDisplayName, getUserName, getGroupDisplayName, getAccountDisplayName, _testOnly_accountEmail} from './display-name-util.js';
-
-suite('display-name-utils tests', () => {
-  // eslint-disable-next-line no-unused-vars
-  const config = {
-    user: {
-      anonymous_coward_name: 'Anonymous Coward',
-    },
-  };
-
-  test('getDisplayName name only', () => {
-    const account = {
-      name: 'test-name',
-    };
-    assert.equal(getDisplayName(config, account),
-        'test-name');
-  });
-
-  test('getDisplayName prefer displayName', () => {
-    const account = {
-      name: 'test-name',
-      display_name: 'better-name',
-    };
-    assert.equal(getDisplayName(config, account),
-        'better-name');
-  });
-
-  test('getDisplayName prefer username default', () => {
-    const account = {
-      name: 'test-name',
-      username: 'user-name',
-    };
-    const config = {
-      accounts: {
-        default_display_name: 'USERNAME',
-      },
-    };
-    assert.equal(getDisplayName(config, account),
-        'user-name');
-  });
-
-  test('getDisplayName firstNameOnly', () => {
-    const account = {
-      name: 'firstname lastname',
-    };
-    assert.equal(getDisplayName(config, account, true), 'firstname');
-  });
-
-  test('getDisplayName prefer first name default', () => {
-    const account = {
-      name: 'firstname lastname',
-    };
-    const config = {
-      accounts: {
-        default_display_name: 'FIRST_NAME',
-      },
-    };
-    assert.equal(getDisplayName(config, account),
-        'firstname');
-  });
-
-  test('getDisplayName ignore leading whitespace for first name', () => {
-    const account = {
-      name: '   firstname lastname',
-    };
-    const config = {
-      accounts: {
-        default_display_name: 'FIRST_NAME',
-      },
-    };
-    assert.equal(getDisplayName(config, account),
-        'firstname');
-  });
-
-  test('getDisplayName full name default', () => {
-    const account = {
-      name: 'firstname lastname',
-    };
-    const config = {
-      accounts: {
-        default_display_name: 'FULL_NAME',
-      },
-    };
-    assert.equal(getDisplayName(config, account),
-        'firstname lastname');
-  });
-
-  test('getDisplayName name only', () => {
-    const account = {
-      name: 'test-name',
-    };
-    assert.deepEqual(getUserName(config, account),
-        'test-name');
-  });
-
-  test('getUserName username only', () => {
-    const account = {
-      username: 'test-user',
-    };
-    assert.deepEqual(getUserName(config, account),
-        'test-user');
-  });
-
-  test('getUserName email only', () => {
-    const account = {
-      email: 'test-user@test-url.com',
-    };
-    assert.deepEqual(getUserName(config, account),
-        'test-user@test-url.com');
-  });
-
-  test('getUserName returns not Anonymous Coward as the anon name', () => {
-    assert.deepEqual(getUserName(config, null),
-        'Anonymous');
-  });
-
-  test('getUserName for the config returning the anon name', () => {
-    const config = {
-      user: {
-        anonymous_coward_name: 'Test Anon',
-      },
-    };
-    assert.deepEqual(getUserName(config, null),
-        'Test Anon');
-  });
-
-  test('getAccountDisplayName - account with name only', () => {
-    assert.equal(
-        getAccountDisplayName(config,
-            {name: 'Some user name'}),
-        'Some user name');
-  });
-
-  test('getAccountDisplayName - account with email only', () => {
-    assert.equal(
-        getAccountDisplayName(config,
-            {email: 'my@example.com'}),
-        'my@example.com <my@example.com>');
-  });
-
-  test('getAccountDisplayName - account with name and status', () => {
-    assert.equal(
-        getAccountDisplayName(config, {
-          name: 'Some name',
-          status: 'OOO',
-        }),
-        'Some name (OOO)');
-  });
-
-  test('getAccountDisplayName - account with name and email', () => {
-    assert.equal(
-        getAccountDisplayName(config, {
-          name: 'Some name',
-          email: 'my@example.com',
-        }),
-        'Some name <my@example.com>');
-  });
-
-  test('getAccountDisplayName - account with name, email and status', () => {
-    assert.equal(
-        getAccountDisplayName(config, {
-          name: 'Some name',
-          email: 'my@example.com',
-          status: 'OOO',
-        }),
-        'Some name <my@example.com> (OOO)');
-  });
-
-  test('getGroupDisplayName', () => {
-    assert.equal(
-        getGroupDisplayName({name: 'Some user name'}),
-        'Some user name (group)');
-  });
-
-  test('_accountEmail', () => {
-    assert.equal(
-        _testOnly_accountEmail('email@gerritreview.com'),
-        '<email@gerritreview.com>');
-    assert.equal(_testOnly_accountEmail(undefined), '');
-  });
-});
-
diff --git a/polygerrit-ui/app/utils/display-name-util_test.ts b/polygerrit-ui/app/utils/display-name-util_test.ts
new file mode 100644
index 0000000..e6d4704
--- /dev/null
+++ b/polygerrit-ui/app/utils/display-name-util_test.ts
@@ -0,0 +1,225 @@
+/**
+ * @license
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+  AccountInfo,
+  DefaultDisplayNameConfig,
+  EmailAddress,
+  GroupName,
+  ServerInfo,
+} from '../api/rest-api';
+import '../test/common-test-setup-karma';
+import {
+  getDisplayName,
+  getUserName,
+  getGroupDisplayName,
+  getAccountDisplayName,
+  _testOnly_accountEmail,
+} from './display-name-util';
+import {
+  createAccountsConfig,
+  createGroupInfo,
+  createServerInfo,
+} from '../test/test-data-generators';
+
+suite('display-name-utils tests', () => {
+  const config: ServerInfo = {
+    ...createServerInfo(),
+    user: {
+      anonymous_coward_name: 'Anonymous Coward',
+    },
+  };
+
+  test('getDisplayName name only', () => {
+    const account = {
+      name: 'test-name',
+    };
+    assert.equal(getDisplayName(config, account), 'test-name');
+  });
+
+  test('getDisplayName prefer displayName', () => {
+    const account = {
+      name: 'test-name',
+      display_name: 'better-name',
+    };
+    assert.equal(getDisplayName(config, account), 'better-name');
+  });
+
+  test('getDisplayName prefer username default', () => {
+    const account = {
+      name: 'test-name',
+      username: 'user-name',
+    };
+    const config: ServerInfo = {
+      ...createServerInfo(),
+      accounts: {
+        ...createAccountsConfig(),
+        default_display_name: DefaultDisplayNameConfig.USERNAME,
+      },
+    };
+    assert.equal(getDisplayName(config, account), 'user-name');
+  });
+
+  test('getDisplayName firstNameOnly', () => {
+    const account = {
+      name: 'firstname lastname',
+    };
+    assert.equal(getDisplayName(config, account, true), 'firstname');
+  });
+
+  test('getDisplayName prefer first name default', () => {
+    const account = {
+      name: 'firstname lastname',
+    };
+    const config: ServerInfo = {
+      ...createServerInfo(),
+      accounts: {
+        ...createAccountsConfig(),
+        default_display_name: DefaultDisplayNameConfig.FIRST_NAME,
+      },
+    };
+    assert.equal(getDisplayName(config, account), 'firstname');
+  });
+
+  test('getDisplayName ignore leading whitespace for first name', () => {
+    const account = {
+      name: '   firstname lastname',
+    };
+    const config: ServerInfo = {
+      ...createServerInfo(),
+      accounts: {
+        ...createAccountsConfig(),
+        default_display_name: DefaultDisplayNameConfig.FIRST_NAME,
+      },
+    };
+    assert.equal(getDisplayName(config, account), 'firstname');
+  });
+
+  test('getDisplayName full name default', () => {
+    const account = {
+      name: 'firstname lastname',
+    };
+    const config: ServerInfo = {
+      ...createServerInfo(),
+      accounts: {
+        ...createAccountsConfig(),
+        default_display_name: DefaultDisplayNameConfig.FULL_NAME,
+      },
+    };
+    assert.equal(getDisplayName(config, account), 'firstname lastname');
+  });
+
+  test('getDisplayName name only', () => {
+    const account = {
+      name: 'test-name',
+    };
+    assert.deepEqual(getUserName(config, account), 'test-name');
+  });
+
+  test('getUserName username only', () => {
+    const account = {
+      username: 'test-user',
+    };
+    assert.deepEqual(getUserName(config, account), 'test-user');
+  });
+
+  test('getUserName email only', () => {
+    const account: AccountInfo = {
+      email: 'test-user@test-url.com' as EmailAddress,
+    };
+    assert.deepEqual(getUserName(config, account), 'test-user@test-url.com');
+  });
+
+  test('getUserName returns not Anonymous Coward as the anon name', () => {
+    assert.deepEqual(getUserName(config, undefined), 'Anonymous');
+  });
+
+  test('getUserName for the config returning the anon name', () => {
+    const config: ServerInfo = {
+      ...createServerInfo(),
+      user: {
+        anonymous_coward_name: 'Test Anon',
+      },
+    };
+    assert.deepEqual(getUserName(config, undefined), 'Test Anon');
+  });
+
+  test('getAccountDisplayName - account with name only', () => {
+    assert.equal(
+      getAccountDisplayName(config, {name: 'Some user name'}),
+      'Some user name'
+    );
+  });
+
+  test('getAccountDisplayName - account with email only', () => {
+    assert.equal(
+      getAccountDisplayName(config, {
+        email: 'my@example.com' as EmailAddress,
+      }),
+      'my@example.com <my@example.com>'
+    );
+  });
+
+  test('getAccountDisplayName - account with name and status', () => {
+    assert.equal(
+      getAccountDisplayName(config, {
+        name: 'Some name',
+        status: 'OOO',
+      }),
+      'Some name (OOO)'
+    );
+  });
+
+  test('getAccountDisplayName - account with name and email', () => {
+    assert.equal(
+      getAccountDisplayName(config, {
+        name: 'Some name',
+        email: 'my@example.com' as EmailAddress,
+      }),
+      'Some name <my@example.com>'
+    );
+  });
+
+  test('getAccountDisplayName - account with name, email and status', () => {
+    assert.equal(
+      getAccountDisplayName(config, {
+        name: 'Some name',
+        email: 'my@example.com' as EmailAddress,
+        status: 'OOO',
+      }),
+      'Some name <my@example.com> (OOO)'
+    );
+  });
+
+  test('getGroupDisplayName', () => {
+    assert.equal(
+      getGroupDisplayName({
+        ...createGroupInfo(),
+        name: 'Some user name' as GroupName,
+      }),
+      'Some user name (group)'
+    );
+  });
+
+  test('_accountEmail', () => {
+    assert.equal(
+      _testOnly_accountEmail('email@gerritreview.com'),
+      '<email@gerritreview.com>'
+    );
+    assert.equal(_testOnly_accountEmail(undefined), '');
+  });
+});
diff --git a/polygerrit-ui/app/utils/path-list-util_test.js b/polygerrit-ui/app/utils/path-list-util_test.js
deleted file mode 100644
index 4d06344..0000000
--- a/polygerrit-ui/app/utils/path-list-util_test.js
+++ /dev/null
@@ -1,161 +0,0 @@
-/**
- * @license
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import '../test/common-test-setup-karma.js';
-import {SpecialFilePath} from '../constants/constants.js';
-import {
-  addUnmodifiedFiles,
-  computeDisplayPath,
-  isMagicPath,
-  specialFilePathCompare, truncatePath,
-} from './path-list-util.js';
-
-suite('path-list-utl tests', () => {
-  test('special sort', () => {
-    const testFiles = [
-      '/a.h',
-      '/MERGE_LIST',
-      '/a.cpp',
-      '/COMMIT_MSG',
-      '/asdasd',
-      '/mrPeanutbutter.py',
-    ];
-    assert.deepEqual(
-        testFiles.sort(specialFilePathCompare),
-        [
-          '/COMMIT_MSG',
-          '/MERGE_LIST',
-          '/a.h',
-          '/a.cpp',
-          '/asdasd',
-          '/mrPeanutbutter.py',
-        ]);
-  });
-
-  test('special file path sorting', () => {
-    assert.deepEqual(
-        ['.b', '/COMMIT_MSG', '.a', 'file'].sort(
-            specialFilePathCompare),
-        ['/COMMIT_MSG', '.a', '.b', 'file']);
-
-    assert.deepEqual(
-        ['.b', '/COMMIT_MSG', 'foo/bar/baz.cc', 'foo/bar/baz.h'].sort(
-            specialFilePathCompare),
-        ['/COMMIT_MSG', '.b', 'foo/bar/baz.h', 'foo/bar/baz.cc']);
-
-    assert.deepEqual(
-        ['.b', '/COMMIT_MSG', 'foo/bar/baz.cc', 'foo/bar/baz.hpp'].sort(
-            specialFilePathCompare),
-        ['/COMMIT_MSG', '.b', 'foo/bar/baz.hpp', 'foo/bar/baz.cc']);
-
-    assert.deepEqual(
-        ['.b', '/COMMIT_MSG', 'foo/bar/baz.cc', 'foo/bar/baz.hxx'].sort(
-            specialFilePathCompare),
-        ['/COMMIT_MSG', '.b', 'foo/bar/baz.hxx', 'foo/bar/baz.cc']);
-
-    assert.deepEqual(
-        ['foo/bar.h', 'foo/bar.hxx', 'foo/bar.hpp'].sort(
-            specialFilePathCompare),
-        ['foo/bar.h', 'foo/bar.hpp', 'foo/bar.hxx']);
-
-    // Regression test for Issue 4448.
-    assert.deepEqual(
-        [
-          'minidump/minidump_memory_writer.cc',
-          'minidump/minidump_memory_writer.h',
-          'minidump/minidump_thread_writer.cc',
-          'minidump/minidump_thread_writer.h',
-        ].sort(specialFilePathCompare),
-        [
-          'minidump/minidump_memory_writer.h',
-          'minidump/minidump_memory_writer.cc',
-          'minidump/minidump_thread_writer.h',
-          'minidump/minidump_thread_writer.cc',
-        ]);
-
-    // Regression test for Issue 4545.
-    assert.deepEqual(
-        [
-          'task_test.go',
-          'task.go',
-        ].sort(specialFilePathCompare),
-        [
-          'task.go',
-          'task_test.go',
-        ]);
-  });
-
-  test('file display name', () => {
-    assert.equal(computeDisplayPath('/foo/bar/baz'), '/foo/bar/baz');
-    assert.equal(computeDisplayPath('/foobarbaz'), '/foobarbaz');
-    assert.equal(computeDisplayPath('/COMMIT_MSG'), 'Commit message');
-    assert.equal(computeDisplayPath('/MERGE_LIST'), 'Merge list');
-  });
-
-  test('isMagicPath', () => {
-    assert.isFalse(isMagicPath(undefined));
-    assert.isFalse(isMagicPath('/foo.cc'));
-    assert.isTrue(isMagicPath('/COMMIT_MSG'));
-    assert.isTrue(isMagicPath('/MERGE_LIST'));
-  });
-
-  test('patchset level comments are hidden', () => {
-    const commentedPaths = {
-      [SpecialFilePath.PATCHSET_LEVEL_COMMENTS]: true,
-      'file1.txt': true,
-    };
-
-    const files = {'file2.txt': {status: 'M'}};
-    addUnmodifiedFiles(files, commentedPaths);
-    assert.equal(files['file1.txt'].status, 'U');
-    assert.equal(files['file2.txt'].status, 'M');
-    assert.isFalse(files.hasOwnProperty(
-        SpecialFilePath.PATCHSET_LEVEL_COMMENTS));
-  });
-
-  test('truncatePath with long path should add ellipsis', () => {
-    let path = 'level1/level2/level3/level4/file.js';
-    let shortenedPath = truncatePath(path);
-    // The expected path is truncated with an ellipsis.
-    const expectedPath = '\u2026/file.js';
-    assert.equal(shortenedPath, expectedPath);
-
-    path = 'level2/file.js';
-    shortenedPath = truncatePath(path);
-    assert.equal(shortenedPath, expectedPath);
-  });
-
-  test('truncatePath with opt_threshold', () => {
-    let path = 'level1/level2/level3/level4/file.js';
-    let shortenedPath = truncatePath(path, 2);
-    // The expected path is truncated with an ellipsis.
-    const expectedPath = '\u2026/level4/file.js';
-    assert.equal(shortenedPath, expectedPath);
-
-    path = 'level2/file.js';
-    shortenedPath = truncatePath(path, 2);
-    assert.equal(shortenedPath, path);
-  });
-
-  test('truncatePath with short path should not add ellipsis', () => {
-    const path = 'file.js';
-    const expectedPath = 'file.js';
-    const shortenedPath = truncatePath(path);
-    assert.equal(shortenedPath, expectedPath);
-  });
-});
-
diff --git a/polygerrit-ui/app/utils/path-list-util_test.ts b/polygerrit-ui/app/utils/path-list-util_test.ts
new file mode 100644
index 0000000..79b5f09
--- /dev/null
+++ b/polygerrit-ui/app/utils/path-list-util_test.ts
@@ -0,0 +1,170 @@
+/**
+ * @license
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import '../test/common-test-setup-karma';
+import {FileInfoStatus, SpecialFilePath} from '../constants/constants';
+import {
+  addUnmodifiedFiles,
+  computeDisplayPath,
+  isMagicPath,
+  specialFilePathCompare,
+  truncatePath,
+} from './path-list-util';
+import {FileInfo} from '../api/rest-api';
+import {hasOwnProperty} from './common-util';
+
+suite('path-list-utl tests', () => {
+  test('special sort', () => {
+    const testFiles = [
+      '/a.h',
+      '/MERGE_LIST',
+      '/a.cpp',
+      '/COMMIT_MSG',
+      '/asdasd',
+      '/mrPeanutbutter.py',
+    ];
+    assert.deepEqual(testFiles.sort(specialFilePathCompare), [
+      '/COMMIT_MSG',
+      '/MERGE_LIST',
+      '/a.h',
+      '/a.cpp',
+      '/asdasd',
+      '/mrPeanutbutter.py',
+    ]);
+  });
+
+  test('special file path sorting', () => {
+    assert.deepEqual(
+      ['.b', '/COMMIT_MSG', '.a', 'file'].sort(specialFilePathCompare),
+      ['/COMMIT_MSG', '.a', '.b', 'file']
+    );
+
+    assert.deepEqual(
+      ['.b', '/COMMIT_MSG', 'foo/bar/baz.cc', 'foo/bar/baz.h'].sort(
+        specialFilePathCompare
+      ),
+      ['/COMMIT_MSG', '.b', 'foo/bar/baz.h', 'foo/bar/baz.cc']
+    );
+
+    assert.deepEqual(
+      ['.b', '/COMMIT_MSG', 'foo/bar/baz.cc', 'foo/bar/baz.hpp'].sort(
+        specialFilePathCompare
+      ),
+      ['/COMMIT_MSG', '.b', 'foo/bar/baz.hpp', 'foo/bar/baz.cc']
+    );
+
+    assert.deepEqual(
+      ['.b', '/COMMIT_MSG', 'foo/bar/baz.cc', 'foo/bar/baz.hxx'].sort(
+        specialFilePathCompare
+      ),
+      ['/COMMIT_MSG', '.b', 'foo/bar/baz.hxx', 'foo/bar/baz.cc']
+    );
+
+    assert.deepEqual(
+      ['foo/bar.h', 'foo/bar.hxx', 'foo/bar.hpp'].sort(specialFilePathCompare),
+      ['foo/bar.h', 'foo/bar.hpp', 'foo/bar.hxx']
+    );
+
+    // Regression test for Issue 4448.
+    assert.deepEqual(
+      [
+        'minidump/minidump_memory_writer.cc',
+        'minidump/minidump_memory_writer.h',
+        'minidump/minidump_thread_writer.cc',
+        'minidump/minidump_thread_writer.h',
+      ].sort(specialFilePathCompare),
+      [
+        'minidump/minidump_memory_writer.h',
+        'minidump/minidump_memory_writer.cc',
+        'minidump/minidump_thread_writer.h',
+        'minidump/minidump_thread_writer.cc',
+      ]
+    );
+
+    // Regression test for Issue 4545.
+    assert.deepEqual(['task_test.go', 'task.go'].sort(specialFilePathCompare), [
+      'task.go',
+      'task_test.go',
+    ]);
+  });
+
+  test('file display name', () => {
+    assert.equal(computeDisplayPath('/foo/bar/baz'), '/foo/bar/baz');
+    assert.equal(computeDisplayPath('/foobarbaz'), '/foobarbaz');
+    assert.equal(computeDisplayPath('/COMMIT_MSG'), 'Commit message');
+    assert.equal(computeDisplayPath('/MERGE_LIST'), 'Merge list');
+  });
+
+  test('isMagicPath', () => {
+    assert.isFalse(isMagicPath(undefined));
+    assert.isFalse(isMagicPath('/foo.cc'));
+    assert.isTrue(isMagicPath('/COMMIT_MSG'));
+    assert.isTrue(isMagicPath('/MERGE_LIST'));
+  });
+
+  test('patchset level comments are hidden', () => {
+    const commentedPaths = {
+      [SpecialFilePath.PATCHSET_LEVEL_COMMENTS]: true,
+      'file1.txt': true,
+    };
+
+    const files: {[filename: string]: FileInfo} = {
+      'file2.txt': {
+        status: FileInfoStatus.REWRITTEN,
+        size_delta: 10,
+        size: 10,
+      },
+    };
+    addUnmodifiedFiles(files, commentedPaths);
+    assert.equal(files['file1.txt'].status, FileInfoStatus.UNMODIFIED);
+    assert.equal(files['file2.txt'].status, FileInfoStatus.REWRITTEN);
+    assert.isFalse(
+      hasOwnProperty(files, SpecialFilePath.PATCHSET_LEVEL_COMMENTS)
+    );
+  });
+
+  test('truncatePath with long path should add ellipsis', () => {
+    let path = 'level1/level2/level3/level4/file.js';
+    let shortenedPath = truncatePath(path);
+    // The expected path is truncated with an ellipsis.
+    const expectedPath = '\u2026/file.js';
+    assert.equal(shortenedPath, expectedPath);
+
+    path = 'level2/file.js';
+    shortenedPath = truncatePath(path);
+    assert.equal(shortenedPath, expectedPath);
+  });
+
+  test('truncatePath with opt_threshold', () => {
+    let path = 'level1/level2/level3/level4/file.js';
+    let shortenedPath = truncatePath(path, 2);
+    // The expected path is truncated with an ellipsis.
+    const expectedPath = '\u2026/level4/file.js';
+    assert.equal(shortenedPath, expectedPath);
+
+    path = 'level2/file.js';
+    shortenedPath = truncatePath(path, 2);
+    assert.equal(shortenedPath, path);
+  });
+
+  test('truncatePath with short path should not add ellipsis', () => {
+    const path = 'file.js';
+    const expectedPath = 'file.js';
+    const shortenedPath = truncatePath(path);
+    assert.equal(shortenedPath, expectedPath);
+  });
+});
diff --git a/polygerrit-ui/app/utils/url-util_test.js b/polygerrit-ui/app/utils/url-util_test.ts
similarity index 66%
rename from polygerrit-ui/app/utils/url-util_test.js
rename to polygerrit-ui/app/utils/url-util_test.ts
index 5cd4bb4..63dc81d 100644
--- a/polygerrit-ui/app/utils/url-util_test.js
+++ b/polygerrit-ui/app/utils/url-util_test.ts
@@ -15,7 +15,9 @@
  * limitations under the License.
  */
 
-import '../test/common-test-setup-karma.js';
+import {ServerInfo} from '../api/rest-api';
+import '../test/common-test-setup-karma';
+import {createGerritInfo, createServerInfo} from '../test/test-data-generators';
 import {
   getBaseUrl,
   getDocsBaseUrl,
@@ -25,11 +27,13 @@
   toPath,
   toPathname,
   toSearchParams,
-} from './url-util.js';
+} from './url-util';
+import {appContext} from '../services/app-context';
+import {stubRestApi} from '../test/test-utils';
 
 suite('url-util tests', () => {
   suite('getBaseUrl tests', () => {
-    let originalCanonicalPath;
+    let originalCanonicalPath: string | undefined;
 
     suiteSetup(() => {
       originalCanonicalPath = window.CANONICAL_PATH;
@@ -51,43 +55,50 @@
     });
 
     test('null config', async () => {
-      const mockRestApi = {
-        probePath: sinon.stub().returns(Promise.resolve(true)),
-      };
-      const docsBaseUrl = await getDocsBaseUrl(null, mockRestApi);
-      assert.isTrue(
-          mockRestApi.probePath.calledWith('/Documentation/index.html'));
+      const probePathMock = stubRestApi('probePath').resolves(true);
+      const docsBaseUrl = await getDocsBaseUrl(
+        undefined,
+        appContext.restApiService
+      );
+      assert.isTrue(probePathMock.calledWith('/Documentation/index.html'));
       assert.equal(docsBaseUrl, '/Documentation');
     });
 
     test('no doc config', async () => {
-      const mockRestApi = {
-        probePath: sinon.stub().returns(Promise.resolve(true)),
+      const probePathMock = stubRestApi('probePath').resolves(true);
+      const config: ServerInfo = {
+        ...createServerInfo(),
+        gerrit: createGerritInfo(),
       };
-      const config = {gerrit: {}};
-      const docsBaseUrl = await getDocsBaseUrl(config, mockRestApi);
-      assert.isTrue(
-          mockRestApi.probePath.calledWith('/Documentation/index.html'));
+      const docsBaseUrl = await getDocsBaseUrl(
+        config,
+        appContext.restApiService
+      );
+      assert.isTrue(probePathMock.calledWith('/Documentation/index.html'));
       assert.equal(docsBaseUrl, '/Documentation');
     });
 
     test('has doc config', async () => {
-      const mockRestApi = {
-        probePath: sinon.stub().returns(Promise.resolve(true)),
+      const probePathMock = stubRestApi('probePath').resolves(true);
+      const config: ServerInfo = {
+        ...createServerInfo(),
+        gerrit: {...createGerritInfo(), doc_url: 'foobar'},
       };
-      const config = {gerrit: {doc_url: 'foobar'}};
-      const docsBaseUrl = await getDocsBaseUrl(config, mockRestApi);
-      assert.isFalse(mockRestApi.probePath.called);
+      const docsBaseUrl = await getDocsBaseUrl(
+        config,
+        appContext.restApiService
+      );
+      assert.isFalse(probePathMock.called);
       assert.equal(docsBaseUrl, 'foobar');
     });
 
     test('no probe', async () => {
-      const mockRestApi = {
-        probePath: sinon.stub().returns(Promise.resolve(false)),
-      };
-      const docsBaseUrl = await getDocsBaseUrl(null, mockRestApi);
-      assert.isTrue(
-          mockRestApi.probePath.calledWith('/Documentation/index.html'));
+      const probePathMock = stubRestApi('probePath').resolves(false);
+      const docsBaseUrl = await getDocsBaseUrl(
+        undefined,
+        appContext.restApiService
+      );
+      assert.isTrue(probePathMock.calledWith('/Documentation/index.html'));
       assert.isNotOk(docsBaseUrl);
     });
   });
@@ -144,7 +155,9 @@
     assert.equal(toPath('asdf', params), 'asdf');
     params.set('qwer', 'zxcv');
     assert.equal(toPath('asdf', params), 'asdf?qwer=zxcv');
-    assert.equal(toPath(toPathname('asdf?qwer=zxcv'),
-        toSearchParams('asdf?qwer=zxcv')), 'asdf?qwer=zxcv');
+    assert.equal(
+      toPath(toPathname('asdf?qwer=zxcv'), toSearchParams('asdf?qwer=zxcv')),
+      'asdf?qwer=zxcv'
+    );
   });
 });