Convert gr-comment-thread_test to typescript

Change-Id: I7fb433eac8d2178f2b64b9d418ac93823eedab8d
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
index 4bad587..f0c5012 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
@@ -51,6 +51,7 @@
 import {GrStorage, StorageLocation} from '../gr-storage/gr-storage';
 import {CustomKeyboardEvent} from '../../../types/events';
 import {LineNumber, FILE} from '../../diff/gr-diff/gr-diff-line';
+import {GrButton} from '../gr-button/gr-button';
 
 const UNRESOLVED_EXPAND_COUNT = 5;
 const NEWLINE_PATTERN = /\n/g;
@@ -59,6 +60,8 @@
   $: {
     restAPI: RestApiService & Element;
     storage: GrStorage;
+    replyBtn: GrButton;
+    quoteBtn: GrButton;
   };
 }
 
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.ts
index 1833b73..8ce220c 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.ts
@@ -18,8 +18,32 @@
 import '../../../test/common-test-setup-karma.js';
 import './gr-comment-thread.js';
 import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
-import {SpecialFilePath} from '../../../constants/constants.js';
-import {sortComments} from '../../../utils/comment-util.js';
+import {SpecialFilePath, Side} from '../../../constants/constants.js';
+import {
+  sortComments,
+  UIComment,
+  UIRobot,
+  isDraft,
+  UIDraft,
+} from '../../../utils/comment-util.js';
+import {GrCommentThread} from './gr-comment-thread.js';
+import {
+  PatchSetNum,
+  NumericChangeId,
+  UrlEncodedCommentId,
+  Timestamp,
+  RobotId,
+  RobotRunId,
+  RepoName,
+  ConfigInfo,
+  EmailAddress,
+} from '../../../types/common.js';
+import {GrComment} from '../gr-comment/gr-comment.js';
+import {LineNumber} from '../../diff/gr-diff/gr-diff-line.js';
+import {
+  tap,
+  pressAndReleaseKeyOn,
+} from '@polymer/iron-test-helpers/mock-interactions';
 
 const basicFixture = fixtureFromElement('gr-comment-thread');
 
@@ -27,91 +51,108 @@
 
 suite('gr-comment-thread tests', () => {
   suite('basic test', () => {
-    let element;
+    let element: GrCommentThread;
 
     setup(() => {
       stub('gr-rest-api-interface', {
-        getLoggedIn() { return Promise.resolve(false); },
+        getLoggedIn() {
+          return Promise.resolve(false);
+        },
       });
 
       element = basicFixture.instantiate();
-      element.patchNum = '3';
-      element.changeNum = '1';
+      element.patchNum = 3 as PatchSetNum;
+      element.changeNum = 1 as NumericChangeId;
       flush();
     });
 
     test('comments are sorted correctly', () => {
-      const comments = [
+      const comments: UIComment[] = [
         {
           message: 'i like you, too',
-          in_reply_to: 'sallys_confession',
+          in_reply_to: 'sallys_confession' as UrlEncodedCommentId,
           __date: new Date('2015-12-25'),
-        }, {
-          id: 'sallys_confession',
+        },
+        {
+          id: 'sallys_confession' as UrlEncodedCommentId,
           message: 'i like you, jack',
-          updated: '2015-12-24 15:00:20.396000000',
-        }, {
-          id: 'sally_to_dr_finklestein',
+          updated: '2015-12-24 15:00:20.396000000' as Timestamp,
+        },
+        {
+          id: 'sally_to_dr_finklestein' as UrlEncodedCommentId,
           message: 'i’m running away',
-          updated: '2015-10-31 09:00:20.396000000',
-        }, {
-          id: 'sallys_defiance',
-          in_reply_to: 'sally_to_dr_finklestein',
+          updated: '2015-10-31 09:00:20.396000000' as Timestamp,
+        },
+        {
+          id: 'sallys_defiance' as UrlEncodedCommentId,
+          in_reply_to: 'sally_to_dr_finklestein' as UrlEncodedCommentId,
           message: 'i will poison you so i can get away',
-          updated: '2015-10-31 15:00:20.396000000',
-        }, {
-          id: 'dr_finklesteins_response',
-          in_reply_to: 'sally_to_dr_finklestein',
+          updated: '2015-10-31 15:00:20.396000000' as Timestamp,
+        },
+        {
+          id: 'dr_finklesteins_response' as UrlEncodedCommentId,
+          in_reply_to: 'sally_to_dr_finklestein' as UrlEncodedCommentId,
           message: 'no i will pull a thread and your arm will fall off',
-          updated: '2015-10-31 11:00:20.396000000',
-        }, {
-          id: 'sallys_mission',
+          updated: '2015-10-31 11:00:20.396000000' as Timestamp,
+        },
+        {
+          id: 'sallys_mission' as UrlEncodedCommentId,
           message: 'i have to find santa',
-          updated: '2015-12-24 15:00:20.396000000',
+          updated: '2015-12-24 15:00:20.396000000' as Timestamp,
         },
       ];
       const results = sortComments(comments);
       assert.deepEqual(results, [
         {
-          id: 'sally_to_dr_finklestein',
+          id: 'sally_to_dr_finklestein' as UrlEncodedCommentId,
           message: 'i’m running away',
-          updated: '2015-10-31 09:00:20.396000000',
-        }, {
-          id: 'dr_finklesteins_response',
-          in_reply_to: 'sally_to_dr_finklestein',
+          updated: '2015-10-31 09:00:20.396000000' as Timestamp,
+        },
+        {
+          id: 'dr_finklesteins_response' as UrlEncodedCommentId,
+          in_reply_to: 'sally_to_dr_finklestein' as UrlEncodedCommentId,
           message: 'no i will pull a thread and your arm will fall off',
-          updated: '2015-10-31 11:00:20.396000000',
-        }, {
-          id: 'sallys_defiance',
-          in_reply_to: 'sally_to_dr_finklestein',
+          updated: '2015-10-31 11:00:20.396000000' as Timestamp,
+        },
+        {
+          id: 'sallys_defiance' as UrlEncodedCommentId,
+          in_reply_to: 'sally_to_dr_finklestein' as UrlEncodedCommentId,
           message: 'i will poison you so i can get away',
-          updated: '2015-10-31 15:00:20.396000000',
-        }, {
-          id: 'sallys_confession',
+          updated: '2015-10-31 15:00:20.396000000' as Timestamp,
+        },
+        {
+          id: 'sallys_confession' as UrlEncodedCommentId,
           message: 'i like you, jack',
-          updated: '2015-12-24 15:00:20.396000000',
-        }, {
-          id: 'sallys_mission',
+          updated: '2015-12-24 15:00:20.396000000' as Timestamp,
+        },
+        {
+          id: 'sallys_mission' as UrlEncodedCommentId,
           message: 'i have to find santa',
-          updated: '2015-12-24 15:00:20.396000000',
-        }, {
-          message: 'i like you, too',
-          in_reply_to: 'sallys_confession',
+          updated: '2015-12-24 15:00:20.396000000' as Timestamp,
+        },
+        {
+          message: 'i like you, too' as UrlEncodedCommentId,
+          in_reply_to: 'sallys_confession' as UrlEncodedCommentId,
           __date: new Date('2015-12-25'),
         },
       ]);
     });
 
     test('addOrEditDraft w/ edit draft', () => {
-      element.comments = [{
-        id: 'jacks_reply',
-        message: 'i like you, too',
-        in_reply_to: 'sallys_confession',
-        updated: '2015-12-25 15:00:20.396000000',
-        __draft: true,
-      }];
-      const commentElStub = sinon.stub(element, '_commentElWithDraftID')
-          .callsFake(() => { return {}; });
+      element.comments = [
+        {
+          id: 'jacks_reply' as UrlEncodedCommentId,
+          message: 'i like you, too',
+          in_reply_to: 'sallys_confession' as UrlEncodedCommentId,
+          updated: '2015-12-25 15:00:20.396000000' as Timestamp,
+          __draft: true,
+        },
+      ];
+      const commentElStub = sinon
+        .stub(element, '_commentElWithDraftID')
+        .callsFake(() => {
+          return new GrComment();
+        });
       const addDraftStub = sinon.stub(element, 'addDraft');
 
       element.addOrEditDraft(123);
@@ -122,8 +163,11 @@
 
     test('addOrEditDraft w/o edit draft', () => {
       element.comments = [];
-      const commentElStub = sinon.stub(element, '_commentElWithDraftID')
-          .callsFake(() => { return {}; });
+      const commentElStub = sinon
+        .stub(element, '_commentElWithDraftID')
+        .callsFake(() => {
+          return new GrComment();
+        });
       const addDraftStub = sinon.stub(element, 'addDraft');
 
       element.addOrEditDraft(123);
@@ -134,40 +178,61 @@
 
     test('_shouldDisableAction', () => {
       let showActions = true;
-      const lastComment = {};
+      const lastComment: UIComment = {};
       assert.equal(
-          element._shouldDisableAction(showActions, lastComment), false);
+        element._shouldDisableAction(showActions, lastComment),
+        false
+      );
       showActions = false;
       assert.equal(
-          element._shouldDisableAction(showActions, lastComment), true);
+        element._shouldDisableAction(showActions, lastComment),
+        true
+      );
       showActions = true;
       lastComment.__draft = true;
       assert.equal(
-          element._shouldDisableAction(showActions, lastComment), true);
-      const robotComment = {};
-      robotComment.robot_id = true;
+        element._shouldDisableAction(showActions, lastComment),
+        true
+      );
+      const robotComment: UIRobot = {
+        id: '1234' as UrlEncodedCommentId,
+        updated: '1234' as Timestamp,
+        robot_id: 'robot_id' as RobotId,
+        robot_run_id: 'robot_run_id' as RobotRunId,
+        properties: {},
+        fix_suggestions: [],
+      };
       assert.equal(
-          element._shouldDisableAction(showActions, robotComment), false);
+        element._shouldDisableAction(showActions, robotComment),
+        false
+      );
     });
 
     test('_hideActions', () => {
       let showActions = true;
-      const lastComment = {};
+      const lastComment: UIComment = {};
       assert.equal(element._hideActions(showActions, lastComment), false);
       showActions = false;
       assert.equal(element._hideActions(showActions, lastComment), true);
       showActions = true;
       lastComment.__draft = true;
       assert.equal(element._hideActions(showActions, lastComment), true);
-      const robotComment = {};
-      robotComment.robot_id = true;
+      const robotComment: UIRobot = {
+        id: '1234' as UrlEncodedCommentId,
+        updated: '1234' as Timestamp,
+        robot_id: 'robot_id' as RobotId,
+        robot_run_id: 'robot_run_id' as RobotRunId,
+        properties: {},
+        fix_suggestions: [],
+      };
       assert.equal(element._hideActions(showActions, robotComment), true);
     });
 
     test('setting project name loads the project config', done => {
-      const projectName = 'foo/bar/baz';
-      const getProjectStub = sinon.stub(element.$.restAPI, 'getProjectConfig')
-          .returns(Promise.resolve({}));
+      const projectName = 'foo/bar/baz' as RepoName;
+      const getProjectStub = sinon
+        .stub(element.$.restAPI, 'getProjectConfig')
+        .returns(Promise.resolve({} as ConfigInfo));
       element.projectName = projectName;
       flush(() => {
         assert.isTrue(getProjectStub.calledWithExactly(projectName));
@@ -178,26 +243,30 @@
     test('optionally show file path', () => {
       // Path info doesn't exist when showFilePath is false. Because it's in a
       // dom-if it is not yet in the dom.
-      assert.isNotOk(element.shadowRoot
-          .querySelector('.pathInfo'));
+      assert.isNotOk(element.shadowRoot?.querySelector('.pathInfo'));
 
       const commentStub = sinon.stub(GerritNav, 'getUrlForComment');
-      element.changeNum = 123;
-      element.projectName = 'test project';
+      element.changeNum = 123 as NumericChangeId;
+      element.projectName = 'test project' as RepoName;
       element.path = 'path/to/file';
-      element.latestPatchNum = 10;
-      element.patchNum = 3;
+      element.patchNum = 3 as PatchSetNum;
       element.lineNum = 5;
-      element.comments = [{id: 'comment_id'}];
+      element.comments = [{id: 'comment_id' as UrlEncodedCommentId}];
       element.showFilePath = true;
       flush();
-      assert.isOk(element.shadowRoot
-          .querySelector('.pathInfo'));
-      assert.notEqual(getComputedStyle(element.shadowRoot
-          .querySelector('.pathInfo')).display,
-      'none');
-      assert.isTrue(commentStub.calledWithExactly(
-          element.changeNum, element.projectName, 'comment_id'));
+      assert.isOk(element.shadowRoot?.querySelector('.pathInfo'));
+      assert.notEqual(
+        getComputedStyle(element.shadowRoot!.querySelector('.pathInfo')!)
+          .display,
+        'none'
+      );
+      assert.isTrue(
+        commentStub.calledWithExactly(
+          element.changeNum,
+          element.projectName,
+          'comment_id' as UrlEncodedCommentId
+        )
+      );
     });
 
     test('_computeDisplayPath', () => {
@@ -207,7 +276,7 @@
       element.lineNum = 5;
       assert.equal(element._computeDisplayPath(path), 'path/to/file');
 
-      element.patchNum = '3';
+      element.patchNum = 3 as PatchSetNum;
       path = SpecialFilePath.PATCHSET_LEVEL_COMMENTS;
       assert.equal(element._computeDisplayPath(path), 'Patchset');
     });
@@ -228,134 +297,157 @@
 });
 
 suite('comment action tests with unresolved thread', () => {
-  let element;
+  let element: GrCommentThread;
 
   setup(() => {
     stub('gr-rest-api-interface', {
-      getLoggedIn() { return Promise.resolve(false); },
+      getLoggedIn() {
+        return Promise.resolve(false);
+      },
       saveDiffDraft() {
-        return Promise.resolve({
+        return Promise.resolve(({
+          headers: {} as Headers,
+          redirected: false,
+          status: 200,
+          statusText: '',
+          type: '' as ResponseType,
+          url: '',
           ok: true,
           text() {
-            return Promise.resolve(')]}\'\n' +
+            return Promise.resolve(
+              ")]}'\n" +
                 JSON.stringify({
                   id: '7afa4931_de3d65bd',
                   path: '/path/to/file.txt',
                   line: 5,
-                  in_reply_to: 'baf0414d_60047215',
+                  in_reply_to: 'baf0414d_60047215' as UrlEncodedCommentId,
                   updated: '2015-12-21 02:01:10.850000000',
                   message: 'Done',
-                }));
+                })
+            );
           },
-        });
+        } as unknown) as Response);
       },
-      deleteDiffDraft() { return Promise.resolve({ok: true}); },
+      deleteDiffDraft() {
+        return Promise.resolve(({ok: true} as unknown) as Response);
+      },
     });
     element = withCommentFixture.instantiate();
-    element.patchNum = '1';
-    element.changeNum = '1';
-    element.comments = [{
-      author: {
-        name: 'Mr. Peanutbutter',
-        email: 'tenn1sballchaser@aol.com',
+    element.patchNum = 1 as PatchSetNum;
+    element.changeNum = 1 as NumericChangeId;
+    element.comments = [
+      {
+        author: {
+          name: 'Mr. Peanutbutter',
+          email: ('tenn1sballchaser@aol.com' as EmailAddress) as EmailAddress,
+        },
+        id: 'baf0414d_60047215' as UrlEncodedCommentId,
+        line: 5,
+        message: 'is this a crossover episode!?',
+        updated: '2015-12-08 19:48:33.843000000' as Timestamp,
+        path: '/path/to/file.txt',
+        unresolved: true,
+        patch_set: 3 as PatchSetNum,
+        __commentSide: Side.LEFT,
       },
-      id: 'baf0414d_60047215',
-      line: 5,
-      message: 'is this a crossover episode!?',
-      updated: '2015-12-08 19:48:33.843000000',
-      path: '/path/to/file.txt',
-      unresolved: true,
-      patch_set: 3,
-      __commentSide: 'left',
-    }];
+    ];
     flush();
   });
 
   test('reply', () => {
-    const commentEl = element.shadowRoot
-        .querySelector('gr-comment');
-    const reportStub = sinon.stub(element.reporting,
-        'recordDraftInteraction');
+    const commentEl = element.shadowRoot?.querySelector('gr-comment');
+    const reportStub = sinon.stub(element.reporting, 'recordDraftInteraction');
     assert.ok(commentEl);
 
     const replyBtn = element.$.replyBtn;
-    MockInteractions.tap(replyBtn);
+    tap(replyBtn);
     flush();
 
-    const drafts = element._orderedComments.filter(c => c.__draft == true);
+    const drafts = element._orderedComments.filter(c => isDraft(c));
     assert.equal(drafts.length, 1);
     assert.notOk(drafts[0].message, 'message should be empty');
-    assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
+    assert.equal(
+      drafts[0].in_reply_to,
+      ('baf0414d_60047215' as UrlEncodedCommentId) as UrlEncodedCommentId
+    );
     assert.isTrue(reportStub.calledOnce);
   });
 
   test('quote reply', () => {
-    const commentEl = element.shadowRoot
-        .querySelector('gr-comment');
-    const reportStub = sinon.stub(element.reporting,
-        'recordDraftInteraction');
+    const commentEl = element.shadowRoot?.querySelector('gr-comment');
+    const reportStub = sinon.stub(element.reporting, 'recordDraftInteraction');
     assert.ok(commentEl);
 
     const quoteBtn = element.$.quoteBtn;
-    MockInteractions.tap(quoteBtn);
+    tap(quoteBtn);
     flush();
 
-    const drafts = element._orderedComments.filter(c => c.__draft == true);
+    const drafts = element._orderedComments.filter(c => isDraft(c));
     assert.equal(drafts.length, 1);
     assert.equal(drafts[0].message, '> is this a crossover episode!?\n\n');
-    assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
+    assert.equal(
+      drafts[0].in_reply_to,
+      ('baf0414d_60047215' as UrlEncodedCommentId) as UrlEncodedCommentId
+    );
     assert.isTrue(reportStub.calledOnce);
   });
 
   test('quote reply multiline', () => {
-    const reportStub = sinon.stub(element.reporting,
-        'recordDraftInteraction');
-    element.comments = [{
-      author: {
-        name: 'Mr. Peanutbutter',
-        email: 'tenn1sballchaser@aol.com',
+    const reportStub = sinon.stub(element.reporting, 'recordDraftInteraction');
+    element.comments = [
+      {
+        author: {
+          name: 'Mr. Peanutbutter',
+          email: ('tenn1sballchaser@aol.com' as EmailAddress) as EmailAddress,
+        },
+        id: 'baf0414d_60047215' as UrlEncodedCommentId,
+        path: 'test',
+        line: 5,
+        message: 'is this a crossover episode!?\nIt might be!',
+        updated: '2015-12-08 19:48:33.843000000' as Timestamp,
       },
-      id: 'baf0414d_60047215',
-      path: 'test',
-      line: 5,
-      message: 'is this a crossover episode!?\nIt might be!',
-      updated: '2015-12-08 19:48:33.843000000',
-    }];
+    ];
     flush();
 
-    const commentEl = element.shadowRoot
-        .querySelector('gr-comment');
+    const commentEl = element.shadowRoot?.querySelector('gr-comment');
     assert.ok(commentEl);
 
     const quoteBtn = element.$.quoteBtn;
-    MockInteractions.tap(quoteBtn);
+    tap(quoteBtn);
     flush();
 
-    const drafts = element._orderedComments.filter(c => c.__draft == true);
+    const drafts = element._orderedComments.filter(c => isDraft(c));
     assert.equal(drafts.length, 1);
-    assert.equal(drafts[0].message,
-        '> is this a crossover episode!?\n> It might be!\n\n');
-    assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
+    assert.equal(
+      drafts[0].message,
+      '> is this a crossover episode!?\n> It might be!\n\n'
+    );
+    assert.equal(
+      drafts[0].in_reply_to,
+      'baf0414d_60047215' as UrlEncodedCommentId
+    );
     assert.isTrue(reportStub.calledOnce);
   });
 
   test('ack', done => {
-    const reportStub = sinon.stub(element.reporting,
-        'recordDraftInteraction');
-    element.changeNum = '42';
-    element.patchNum = '1';
+    const reportStub = sinon.stub(element.reporting, 'recordDraftInteraction');
+    element.changeNum = 42 as NumericChangeId;
+    element.patchNum = 1 as PatchSetNum;
 
-    const commentEl = element.shadowRoot
-        .querySelector('gr-comment');
+    const commentEl = element.shadowRoot?.querySelector('gr-comment');
     assert.ok(commentEl);
 
-    const ackBtn = element.shadowRoot.querySelector('#ackBtn');
-    MockInteractions.tap(ackBtn);
+    const ackBtn = element.shadowRoot?.querySelector('#ackBtn');
+    assert.isOk(ackBtn);
+    tap(ackBtn!);
     flush(() => {
-      const drafts = element.comments.filter(c => c.__draft == true);
+      const drafts = element.comments.filter(c => isDraft(c));
       assert.equal(drafts.length, 1);
       assert.equal(drafts[0].message, 'Ack');
-      assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
+      assert.equal(
+        drafts[0].in_reply_to,
+        'baf0414d_60047215' as UrlEncodedCommentId
+      );
       assert.equal(drafts[0].unresolved, false);
       assert.isTrue(reportStub.calledOnce);
       done();
@@ -363,21 +455,23 @@
   });
 
   test('done', done => {
-    const reportStub = sinon.stub(element.reporting,
-        'recordDraftInteraction');
-    element.changeNum = '42';
-    element.patchNum = '1';
-    const commentEl = element.shadowRoot
-        .querySelector('gr-comment');
+    const reportStub = sinon.stub(element.reporting, 'recordDraftInteraction');
+    element.changeNum = 42 as NumericChangeId;
+    element.patchNum = 1 as PatchSetNum;
+    const commentEl = element.shadowRoot?.querySelector('gr-comment');
     assert.ok(commentEl);
 
-    const doneBtn = element.shadowRoot.querySelector('#doneBtn');
-    MockInteractions.tap(doneBtn);
+    const doneBtn = element.shadowRoot?.querySelector('#doneBtn');
+    assert.isOk(doneBtn);
+    tap(doneBtn!);
     flush(() => {
-      const drafts = element.comments.filter(c => c.__draft == true);
+      const drafts = element.comments.filter(c => isDraft(c));
       assert.equal(drafts.length, 1);
       assert.equal(drafts[0].message, 'Done');
-      assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
+      assert.equal(
+        drafts[0].in_reply_to,
+        'baf0414d_60047215' as UrlEncodedCommentId
+      );
       assert.isFalse(drafts[0].unresolved);
       assert.isTrue(reportStub.calledOnce);
       done();
@@ -385,210 +479,237 @@
   });
 
   test('save', done => {
-    element.changeNum = '42';
-    element.patchNum = '1';
+    element.changeNum = 42 as NumericChangeId;
+    element.patchNum = 1 as PatchSetNum;
     element.path = '/path/to/file.txt';
-    const commentEl = element.shadowRoot
-        .querySelector('gr-comment');
+    const commentEl = element.shadowRoot?.querySelector('gr-comment');
     assert.ok(commentEl);
 
     const saveOrDiscardStub = sinon.stub();
     element.addEventListener('thread-changed', saveOrDiscardStub);
-    element.shadowRoot
-        .querySelector('gr-comment')._fireSave();
+    element.shadowRoot?.querySelector('gr-comment')?._fireSave();
 
     flush(() => {
       assert.isTrue(saveOrDiscardStub.called);
-      assert.equal(saveOrDiscardStub.lastCall.args[0].detail.rootId,
-          'baf0414d_60047215');
-      assert.equal(element.rootId, 'baf0414d_60047215');
-      assert.equal(saveOrDiscardStub.lastCall.args[0].detail.path,
-          '/path/to/file.txt');
+      assert.equal(
+        saveOrDiscardStub.lastCall.args[0].detail.rootId,
+        'baf0414d_60047215'
+      );
+      assert.equal(element.rootId, 'baf0414d_60047215' as UrlEncodedCommentId);
+      assert.equal(
+        saveOrDiscardStub.lastCall.args[0].detail.path,
+        '/path/to/file.txt'
+      );
       done();
     });
   });
 
   test('please fix', done => {
-    element.changeNum = '42';
-    element.patchNum = '1';
-    const commentEl = element.shadowRoot
-        .querySelector('gr-comment');
+    element.changeNum = 42 as NumericChangeId;
+    element.patchNum = 1 as PatchSetNum;
+    const commentEl = element.shadowRoot?.querySelector('gr-comment');
     assert.ok(commentEl);
-    commentEl.addEventListener('create-fix-comment', () => {
-      const drafts = element._orderedComments.filter(c => c.__draft == true);
+    commentEl!.addEventListener('create-fix-comment', () => {
+      const drafts = element._orderedComments.filter(c => isDraft(c));
       assert.equal(drafts.length, 1);
       assert.equal(
-          drafts[0].message, '> is this a crossover episode!?\n\nPlease fix.');
-      assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
+        drafts[0].message,
+        '> is this a crossover episode!?\n\nPlease fix.'
+      );
+      assert.equal(
+        drafts[0].in_reply_to,
+        'baf0414d_60047215' as UrlEncodedCommentId
+      );
       assert.isTrue(drafts[0].unresolved);
       done();
     });
-    commentEl.dispatchEvent(
-        new CustomEvent('create-fix-comment', {
-          detail: {comment: commentEl.comment},
-          composed: true, bubbles: false,
-        }));
+    commentEl!.dispatchEvent(
+      new CustomEvent('create-fix-comment', {
+        detail: {comment: commentEl!.comment},
+        composed: true,
+        bubbles: false,
+      })
+    );
   });
 
   test('discard', done => {
-    element.changeNum = '42';
-    element.patchNum = '1';
+    element.changeNum = 42 as NumericChangeId;
+    element.patchNum = 1 as PatchSetNum;
     element.path = '/path/to/file.txt';
-    element.push('comments', element._newReply(
-        element.comments[0].id,
-        element.comments[0].path,
-        'it’s pronouced jiff, not giff'));
+    assert.isOk(element.comments[0]);
+    element.push(
+      'comments',
+      element._newReply(
+        element.comments[0]!.id as UrlEncodedCommentId,
+        'it’s pronouced jiff, not giff'
+      )
+    );
     flush();
 
     const saveOrDiscardStub = sinon.stub();
     element.addEventListener('thread-changed', saveOrDiscardStub);
-    const draftEl =
-        element.root.querySelectorAll('gr-comment')[1];
+    const draftEl = element.root?.querySelectorAll('gr-comment')[1];
     assert.ok(draftEl);
-    draftEl.addEventListener('comment-discard', () => {
-      const drafts = element.comments.filter(c => c.__draft == true);
+    draftEl!.addEventListener('comment-discard', () => {
+      const drafts = element.comments.filter(c => isDraft(c));
       assert.equal(drafts.length, 0);
       assert.isTrue(saveOrDiscardStub.called);
-      assert.equal(saveOrDiscardStub.lastCall.args[0].detail.rootId,
-          element.rootId);
-      assert.equal(saveOrDiscardStub.lastCall.args[0].detail.path,
-          element.path);
+      assert.equal(
+        saveOrDiscardStub.lastCall.args[0].detail.rootId,
+        element.rootId
+      );
+      assert.equal(
+        saveOrDiscardStub.lastCall.args[0].detail.path,
+        element.path
+      );
       done();
     });
-    draftEl.dispatchEvent(
-        new CustomEvent('comment-discard', {
-          detail: {comment: draftEl.comment},
-          composed: true, bubbles: false,
-        }));
+    draftEl!.dispatchEvent(
+      new CustomEvent('comment-discard', {
+        detail: {comment: draftEl!.comment},
+        composed: true,
+        bubbles: false,
+      })
+    );
   });
 
-  test('discard with a single comment still fires event with previous rootId',
-      done => {
-        element.changeNum = '42';
-        element.patchNum = '1';
-        element.path = '/path/to/file.txt';
-        element.comments = [];
-        element.addOrEditDraft('1');
-        flush();
-        const rootId = element.rootId;
-        assert.isOk(rootId);
+  test('discard with a single comment still fires event with previous rootId', done => {
+    element.changeNum = 42 as NumericChangeId;
+    element.patchNum = 1 as PatchSetNum;
+    element.path = '/path/to/file.txt';
+    element.comments = [];
+    element.addOrEditDraft(1 as LineNumber);
+    flush();
+    const rootId = element.rootId;
+    assert.isOk(rootId);
 
-        const saveOrDiscardStub = sinon.stub();
-        element.addEventListener('thread-changed', saveOrDiscardStub);
-        const draftEl =
-        element.root.querySelectorAll('gr-comment')[0];
-        assert.ok(draftEl);
-        draftEl.addEventListener('comment-discard', () => {
-          assert.equal(element.comments.length, 0);
-          assert.isTrue(saveOrDiscardStub.called);
-          assert.equal(saveOrDiscardStub.lastCall.args[0].detail.rootId,
-              rootId);
-          assert.equal(saveOrDiscardStub.lastCall.args[0].detail.path,
-              element.path);
-          done();
-        });
-        draftEl.dispatchEvent(
-            new CustomEvent('comment-discard', {
-              detail: {comment: draftEl.comment},
-              composed: true, bubbles: false,
-            }));
-      });
+    const saveOrDiscardStub = sinon.stub();
+    element.addEventListener('thread-changed', saveOrDiscardStub);
+    const draftEl = element.root?.querySelectorAll('gr-comment')[0];
+    assert.ok(draftEl);
+    draftEl!.addEventListener('comment-discard', () => {
+      assert.equal(element.comments.length, 0);
+      assert.isTrue(saveOrDiscardStub.called);
+      assert.equal(saveOrDiscardStub.lastCall.args[0].detail.rootId, rootId);
+      assert.equal(
+        saveOrDiscardStub.lastCall.args[0].detail.path,
+        element.path
+      );
+      done();
+    });
+    draftEl!.dispatchEvent(
+      new CustomEvent('comment-discard', {
+        detail: {comment: draftEl!.comment},
+        composed: true,
+        bubbles: false,
+      })
+    );
+  });
 
   test('first editing comment does not add __otherEditing attribute', () => {
-    element.comments = [{
-      author: {
-        name: 'Mr. Peanutbutter',
-        email: 'tenn1sballchaser@aol.com',
+    element.comments = [
+      {
+        author: {
+          name: 'Mr. Peanutbutter',
+          email: ('tenn1sballchaser@aol.com' as EmailAddress) as EmailAddress,
+        },
+        id: 'baf0414d_60047215' as UrlEncodedCommentId,
+        line: 5,
+        path: 'test',
+        message: 'is this a crossover episode!?',
+        updated: '2015-12-08 19:48:33.843000000' as Timestamp,
+        __draft: true,
       },
-      id: 'baf0414d_60047215',
-      line: 5,
-      path: 'test',
-      message: 'is this a crossover episode!?',
-      updated: '2015-12-08 19:48:33.843000000',
-      __draft: true,
-    }];
+    ];
 
     const replyBtn = element.$.replyBtn;
-    MockInteractions.tap(replyBtn);
+    tap(replyBtn);
     flush();
 
-    const editing = element._orderedComments.filter(c => c.__editing == true);
+    const editing = element._orderedComments.filter(c => c.__editing === true);
     assert.equal(editing.length, 1);
     assert.equal(!!editing[0].__otherEditing, false);
   });
 
-  test('When not editing other comments, local storage not set' +
-      ' after discard', done => {
-    element.changeNum = '42';
-    element.patchNum = '1';
-    element.comments = [{
-      author: {
-        name: 'Mr. Peanutbutter',
-        email: 'tenn1sballchaser@aol.com',
-      },
-      id: 'baf0414d_60047215',
-      path: 'test',
-      line: 5,
-      message: 'is this a crossover episode!?',
-      updated: '2015-12-08 19:48:31.843000000',
-    },
-    {
-      author: {
-        name: 'Mr. Peanutbutter',
-        email: 'tenn1sballchaser@aol.com',
-      },
-      __draftID: '1',
-      in_reply_to: 'baf0414d_60047215',
-      path: 'test',
-      line: 5,
-      message: 'yes',
-      updated: '2015-12-08 19:48:32.843000000',
-      __draft: true,
-      __editing: true,
-    },
-    {
-      author: {
-        name: 'Mr. Peanutbutter',
-        email: 'tenn1sballchaser@aol.com',
-      },
-      __draftID: '2',
-      in_reply_to: 'baf0414d_60047215',
-      path: 'test',
-      line: 5,
-      message: 'no',
-      updated: '2015-12-08 19:48:33.843000000',
-      __draft: true,
-    }];
-    const storageStub = sinon.stub(element.$.storage, 'setDraftComment');
-    flush();
+  test(
+    'When not editing other comments, local storage not set' + ' after discard',
+    done => {
+      element.changeNum = 42 as NumericChangeId;
+      element.patchNum = 1 as PatchSetNum;
+      element.comments = [
+        {
+          author: {
+            name: 'Mr. Peanutbutter',
+            email: 'tenn1sballchaser@aol.com' as EmailAddress,
+          },
+          id: 'baf0414d_60047215' as UrlEncodedCommentId,
+          path: 'test',
+          line: 5,
+          message: 'is this a crossover episode!?',
+          updated: '2015-12-08 19:48:31.843000000' as Timestamp,
+        },
+        {
+          author: {
+            name: 'Mr. Peanutbutter',
+            email: 'tenn1sballchaser@aol.com' as EmailAddress,
+          },
+          __draftID: '1',
+          in_reply_to: 'baf0414d_60047215' as UrlEncodedCommentId,
+          path: 'test',
+          line: 5,
+          message: 'yes',
+          updated: '2015-12-08 19:48:32.843000000' as Timestamp,
+          __draft: true,
+          __editing: true,
+        },
+        {
+          author: {
+            name: 'Mr. Peanutbutter',
+            email: 'tenn1sballchaser@aol.com' as EmailAddress,
+          },
+          __draftID: '2',
+          in_reply_to: 'baf0414d_60047215' as UrlEncodedCommentId,
+          path: 'test',
+          line: 5,
+          message: 'no',
+          updated: '2015-12-08 19:48:33.843000000' as Timestamp,
+          __draft: true,
+        },
+      ];
+      const storageStub = sinon.stub(element.$.storage, 'setDraftComment');
+      flush();
 
-    const draftEl =
-    element.root.querySelectorAll('gr-comment')[1];
-    assert.ok(draftEl);
-    draftEl.addEventListener('comment-discard', () => {
-      assert.isFalse(storageStub.called);
-      storageStub.restore();
-      done();
-    });
-    draftEl.dispatchEvent(
+      const draftEl = element.root?.querySelectorAll('gr-comment')[1];
+      assert.ok(draftEl);
+      draftEl!.addEventListener('comment-discard', () => {
+        assert.isFalse(storageStub.called);
+        storageStub.restore();
+        done();
+      });
+      draftEl!.dispatchEvent(
         new CustomEvent('comment-discard', {
-          detail: {comment: draftEl.comment},
-          composed: true, bubbles: false,
-        }));
-  });
+          detail: {comment: draftEl!.comment},
+          composed: true,
+          bubbles: false,
+        })
+      );
+    }
+  );
 
   test('comment-update', () => {
-    const commentEl = element.shadowRoot
-        .querySelector('gr-comment');
+    const commentEl = element.shadowRoot?.querySelector('gr-comment');
     const updatedComment = {
       id: element.comments[0].id,
       foo: 'bar',
     };
-    commentEl.dispatchEvent(
-        new CustomEvent('comment-update', {
-          detail: {comment: updatedComment},
-          composed: true, bubbles: true,
-        }));
+    assert.isOk(commentEl);
+    commentEl!.dispatchEvent(
+      new CustomEvent('comment-update', {
+        detail: {comment: updatedComment},
+        composed: true,
+        bubbles: true,
+      })
+    );
     assert.strictEqual(element.comments[0], updatedComment);
   });
 
@@ -596,26 +717,30 @@
     setup(() => {
       element.comments = [
         {
-          id: 'jacks_reply',
+          id: 'jacks_reply' as UrlEncodedCommentId,
           message: 'i like you, too',
-          in_reply_to: 'sallys_confession',
-          updated: '2015-12-25 15:00:20.396000000',
+          in_reply_to: 'sallys_confession' as UrlEncodedCommentId,
+          updated: '2015-12-25 15:00:20.396000000' as Timestamp,
           unresolved: false,
-        }, {
-          id: 'sallys_confession',
-          in_reply_to: 'nonexistent_comment',
+        },
+        {
+          id: 'sallys_confession' as UrlEncodedCommentId,
+          in_reply_to: 'nonexistent_comment' as UrlEncodedCommentId,
           message: 'i like you, jack',
-          updated: '2015-12-24 15:00:20.396000000',
-        }, {
-          id: 'sally_to_dr_finklestein',
-          in_reply_to: 'nonexistent_comment',
+          updated: '2015-12-24 15:00:20.396000000' as Timestamp,
+        },
+        {
+          id: 'sally_to_dr_finklestein' as UrlEncodedCommentId,
+          in_reply_to: 'nonexistent_comment' as UrlEncodedCommentId,
           message: 'i’m running away',
-          updated: '2015-10-31 09:00:20.396000000',
-        }, {
-          id: 'sallys_defiance',
+          updated: '2015-10-31 09:00:20.396000000' as Timestamp,
+        },
+        {
+          id: 'sallys_defiance' as UrlEncodedCommentId,
           message: 'i will poison you so i can get away',
-          updated: '2015-10-31 15:00:20.396000000',
-        }];
+          updated: '2015-10-31 15:00:20.396000000' as Timestamp,
+        },
+      ];
     });
 
     test('orphan replies', () => {
@@ -623,12 +748,11 @@
     });
 
     test('keyboard shortcuts', () => {
-      const expandCollapseStub =
-          sinon.stub(element, '_expandCollapseComments');
-      MockInteractions.pressAndReleaseKeyOn(element, 69, null, 'e');
+      const expandCollapseStub = sinon.stub(element, '_expandCollapseComments');
+      pressAndReleaseKeyOn(element, 69, null, 'e');
       assert.isTrue(expandCollapseStub.lastCall.calledWith(false));
 
-      MockInteractions.pressAndReleaseKeyOn(element, 69, 'shift', 'e');
+      pressAndReleaseKeyOn(element, 69, 'shift', 'e');
       assert.isTrue(expandCollapseStub.lastCall.calledWith(true));
     });
 
@@ -636,7 +760,10 @@
       element._createReplyComment('dummy', true);
       flush();
       assert.equal(element._orderedComments.length, 5);
-      assert.equal(element._orderedComments[4].in_reply_to, 'jacks_reply');
+      assert.equal(
+        element._orderedComments[4].in_reply_to,
+        'jacks_reply' as UrlEncodedCommentId
+      );
     });
 
     test('resolvable comments', () => {
@@ -664,7 +791,7 @@
 
     test('_setInitialExpandedState with robot_ids', () => {
       for (let i = 0; i < element.comments.length; i++) {
-        element.comments[i].robot_id = 123;
+        (element.comments[i] as UIRobot).robot_id = '123' as RobotId;
       }
       element._setInitialExpandedState();
       for (let i = 0; i < element.comments.length; i++) {
@@ -691,12 +818,12 @@
   test('addDraft sets unresolved state correctly', () => {
     let unresolved = true;
     element.comments = [];
-    element.addDraft(null, null, unresolved);
+    element.addDraft(undefined, undefined, unresolved);
     assert.equal(element.comments[0].unresolved, true);
 
     unresolved = false; // comment should get added as actually resolved.
     element.comments = [];
-    element.addDraft(null, null, unresolved);
+    element.addDraft(undefined, undefined, unresolved);
     assert.equal(element.comments[0].unresolved, false);
 
     element.comments = [];
@@ -706,17 +833,17 @@
 
   test('_newDraft with root', () => {
     const draft = element._newDraft();
-    assert.equal(draft.__commentSide, 'left');
-    assert.equal(draft.patch_set, 3);
+    assert.equal(draft.__commentSide, Side.LEFT);
+    assert.equal(draft.patch_set, 3 as PatchSetNum);
   });
 
   test('_newDraft with no root', () => {
     element.comments = [];
-    element.commentSide = 'right';
-    element.patchNum = 2;
+    element.commentSide = Side.RIGHT;
+    element.patchNum = 2 as PatchSetNum;
     const draft = element._newDraft();
-    assert.equal(draft.__commentSide, 'right');
-    assert.equal(draft.patch_set, 2);
+    assert.equal(draft.__commentSide, Side.RIGHT);
+    assert.equal(draft.patch_set, 2 as PatchSetNum);
   });
 
   test('new comment gets created', () => {
@@ -724,63 +851,68 @@
     element.addOrEditDraft(1);
     assert.equal(element.comments.length, 1);
     // Mock a submitted comment.
-    element.comments[0].id = element.comments[0].__draftID;
-    element.comments[0].__draft = false;
+    element.comments[0].id = (element.comments[0] as UIDraft)
+      .__draftID as UrlEncodedCommentId;
+    delete (element.comments[0] as UIDraft).__draft;
     element.addOrEditDraft(1);
     assert.equal(element.comments.length, 2);
   });
 
   test('unresolved label', () => {
     element.unresolved = false;
-    assert.isTrue(element.$.unresolvedLabel.hasAttribute('hidden'));
+    const label = element.shadowRoot?.querySelector('#unresolvedLabel');
+    assert.isOk(label);
+    assert.isTrue(label!.hasAttribute('hidden'));
     element.unresolved = true;
-    assert.isFalse(element.$.unresolvedLabel.hasAttribute('hidden'));
+    assert.isFalse(label!.hasAttribute('hidden'));
   });
 
   test('draft comments are at the end of orderedComments', () => {
-    element.comments = [{
-      author: {
-        name: 'Mr. Peanutbutter',
-        email: 'tenn1sballchaser@aol.com',
+    element.comments = [
+      {
+        author: {
+          name: 'Mr. Peanutbutter',
+          email: 'tenn1sballchaser@aol.com' as EmailAddress,
+        },
+        id: '2' as UrlEncodedCommentId,
+        line: 5,
+        message: 'Earlier draft',
+        updated: '2015-12-08 19:48:33.843000000' as Timestamp,
+        __draft: true,
       },
-      id: 2,
-      line: 5,
-      message: 'Earlier draft',
-      updated: '2015-12-08 19:48:33.843000000',
-      __draft: true,
-    },
-    {
-      author: {
-        name: 'Mr. Peanutbutter2',
-        email: 'tenn1sballchaser@aol.com',
+      {
+        author: {
+          name: 'Mr. Peanutbutter2',
+          email: 'tenn1sballchaser@aol.com' as EmailAddress,
+        },
+        id: '1' as UrlEncodedCommentId,
+        line: 5,
+        message: 'This comment was left last but is not a draft',
+        updated: '2015-12-10 19:48:33.843000000' as Timestamp,
       },
-      id: 1,
-      line: 5,
-      message: 'This comment was left last but is not a draft',
-      updated: '2015-12-10 19:48:33.843000000',
-    },
-    {
-      author: {
-        name: 'Mr. Peanutbutter2',
-        email: 'tenn1sballchaser@aol.com',
+      {
+        author: {
+          name: 'Mr. Peanutbutter2',
+          email: 'tenn1sballchaser@aol.com' as EmailAddress,
+        },
+        id: '3' as UrlEncodedCommentId,
+        line: 5,
+        message: 'Later draft',
+        updated: '2015-12-09 19:48:33.843000000' as Timestamp,
+        __draft: true,
       },
-      id: 3,
-      line: 5,
-      message: 'Later draft',
-      updated: '2015-12-09 19:48:33.843000000',
-      __draft: true,
-    }];
-    assert.equal(element._orderedComments[0].id, '1');
-    assert.equal(element._orderedComments[1].id, '2');
-    assert.equal(element._orderedComments[2].id, '3');
+    ];
+    assert.equal(element._orderedComments[0].id, '1' as UrlEncodedCommentId);
+    assert.equal(element._orderedComments[1].id, '2' as UrlEncodedCommentId);
+    assert.equal(element._orderedComments[2].id, '3' as UrlEncodedCommentId);
   });
 
   test('reflects lineNum and commentSide to attributes', () => {
     element.lineNum = 7;
-    element.commentSide = 'left';
+    element.commentSide = Side.LEFT;
 
     assert.equal(element.getAttribute('line-num'), '7');
-    assert.equal(element.getAttribute('comment-side'), 'left');
+    assert.equal(element.getAttribute('comment-side'), Side.LEFT);
   });
 
   test('reflects range to JSON serialized attribute if set', () => {
@@ -791,9 +923,13 @@
       end_character: 7,
     };
 
-    assert.deepEqual(
-        JSON.parse(element.getAttribute('range')),
-        {start_line: 4, end_line: 5, start_character: 6, end_character: 7});
+    assert.isOk(element.getAttribute('range'));
+    assert.deepEqual(JSON.parse(element.getAttribute('range')!), {
+      start_line: 4,
+      end_line: 5,
+      start_character: 6,
+      end_character: 7,
+    });
   });
 
   test('removes range attribute if range is unset', () => {
@@ -810,70 +946,75 @@
 });
 
 suite('comment action tests on resolved comments', () => {
-  let element;
+  let element: GrCommentThread;
 
   setup(() => {
     stub('gr-rest-api-interface', {
-      getLoggedIn() { return Promise.resolve(false); },
+      getLoggedIn() {
+        return Promise.resolve(false);
+      },
       saveDiffDraft() {
-        return Promise.resolve({
+        return Promise.resolve(({
           ok: true,
           text() {
-            return Promise.resolve(')]}\'\n' +
+            return Promise.resolve(
+              ")]}'\n" +
                 JSON.stringify({
                   id: '7afa4931_de3d65bd',
                   path: '/path/to/file.txt',
                   line: 5,
-                  in_reply_to: 'baf0414d_60047215',
+                  in_reply_to: 'baf0414d_60047215' as UrlEncodedCommentId,
                   updated: '2015-12-21 02:01:10.850000000',
                   message: 'Done',
-                }));
+                })
+            );
           },
-        });
+        } as unknown) as Response);
       },
-      deleteDiffDraft() { return Promise.resolve({ok: true}); },
+      deleteDiffDraft() {
+        return Promise.resolve(({ok: true} as unknown) as Response);
+      },
     });
     element = withCommentFixture.instantiate();
-    element.patchNum = '1';
-    element.changeNum = '1';
-    element.comments = [{
-      author: {
-        name: 'Mr. Peanutbutter',
-        email: 'tenn1sballchaser@aol.com',
+    element.patchNum = 1 as PatchSetNum;
+    element.changeNum = 1 as NumericChangeId;
+    element.comments = [
+      {
+        author: {
+          name: 'Mr. Peanutbutter',
+          email: 'tenn1sballchaser@aol.com' as EmailAddress,
+        },
+        id: 'baf0414d_60047215' as UrlEncodedCommentId,
+        line: 5,
+        message: 'is this a crossover episode!?',
+        updated: '2015-12-08 19:48:33.843000000' as Timestamp,
+        path: '/path/to/file.txt',
+        unresolved: false,
       },
-      id: 'baf0414d_60047215',
-      line: 5,
-      message: 'is this a crossover episode!?',
-      updated: '2015-12-08 19:48:33.843000000',
-      path: '/path/to/file.txt',
-      unresolved: false,
-    }];
+    ];
     flush();
   });
 
   test('ack and done should be hidden', () => {
-    element.changeNum = '42';
-    element.patchNum = '1';
+    element.changeNum = 42 as NumericChangeId;
+    element.patchNum = 1 as PatchSetNum;
 
-    const commentEl = element.shadowRoot
-        .querySelector('gr-comment');
+    const commentEl = element.shadowRoot?.querySelector('gr-comment');
     assert.ok(commentEl);
 
-    const ackBtn = element.shadowRoot.querySelector('#ackBtn');
-    const doneBtn = element.shadowRoot.querySelector('#doneBtn');
+    const ackBtn = element.shadowRoot?.querySelector('#ackBtn');
+    const doneBtn = element.shadowRoot?.querySelector('#doneBtn');
     assert.equal(ackBtn, null);
     assert.equal(doneBtn, null);
   });
 
   test('reply and quote button should be visible', () => {
-    const commentEl = element.shadowRoot
-        .querySelector('gr-comment');
+    const commentEl = element.shadowRoot?.querySelector('gr-comment');
     assert.ok(commentEl);
 
-    const replyBtn = element.shadowRoot.querySelector('#replyBtn');
-    const quoteBtn = element.shadowRoot.querySelector('#quoteBtn');
+    const replyBtn = element.shadowRoot?.querySelector('#replyBtn');
+    const quoteBtn = element.shadowRoot?.querySelector('#quoteBtn');
     assert.ok(replyBtn);
     assert.ok(quoteBtn);
   });
 });
-