Repository: draft-js-plugins/draft-js-plugins
Pull request #1649 (2020-12-18 10:01:53)

Fixing issue with detecting : at end of word for emoji select
## Checklist

- [x] Fix any eslint errors that occur
- [x] Update change-log for every plugin you touch
- [x] Add/Update tests if you add/change new functionality
- [x] Add/Update docs if you add/change functionality
- [ ] Enable "Allow edits from maintainers" for this PR

## Use-case/Problem

See issue #1645

## Implementation

Fixing regular expression to include `:` at the end of a string.



Patch:
diff --git a/packages/emoji/CHANGELOG.md b/packages/emoji/CHANGELOG.md
index 66e9a612..194b912e 100644
--- a/packages/emoji/CHANGELOG.md
+++ b/packages/emoji/CHANGELOG.md
@@ -16,6 +16,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 - update emojione to emoji-toolkit
 - remove defaultImagePath, defaultImageType and cacheBust to customize components
 - fixing warning on scrolling in emoji select [#1635](https://github.com/draft-js-plugins/draft-js-plugins/issues/1635)
+- fixing issue with detecting : at end of word for emoji select [#1645](https://github.com/draft-js-plugins/draft-js-plugins/issues/1645)
 
 ## 2.1.3
 
diff --git a/packages/emoji/package.json b/packages/emoji/package.json
index 38f68e64..64f806ea 100644
--- a/packages/emoji/package.json
+++ b/packages/emoji/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@draft-js-plugins/emoji",
-  "version": "4.0.0-beta5",
+  "version": "4.0.0-beta6",
   "description": "Emoji Plugin for DraftJS",
   "author": {
     "name": "Nik Graf",
diff --git a/packages/emoji/src/components/EmojiSuggestions/index.tsx b/packages/emoji/src/components/EmojiSuggestions/index.tsx
index 6c84642a..7a3677e4 100644
--- a/packages/emoji/src/components/EmojiSuggestions/index.tsx
+++ b/packages/emoji/src/components/EmojiSuggestions/index.tsx
@@ -39,9 +39,7 @@ interface EmojiSuggestionsParams extends EmojiSuggestionsPubParams {
   emojiImage: ComponentType<EmojiImageProps>;
 }
 
-export default class EmojiSuggestions extends Component<
-  EmojiSuggestionsParams
-> {
+export default class EmojiSuggestions extends Component<EmojiSuggestionsParams> {
   state = {
     isActive: false,
     focusedOptionIndex: 0,
@@ -122,21 +120,21 @@ export default class EmojiSuggestions extends Component<
       return removeList();
 
     // identify the start & end positon of each search-text
-    const offsetDetails = searches.map(offsetKey =>
+    const offsetDetails = searches.map((offsetKey) =>
       utils.decodeOffsetKey(offsetKey!)
     );
 
     // a leave can be empty when it is removed due e.g. using backspace
     const leaves = offsetDetails
-      .filter(offsetDetail => offsetDetail!.blockKey === anchorKey)
-      .map(offsetDetail =>
+      .filter((offsetDetail) => offsetDetail!.blockKey === anchorKey)
+      .map((offsetDetail) =>
         editorState
           .getBlockTree(offsetDetail!.blockKey)
           .getIn([offsetDetail!.decoratorKey, 'leaves', offsetDetail!.leafKey])
       );
 
     // if all leaves are undefined the popover should be removed
-    if (leaves.every(leave => leave === undefined)) {
+    if (leaves.every((leave) => leave === undefined)) {
       return removeList();
     }
 
@@ -145,7 +143,7 @@ export default class EmojiSuggestions extends Component<
     // the @ causes troubles due selection confusion.
     const plainText = editorState.getCurrentContent().getPlainText();
     const selectionIsInsideWord = leaves
-      .filter(leave => leave !== undefined)
+      .filter((leave) => leave !== undefined)
       .map(
         ({ start, end }) =>
           (start === 0 &&
@@ -156,11 +154,11 @@ export default class EmojiSuggestions extends Component<
           (anchorOffset > start + 1 &&
             anchorOffset <= end) /*: is in the text or at the end*/
       );
-    if (selectionIsInsideWord.every(isInside => isInside === false))
+    if (selectionIsInsideWord.every((isInside) => isInside === false))
       return removeList();
 
     this.activeOffsetKey = selectionIsInsideWord
-      .filter(value => value === true)
+      .filter((value) => value === true)
       .keySeq()
       .first();
 
@@ -236,7 +234,7 @@ export default class EmojiSuggestions extends Component<
     keyboardEvent.preventDefault();
 
     const activeOffsetKey = this.lastSelectionIsInsideWord!.filter(
-      value => value === true
+      (value) => value === true
     )
       .keySeq()
       .first();
@@ -275,7 +273,8 @@ export default class EmojiSuggestions extends Component<
     );
     const emojiValue = word.substring(1, word.length).toLowerCase();
     const filteredValues = this.props.shortNames.filter(
-      emojiShortName => !emojiValue || emojiShortName!.indexOf(emojiValue) > -1
+      (emojiShortName) =>
+        !emojiValue || emojiShortName!.indexOf(emojiValue) > -1
     ) as List<string>;
     const size = filteredValues.size < 9 ? filteredValues.size : 9;
     return filteredValues.setSize(size);
@@ -368,7 +367,7 @@ export default class EmojiSuggestions extends Component<
         className={theme.emojiSuggestions}
         role="listbox"
         id={`emojis-list-${this.key}`}
-        ref={element => {
+        ref={(element) => {
           this.popover = element;
         }}
       >
diff --git a/packages/emoji/src/emojiSuggestionsStrategy.ts b/packages/emoji/src/emojiSuggestionsStrategy.ts
index 3795a2f2..4fedb9e6 100644
--- a/packages/emoji/src/emojiSuggestionsStrategy.ts
+++ b/packages/emoji/src/emojiSuggestionsStrategy.ts
@@ -1,7 +1,7 @@
 import { ContentBlock } from 'draft-js';
 import findWithRegex from 'find-with-regex';
 
-const EMOJI_REGEX = /(\s|^):[\w]*/g;
+const EMOJI_REGEX = /(\s|^):[\w]*:?/g;
 
 export default (
   contentBlock: ContentBlock,


Test patch:
diff --git a/packages/emoji/src/__test__/emojiSuggestionsStrategy.test.ts b/packages/emoji/src/__test__/emojiSuggestionsStrategy.test.ts
new file mode 100644
index 00000000..4c8980be
--- /dev/null
+++ b/packages/emoji/src/__test__/emojiSuggestionsStrategy.test.ts
@@ -0,0 +1,64 @@
+import {
+  ContentBlock,
+  convertFromRaw,
+  genKey,
+  RawDraftEntity,
+  RawDraftEntityRange,
+} from 'draft-js';
+import emojiSuggestionsStrategy from '../emojiSuggestionsStrategy';
+
+const getBlock = (
+  text: string,
+  entityRanges: RawDraftEntityRange[] = [],
+  entityMap: { [key: string]: RawDraftEntity } = {}
+): ContentBlock => {
+  const contentState = convertFromRaw({
+    blocks: [
+      {
+        key: genKey(),
+        text,
+        type: 'unstyled',
+        depth: 0,
+        inlineStyleRanges: [],
+        entityRanges,
+      },
+    ],
+    entityMap,
+  });
+  return contentState.getFirstBlock();
+};
+
+describe('test strategy', () => {
+  test.each([
+    ['testing empty string', '', []],
+    ['testing with : inside words', 'test:test', []],
+    ['testing with : at start and no text', ':', [[0, 1]]],
+    ['testing with : at start and text', ':test', [[0, 5]]],
+    ['testing with :', 'Lorem ipsum dolor : sit amet', [[17, 19]]],
+    ['testing with : and text', 'Lorem ipsum dolor :test sit amet', [[17, 23]]],
+    [
+      'testing with : on start and end',
+      'Lorem ipsum dolor :test: sit amet',
+      [[17, 24]],
+    ],
+    ['testing with : on end', 'Lorem ipsum dolor test: sit amet', []],
+    [
+      'testing with multiple : and text',
+      'Lorem :test ipsum dolor :test sit : amet :test:',
+      [
+        [5, 11],
+        [23, 29],
+        [33, 35],
+        [40, 47],
+      ],
+    ],
+  ])('%s', (_description, text, result) => {
+    const callback = jest.fn();
+    emojiSuggestionsStrategy(getBlock(text), callback);
+    expect(callback).toHaveBeenCalledTimes(result.length);
+    // eslint-disable-next-line no-plusplus
+    for (let i = 0; i < result.length; ++i) {
+      expect(callback).toHaveBeenNthCalledWith(i + 1, ...result[i]);
+    }
+  });
+});