Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 08a2d81

Browse files
commonlawfeatureRyan Browne
andauthored
Resolve emoji autocomplete not being temporally consistent (#8086)
* Adds a test to demonstrate the issue with emoji autocomplete reported in element-hq/element-web#19302. Signed-off-by: Ryan Browne <[email protected]> * Trim trailing `:` when checking for autocompletes for emoji. Closes element-hq/element-web#19302 Signed-off-by: Ryan Browne <[email protected]> * Move all references to the emoji delimiter character to reference a constant. Signed-off-by: Ryan Browne <[email protected]> * Revert "Move all references to the emoji delimiter character to reference a constant." This reverts commit ac09e71. Signed-off-by: Ryan Browne <[email protected]> * Rename variable. Signed-off-by: Ryan Browne <[email protected]> * Make the test file a .js file. Signed-off-by: Ryan Browne <[email protected]> * Update quotes to match style and make a valid stubbed room. Signed-off-by: Ryan Browne <[email protected]> * Fix variable name and test reporting. Signed-off-by: Ryan Browne <[email protected]> * Use str.replace with a regex. Signed-off-by: Ryan Browne <[email protected]> * Use an improved regex that does not have have to iterate through the entire string, and can just backtrack at most the last 2 characters. Signed-off-by: Ryan Browne <[email protected]> * Revert "Use an improved regex that does not have have to iterate through the entire string, and can just backtrack at most the last 2 characters." This regex is very efficient, but requires a specific form of the emoji shortcode that it is not clear is within our control. This is a restriction that is not required by the technicalities of solving the bug this PR is attempting to fix. (It requires that an emoji shortcode end with a colon.) This reverts commit 220cb0e. Signed-off-by: Ryan Browne <[email protected]> Co-authored-by: Ryan Browne <[email protected]>
1 parent c9880fe commit 08a2d81

File tree

2 files changed

+77
-1
lines changed

2 files changed

+77
-1
lines changed

src/autocomplete/EmojiProvider.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ Copyright 2016 Aviral Dasgupta
33
Copyright 2017 Vector Creations Ltd
44
Copyright 2017, 2018 New Vector Ltd
55
Copyright 2019 The Matrix.org Foundation C.I.C.
6+
Copyright 2022 Ryan Browne <[email protected]>
67
78
Licensed under the Apache License, Version 2.0 (the "License");
89
you may not use this file except in compliance with the License.
@@ -62,6 +63,13 @@ function score(query, space) {
6263
}
6364
}
6465

66+
function colonsTrimmed(str: string): string {
67+
// Trim off leading and potentially trailing `:` to correctly match the emoji data as they exist in emojibase.
68+
// Notes: The regex is pinned to the start and end of the string so that we can use the lazy-capturing `*?` matcher.
69+
// It needs to be lazy so that the trailing `:` is not captured in the replacement group, if it exists.
70+
return str.replace(/^:(.*?):?$/, "$1");
71+
}
72+
6573
export default class EmojiProvider extends AutocompleteProvider {
6674
matcher: QueryMatcher<ISortedEmoji>;
6775
nameMatcher: QueryMatcher<ISortedEmoji>;
@@ -108,8 +116,9 @@ export default class EmojiProvider extends AutocompleteProvider {
108116
// then sort by score (Infinity if matchedString not in shortcode)
109117
sorters.push(c => score(matchedString, c.emoji.shortcodes[0]));
110118
// then sort by max score of all shortcodes, trim off the `:`
119+
const trimmedMatch = colonsTrimmed(matchedString);
111120
sorters.push(c => Math.min(
112-
...c.emoji.shortcodes.map(s => score(matchedString.substring(1), s)),
121+
...c.emoji.shortcodes.map(s => score(trimmedMatch, s)),
113122
));
114123
// If the matchedString is not empty, sort by length of shortcode. Example:
115124
// matchedString = ":bookmark"
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
Copyright 2022 Ryan Browne <[email protected]>
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import EmojiProvider from '../../src/autocomplete/EmojiProvider';
18+
import { mkStubRoom } from '../test-utils/test-utils';
19+
20+
const EMOJI_SHORTCODES = [
21+
":+1",
22+
":heart",
23+
":grinning",
24+
":hand",
25+
":man",
26+
":sweat",
27+
":monkey",
28+
":boat",
29+
":mailbox",
30+
":cop",
31+
":bow",
32+
":kiss",
33+
":golf",
34+
];
35+
36+
// Some emoji shortcodes are too short and do not actually trigger autocompletion until the ending `:`.
37+
// This means that we cannot compare their autocompletion before and after the ending `:` and have
38+
// to simply assert that the final completion with the colon is the exact emoji.
39+
const TOO_SHORT_EMOJI_SHORTCODE = [
40+
{ emojiShortcode: ":o", expectedEmoji: "⭕️" },
41+
];
42+
43+
describe('EmojiProvider', function() {
44+
const testRoom = mkStubRoom(undefined, undefined, undefined);
45+
46+
it.each(EMOJI_SHORTCODES)('Returns consistent results after final colon %s', async function(emojiShortcode) {
47+
const ep = new EmojiProvider(testRoom);
48+
const range = { "beginning": true, "start": 0, "end": 3 };
49+
const completionsBeforeColon = await ep.getCompletions(emojiShortcode, range);
50+
const completionsAfterColon = await ep.getCompletions(emojiShortcode + ':', range);
51+
52+
const firstCompletionWithoutColon = completionsBeforeColon[0].completion;
53+
const firstCompletionWithColon = completionsAfterColon[0].completion;
54+
55+
expect(firstCompletionWithoutColon).toEqual(firstCompletionWithColon);
56+
});
57+
58+
it.each(
59+
TOO_SHORT_EMOJI_SHORTCODE,
60+
)('Returns correct results after final colon $emojiShortcode', async ({ emojiShortcode, expectedEmoji }) => {
61+
const ep = new EmojiProvider(testRoom);
62+
const range = { "beginning": true, "start": 0, "end": 3 };
63+
const completions = await ep.getCompletions(emojiShortcode + ':', range);
64+
65+
expect(completions[0].completion).toEqual(expectedEmoji);
66+
});
67+
});

0 commit comments

Comments
 (0)