Convert gr-textarea_test.js to typescript

Change-Id: I87b6035a680ee3c7122fb3b0cee7b65e18bee9fd
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
index 4b6eceb..17c46c6 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
@@ -33,6 +33,7 @@
   Item,
   ItemSelectedEvent,
 } from '../gr-autocomplete-dropdown/gr-autocomplete-dropdown';
+import {CustomKeyboardEvent} from '../../../types/events';
 
 const MAX_ITEMS_DROPDOWN = 10;
 
@@ -235,11 +236,14 @@
     this._setEmoji(this.$.emojiSuggestions.getCurrentText());
   }
 
-  _handleEnterByKey(e: CustomEvent<{keyboardEvent: KeyboardEvent}>) {
+  _handleEnterByKey(e: CustomKeyboardEvent) {
     // Enter should have newline behavior if the picker is closed or if the user
     // has only typed ':'. Also make sure that shortcuts aren't clobbered.
     if (this._hideEmojiAutocomplete || this.disableEnterKeyForSelectingEmoji) {
-      if (!e.detail.keyboardEvent.metaKey && !e.detail.keyboardEvent.ctrlKey) {
+      if (
+        !e.detail.keyboardEvent?.metaKey &&
+        !e.detail.keyboardEvent?.ctrlKey
+      ) {
         this.indent(e);
       }
       return;
@@ -414,7 +418,7 @@
     );
   }
 
-  private indent(e: CustomEvent<{keyboardEvent: KeyboardEvent}>): void {
+  private indent(e: CustomKeyboardEvent): void {
     if (!document.queryCommandSupported('insertText')) {
       return;
     }
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.js b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.js
deleted file mode 100644
index 7c2f209..0000000
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.js
+++ /dev/null
@@ -1,373 +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 './gr-textarea.js';
-import {html} from '@polymer/polymer/lib/utils/html-tag.js';
-
-const basicFixture = fixtureFromElement('gr-textarea');
-
-const monospaceFixture = fixtureFromTemplate(html`
-<gr-textarea monospace="true"></gr-textarea>
-`);
-
-const hideBorderFixture = fixtureFromTemplate(html`
-<gr-textarea hide-border="true"></gr-textarea>
-`);
-
-suite('gr-textarea tests', () => {
-  let element;
-
-  setup(() => {
-    element = basicFixture.instantiate();
-    sinon.stub(element.reporting, 'reportInteraction');
-  });
-
-  test('monospace is set properly', () => {
-    assert.isFalse(element.classList.contains('monospace'));
-  });
-
-  test('hideBorder is set properly', () => {
-    assert.isFalse(element.$.textarea.classList.contains('noBorder'));
-  });
-
-  test('emoji selector is not open with the textarea lacks focus', () => {
-    element.$.textarea.selectionStart = 1;
-    element.$.textarea.selectionEnd = 1;
-    element.text = ':';
-    assert.isFalse(!element.$.emojiSuggestions.isHidden);
-  });
-
-  test('emoji selector is not open when a general text is entered', () => {
-    MockInteractions.focus(element.$.textarea);
-    element.$.textarea.selectionStart = 9;
-    element.$.textarea.selectionEnd = 9;
-    element.text = 'some text';
-    assert.isFalse(!element.$.emojiSuggestions.isHidden);
-  });
-
-  test('emoji selector opens when a colon is typed & the textarea has focus',
-      () => {
-        MockInteractions.focus(element.$.textarea);
-        // Needed for Safari tests. selectionStart is not updated when text is
-        // updated.
-        element.$.textarea.selectionStart = 1;
-        element.$.textarea.selectionEnd = 1;
-        element.text = ':';
-        flush();
-        assert.isFalse(element.$.emojiSuggestions.isHidden);
-        assert.equal(element._colonIndex, 0);
-        assert.isFalse(element._hideEmojiAutocomplete);
-        assert.equal(element._currentSearchString, '');
-      });
-
-  test('emoji selector opens when a colon is typed after space',
-      () => {
-        MockInteractions.focus(element.$.textarea);
-        // Needed for Safari tests. selectionStart is not updated when text is
-        // updated.
-        element.$.textarea.selectionStart = 2;
-        element.$.textarea.selectionEnd = 2;
-        element.text = ' :';
-        flush();
-        assert.isFalse(element.$.emojiSuggestions.isHidden);
-        assert.equal(element._colonIndex, 1);
-        assert.isFalse(element._hideEmojiAutocomplete);
-        assert.equal(element._currentSearchString, '');
-      });
-
-  test('emoji selector doesn\`t open when a colon is typed after character',
-      () => {
-        MockInteractions.focus(element.$.textarea);
-        // Needed for Safari tests. selectionStart is not updated when text is
-        // updated.
-        element.$.textarea.selectionStart = 5;
-        element.$.textarea.selectionEnd = 5;
-        element.text = 'test:';
-        flush();
-        assert.isTrue(element.$.emojiSuggestions.isHidden);
-        assert.isTrue(element._hideEmojiAutocomplete);
-      });
-
-  test('emoji selector opens when a colon is typed and some substring',
-      () => {
-        MockInteractions.focus(element.$.textarea);
-        // Needed for Safari tests. selectionStart is not updated when text is
-        // updated.
-        element.$.textarea.selectionStart = 1;
-        element.$.textarea.selectionEnd = 1;
-        element.text = ':';
-        element.$.textarea.selectionStart = 2;
-        element.$.textarea.selectionEnd = 2;
-        element.text = ':t';
-        flush();
-        assert.isFalse(element.$.emojiSuggestions.isHidden);
-        assert.equal(element._colonIndex, 0);
-        assert.isFalse(element._hideEmojiAutocomplete);
-        assert.equal(element._currentSearchString, 't');
-      });
-
-  test('emoji selector opens when a colon is typed in middle of text',
-      () => {
-        MockInteractions.focus(element.$.textarea);
-        // Needed for Safari tests. selectionStart is not updated when text is
-        // updated.
-        element.$.textarea.selectionStart = 1;
-        element.$.textarea.selectionEnd = 1;
-        // Since selectionStart is on Chrome set always on end of text, we
-        // stub it to 1
-        const text = ': hello';
-        sinon.stub(element.$, 'textarea').value( {
-          selectionStart: 1,
-          value: text,
-          textarea: {
-            focus: () => {},
-          },
-        });
-        element.text = text;
-        flush();
-        assert.isFalse(element.$.emojiSuggestions.isHidden);
-        assert.equal(element._colonIndex, 0);
-        assert.isFalse(element._hideEmojiAutocomplete);
-        assert.equal(element._currentSearchString, '');
-      });
-  test('emoji selector closes when text changes before the colon', () => {
-    const resetStub = sinon.stub(element, '_resetEmojiDropdown');
-    MockInteractions.focus(element.$.textarea);
-    flush();
-    element.$.textarea.selectionStart = 10;
-    element.$.textarea.selectionEnd = 10;
-    element.text = 'test test ';
-    element.$.textarea.selectionStart = 12;
-    element.$.textarea.selectionEnd = 12;
-    element.text = 'test test :';
-    element.$.textarea.selectionStart = 15;
-    element.$.textarea.selectionEnd = 15;
-    element.text = 'test test :smi';
-
-    assert.equal(element._currentSearchString, 'smi');
-    assert.isFalse(resetStub.called);
-    element.text = 'test test test :smi';
-    assert.isTrue(resetStub.called);
-  });
-
-  test('_resetEmojiDropdown', () => {
-    const closeSpy = sinon.spy(element, 'closeDropdown');
-    element._resetEmojiDropdown();
-    assert.equal(element._currentSearchString, '');
-    assert.isTrue(element._hideEmojiAutocomplete);
-    assert.equal(element._colonIndex, null);
-
-    element.$.emojiSuggestions.open();
-    flush();
-    element._resetEmojiDropdown();
-    assert.isTrue(closeSpy.called);
-  });
-
-  test('_determineSuggestions', () => {
-    const emojiText = 'tear';
-    const formatSpy = sinon.spy(element, '_formatSuggestions');
-    element._determineSuggestions(emojiText);
-    assert.isTrue(formatSpy.called);
-    assert.isTrue(formatSpy.lastCall.calledWithExactly(
-        [{dataValue: '😂', value: '😂', match: 'tears :\')',
-          text: '😂 tears :\')'},
-        {dataValue: '😢', value: '😢', match: 'tear', text: '😢 tear'},
-        ]));
-  });
-
-  test('_formatSuggestions', () => {
-    const matchedSuggestions = [{value: '😢', match: 'tear'},
-      {value: '😂', match: 'tears'}];
-    element._formatSuggestions(matchedSuggestions);
-    assert.deepEqual(
-        [{value: '😢', dataValue: '😢', match: 'tear', text: '😢 tear'},
-          {value: '😂', dataValue: '😂', match: 'tears', text: '😂 tears'}],
-        element._suggestions);
-  });
-
-  test('_handleEmojiSelect', () => {
-    element.$.textarea.selectionStart = 16;
-    element.$.textarea.selectionEnd = 16;
-    element.text = 'test test :tears';
-    element._colonIndex = 10;
-    const selectedItem = {dataset: {value: '😂'}};
-    const event = {detail: {selected: selectedItem}};
-    element._handleEmojiSelect(event);
-    assert.equal(element.text, 'test test 😂');
-  });
-
-  test('_updateCaratPosition', () => {
-    element.$.textarea.selectionStart = 4;
-    element.$.textarea.selectionEnd = 4;
-    element.text = 'test';
-    element._updateCaratPosition();
-    assert.deepEqual(element.$.hiddenText.innerHTML, element.text +
-        element.$.caratSpan.outerHTML);
-  });
-
-  test('newline receives matching indentation', async () => {
-    const indentCommand = sinon.stub(document, 'execCommand');
-    element.$.textarea.value = '    a';
-    element._handleEnterByKey(
-        new CustomEvent('keydown', {detail: {keyboardEvent: {keyCode: 13}}})
-    );
-    await flush();
-    assert.deepEqual(indentCommand.args[0], ['insertText', false, '\n    ']);
-  });
-
-  test('ctrl+enter and meta+enter do not indent', async () => {
-    const indentCommand = sinon.stub(document, 'execCommand');
-    element.$.textarea.value = '    a';
-    element._handleEnterByKey(
-        new CustomEvent('keydown', {
-          detail: {keyboardEvent: {keyCode: 13, ctrlKey: true}},
-        })
-    );
-    await flush();
-    assert.isTrue(indentCommand.notCalled);
-
-    element._handleEnterByKey(
-        new CustomEvent('keydown', {
-          detail: {keyboardEvent: {keyCode: 13, metaKey: true}},
-        })
-    );
-    await flush();
-    assert.isTrue(indentCommand.notCalled);
-  });
-
-  test('emoji dropdown is closed when iron-overlay-closed is fired', () => {
-    const resetSpy = sinon.spy(element, '_resetEmojiDropdown');
-    element.$.emojiSuggestions.dispatchEvent(
-        new CustomEvent('dropdown-closed', {
-          composed: true, bubbles: true,
-        }));
-    assert.isTrue(resetSpy.called);
-  });
-
-  test('_onValueChanged fires bind-value-changed', () => {
-    const listenerStub = sinon.stub();
-    const eventObject = {currentTarget: {focused: false}};
-    element.addEventListener('bind-value-changed', listenerStub);
-    element._onValueChanged(eventObject);
-    assert.isTrue(listenerStub.called);
-  });
-
-  suite('keyboard shortcuts', () => {
-    function setupDropdown(callback) {
-      MockInteractions.focus(element.$.textarea);
-      element.$.textarea.selectionStart = 1;
-      element.$.textarea.selectionEnd = 1;
-      element.text = ':';
-      element.$.textarea.selectionStart = 1;
-      element.$.textarea.selectionEnd = 2;
-      element.text = ':1';
-      flush();
-    }
-
-    test('escape key', () => {
-      const resetSpy = sinon.spy(element, '_resetEmojiDropdown');
-      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 27);
-      assert.isFalse(resetSpy.called);
-      setupDropdown();
-      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 27);
-      assert.isTrue(resetSpy.called);
-      assert.isFalse(!element.$.emojiSuggestions.isHidden);
-    });
-
-    test('up key', () => {
-      const upSpy = sinon.spy(element.$.emojiSuggestions, 'cursorUp');
-      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 38);
-      assert.isFalse(upSpy.called);
-      setupDropdown();
-      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 38);
-      assert.isTrue(upSpy.called);
-    });
-
-    test('down key', () => {
-      const downSpy = sinon.spy(element.$.emojiSuggestions, 'cursorDown');
-      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 40);
-      assert.isFalse(downSpy.called);
-      setupDropdown();
-      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 40);
-      assert.isTrue(downSpy.called);
-    });
-
-    test('enter key', () => {
-      const enterSpy = sinon.spy(element.$.emojiSuggestions,
-          'getCursorTarget');
-      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
-      assert.isFalse(enterSpy.called);
-      setupDropdown();
-      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
-      assert.isTrue(enterSpy.called);
-      flush();
-      assert.equal(element.text, '💯');
-    });
-
-    test('enter key - ignored on just colon without more information', () => {
-      const enterSpy = sinon.spy(element.$.emojiSuggestions,
-          'getCursorTarget');
-      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
-      assert.isFalse(enterSpy.called);
-      MockInteractions.focus(element.$.textarea);
-      element.$.textarea.selectionStart = 1;
-      element.$.textarea.selectionEnd = 1;
-      element.text = ':';
-      flush();
-      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
-      assert.isFalse(enterSpy.called);
-    });
-  });
-
-  suite('gr-textarea monospace', () => {
-  // gr-textarea set monospace class in the ready() method.
-  // In Polymer2, ready() is called from the fixture(...) method,
-  // If ready() is called again later, some nested elements doesn't
-  // handle it correctly. A separate test-fixture is used to set
-  // properties before ready() is called.
-
-    let element;
-
-    setup(() => {
-      element = monospaceFixture.instantiate();
-    });
-
-    test('monospace is set properly', () => {
-      assert.isTrue(element.classList.contains('monospace'));
-    });
-  });
-
-  suite('gr-textarea hideBorder', () => {
-  // gr-textarea set noBorder class in the ready() method.
-  // In Polymer2, ready() is called from the fixture(...) method,
-  // If ready() is called again later, some nested elements doesn't
-  // handle it correctly. A separate test-fixture is used to set
-  // properties before ready() is called.
-
-    let element;
-
-    setup(() => {
-      element = hideBorderFixture.instantiate();
-    });
-
-    test('hideBorder is set properly', () => {
-      assert.isTrue(element.$.textarea.classList.contains('noBorder'));
-    });
-  });
-});
-
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts
new file mode 100644
index 0000000..506c348
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts
@@ -0,0 +1,390 @@
+/**
+ * @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 './gr-textarea';
+import {GrTextarea} from './gr-textarea';
+import {html} from '@polymer/polymer/lib/utils/html-tag';
+import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
+import {CustomKeyboardEvent} from '../../../types/events';
+import {ItemSelectedEvent} from '../gr-autocomplete-dropdown/gr-autocomplete-dropdown';
+
+const basicFixture = fixtureFromElement('gr-textarea');
+
+const monospaceFixture = fixtureFromTemplate(html`
+  <gr-textarea monospace="true"></gr-textarea>
+`);
+
+const hideBorderFixture = fixtureFromTemplate(html`
+  <gr-textarea hide-border="true"></gr-textarea>
+`);
+
+suite('gr-textarea tests', () => {
+  let element: GrTextarea;
+
+  setup(() => {
+    element = basicFixture.instantiate();
+    sinon.stub(element.reporting, 'reportInteraction');
+  });
+
+  test('monospace is set properly', () => {
+    assert.isFalse(element.classList.contains('monospace'));
+  });
+
+  test('hideBorder is set properly', () => {
+    assert.isFalse(element.$.textarea.classList.contains('noBorder'));
+  });
+
+  test('emoji selector is not open with the textarea lacks focus', () => {
+    element.$.textarea.selectionStart = 1;
+    element.$.textarea.selectionEnd = 1;
+    element.text = ':';
+    assert.isFalse(!element.$.emojiSuggestions.isHidden);
+  });
+
+  test('emoji selector is not open when a general text is entered', () => {
+    MockInteractions.focus(element.$.textarea);
+    element.$.textarea.selectionStart = 9;
+    element.$.textarea.selectionEnd = 9;
+    element.text = 'some text';
+    assert.isFalse(!element.$.emojiSuggestions.isHidden);
+  });
+
+  test('emoji selector opens when a colon is typed & the textarea has focus', () => {
+    MockInteractions.focus(element.$.textarea);
+    // Needed for Safari tests. selectionStart is not updated when text is
+    // updated.
+    element.$.textarea.selectionStart = 1;
+    element.$.textarea.selectionEnd = 1;
+    element.text = ':';
+    flush();
+    assert.isFalse(element.$.emojiSuggestions.isHidden);
+    assert.equal(element._colonIndex, 0);
+    assert.isFalse(element._hideEmojiAutocomplete);
+    assert.equal(element._currentSearchString, '');
+  });
+
+  test('emoji selector opens when a colon is typed after space', () => {
+    MockInteractions.focus(element.$.textarea);
+    // Needed for Safari tests. selectionStart is not updated when text is
+    // updated.
+    element.$.textarea.selectionStart = 2;
+    element.$.textarea.selectionEnd = 2;
+    element.text = ' :';
+    flush();
+    assert.isFalse(element.$.emojiSuggestions.isHidden);
+    assert.equal(element._colonIndex, 1);
+    assert.isFalse(element._hideEmojiAutocomplete);
+    assert.equal(element._currentSearchString, '');
+  });
+
+  test('emoji selector doesn`t open when a colon is typed after character', () => {
+    MockInteractions.focus(element.$.textarea);
+    // Needed for Safari tests. selectionStart is not updated when text is
+    // updated.
+    element.$.textarea.selectionStart = 5;
+    element.$.textarea.selectionEnd = 5;
+    element.text = 'test:';
+    flush();
+    assert.isTrue(element.$.emojiSuggestions.isHidden);
+    assert.isTrue(element._hideEmojiAutocomplete);
+  });
+
+  test('emoji selector opens when a colon is typed and some substring', () => {
+    MockInteractions.focus(element.$.textarea);
+    // Needed for Safari tests. selectionStart is not updated when text is
+    // updated.
+    element.$.textarea.selectionStart = 1;
+    element.$.textarea.selectionEnd = 1;
+    element.text = ':';
+    element.$.textarea.selectionStart = 2;
+    element.$.textarea.selectionEnd = 2;
+    element.text = ':t';
+    flush();
+    assert.isFalse(element.$.emojiSuggestions.isHidden);
+    assert.equal(element._colonIndex, 0);
+    assert.isFalse(element._hideEmojiAutocomplete);
+    assert.equal(element._currentSearchString, 't');
+  });
+
+  test('emoji selector opens when a colon is typed in middle of text', () => {
+    MockInteractions.focus(element.$.textarea);
+    // Needed for Safari tests. selectionStart is not updated when text is
+    // updated.
+    element.$.textarea.selectionStart = 1;
+    element.$.textarea.selectionEnd = 1;
+    // Since selectionStart is on Chrome set always on end of text, we
+    // stub it to 1
+    const text = ': hello';
+    sinon.stub(element.$, 'textarea').value({
+      selectionStart: 1,
+      value: text,
+      textarea: {
+        focus: () => {},
+      },
+    });
+    element.text = text;
+    flush();
+    assert.isFalse(element.$.emojiSuggestions.isHidden);
+    assert.equal(element._colonIndex, 0);
+    assert.isFalse(element._hideEmojiAutocomplete);
+    assert.equal(element._currentSearchString, '');
+  });
+  test('emoji selector closes when text changes before the colon', () => {
+    const resetStub = sinon.stub(element, '_resetEmojiDropdown');
+    MockInteractions.focus(element.$.textarea);
+    flush();
+    element.$.textarea.selectionStart = 10;
+    element.$.textarea.selectionEnd = 10;
+    element.text = 'test test ';
+    element.$.textarea.selectionStart = 12;
+    element.$.textarea.selectionEnd = 12;
+    element.text = 'test test :';
+    element.$.textarea.selectionStart = 15;
+    element.$.textarea.selectionEnd = 15;
+    element.text = 'test test :smi';
+
+    assert.equal(element._currentSearchString, 'smi');
+    assert.isFalse(resetStub.called);
+    element.text = 'test test test :smi';
+    assert.isTrue(resetStub.called);
+  });
+
+  test('_resetEmojiDropdown', () => {
+    const closeSpy = sinon.spy(element, 'closeDropdown');
+    element._resetEmojiDropdown();
+    assert.equal(element._currentSearchString, '');
+    assert.isTrue(element._hideEmojiAutocomplete);
+    assert.equal(element._colonIndex, null);
+
+    element.$.emojiSuggestions.open();
+    flush();
+    element._resetEmojiDropdown();
+    assert.isTrue(closeSpy.called);
+  });
+
+  test('_determineSuggestions', () => {
+    const emojiText = 'tear';
+    const formatSpy = sinon.spy(element, '_formatSuggestions');
+    element._determineSuggestions(emojiText);
+    assert.isTrue(formatSpy.called);
+    assert.isTrue(
+      formatSpy.lastCall.calledWithExactly([
+        {
+          dataValue: '😂',
+          value: '😂',
+          match: "tears :')",
+          text: "😂 tears :')",
+        },
+        {dataValue: '😢', value: '😢', match: 'tear', text: '😢 tear'},
+      ])
+    );
+  });
+
+  test('_formatSuggestions', () => {
+    const matchedSuggestions = [
+      {value: '😢', match: 'tear'},
+      {value: '😂', match: 'tears'},
+    ];
+    element._formatSuggestions(matchedSuggestions);
+    assert.deepEqual(
+      [
+        {value: '😢', dataValue: '😢', match: 'tear', text: '😢 tear'},
+        {value: '😂', dataValue: '😂', match: 'tears', text: '😂 tears'},
+      ],
+      element._suggestions
+    );
+  });
+
+  test('_handleEmojiSelect', () => {
+    element.$.textarea.selectionStart = 16;
+    element.$.textarea.selectionEnd = 16;
+    element.text = 'test test :tears';
+    element._colonIndex = 10;
+    const selectedItem = ({dataset: {value: '😂'}} as unknown) as HTMLElement;
+    const event = new CustomEvent<ItemSelectedEvent>('item-selected', {
+      detail: {trigger: 'click', selected: selectedItem},
+    });
+    element._handleEmojiSelect(event);
+    assert.equal(element.text, 'test test 😂');
+  });
+
+  test('_updateCaratPosition', () => {
+    element.$.textarea.selectionStart = 4;
+    element.$.textarea.selectionEnd = 4;
+    element.text = 'test';
+    element._updateCaratPosition();
+    assert.deepEqual(
+      element.$.hiddenText.innerHTML,
+      element.text + element.$.caratSpan.outerHTML
+    );
+  });
+
+  test('newline receives matching indentation', async () => {
+    const indentCommand = sinon.stub(document, 'execCommand');
+    element.$.textarea.value = '    a';
+    element._handleEnterByKey(
+      new CustomEvent('keydown', {
+        detail: {keyboardEvent: {keyCode: 13}},
+      }) as CustomKeyboardEvent
+    );
+    await flush();
+    assert.deepEqual(indentCommand.args[0], ['insertText', false, '\n    ']);
+  });
+
+  test('ctrl+enter and meta+enter do not indent', async () => {
+    const indentCommand = sinon.stub(document, 'execCommand');
+    element.$.textarea.value = '    a';
+    element._handleEnterByKey(
+      new CustomEvent('keydown', {
+        detail: {keyboardEvent: {keyCode: 13, ctrlKey: true}},
+      }) as CustomKeyboardEvent
+    );
+    await flush();
+    assert.isTrue(indentCommand.notCalled);
+
+    element._handleEnterByKey(
+      new CustomEvent('keydown', {
+        detail: {keyboardEvent: {keyCode: 13, metaKey: true}},
+      }) as CustomKeyboardEvent
+    );
+    await flush();
+    assert.isTrue(indentCommand.notCalled);
+  });
+
+  test('emoji dropdown is closed when iron-overlay-closed is fired', () => {
+    const resetSpy = sinon.spy(element, '_resetEmojiDropdown');
+    element.$.emojiSuggestions.dispatchEvent(
+      new CustomEvent('dropdown-closed', {
+        composed: true,
+        bubbles: true,
+      })
+    );
+    assert.isTrue(resetSpy.called);
+  });
+
+  test('_onValueChanged fires bind-value-changed', () => {
+    const listenerStub = sinon.stub();
+    const eventObject = new CustomEvent('bind-value-changed', {
+      detail: {currentTarget: {focused: false}, value: ''},
+    });
+    element.addEventListener('bind-value-changed', listenerStub);
+    element._onValueChanged(eventObject);
+    assert.isTrue(listenerStub.called);
+  });
+
+  suite('keyboard shortcuts', () => {
+    function setupDropdown() {
+      MockInteractions.focus(element.$.textarea);
+      element.$.textarea.selectionStart = 1;
+      element.$.textarea.selectionEnd = 1;
+      element.text = ':';
+      element.$.textarea.selectionStart = 1;
+      element.$.textarea.selectionEnd = 2;
+      element.text = ':1';
+      flush();
+    }
+
+    test('escape key', () => {
+      const resetSpy = sinon.spy(element, '_resetEmojiDropdown');
+      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 27);
+      assert.isFalse(resetSpy.called);
+      setupDropdown();
+      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 27);
+      assert.isTrue(resetSpy.called);
+      assert.isFalse(!element.$.emojiSuggestions.isHidden);
+    });
+
+    test('up key', () => {
+      const upSpy = sinon.spy(element.$.emojiSuggestions, 'cursorUp');
+      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 38);
+      assert.isFalse(upSpy.called);
+      setupDropdown();
+      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 38);
+      assert.isTrue(upSpy.called);
+    });
+
+    test('down key', () => {
+      const downSpy = sinon.spy(element.$.emojiSuggestions, 'cursorDown');
+      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 40);
+      assert.isFalse(downSpy.called);
+      setupDropdown();
+      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 40);
+      assert.isTrue(downSpy.called);
+    });
+
+    test('enter key', () => {
+      const enterSpy = sinon.spy(element.$.emojiSuggestions, 'getCursorTarget');
+      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
+      assert.isFalse(enterSpy.called);
+      setupDropdown();
+      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
+      assert.isTrue(enterSpy.called);
+      flush();
+      assert.equal(element.text, '💯');
+    });
+
+    test('enter key - ignored on just colon without more information', () => {
+      const enterSpy = sinon.spy(element.$.emojiSuggestions, 'getCursorTarget');
+      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
+      assert.isFalse(enterSpy.called);
+      MockInteractions.focus(element.$.textarea);
+      element.$.textarea.selectionStart = 1;
+      element.$.textarea.selectionEnd = 1;
+      element.text = ':';
+      flush();
+      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
+      assert.isFalse(enterSpy.called);
+    });
+  });
+
+  suite('gr-textarea monospace', () => {
+    // gr-textarea set monospace class in the ready() method.
+    // In Polymer2, ready() is called from the fixture(...) method,
+    // If ready() is called again later, some nested elements doesn't
+    // handle it correctly. A separate test-fixture is used to set
+    // properties before ready() is called.
+
+    let element: GrTextarea;
+
+    setup(() => {
+      element = monospaceFixture.instantiate() as GrTextarea;
+    });
+
+    test('monospace is set properly', () => {
+      assert.isTrue(element.classList.contains('monospace'));
+    });
+  });
+
+  suite('gr-textarea hideBorder', () => {
+    // gr-textarea set noBorder class in the ready() method.
+    // In Polymer2, ready() is called from the fixture(...) method,
+    // If ready() is called again later, some nested elements doesn't
+    // handle it correctly. A separate test-fixture is used to set
+    // properties before ready() is called.
+
+    let element: GrTextarea;
+
+    setup(() => {
+      element = hideBorderFixture.instantiate() as GrTextarea;
+    });
+
+    test('hideBorder is set properly', () => {
+      assert.isTrue(element.$.textarea.classList.contains('noBorder'));
+    });
+  });
+});