@@ -202,6 +202,151 @@ void main() {
202202 check (store.allEmojiCandidates ()).identicalTo (candidates);
203203 });
204204 });
205+
206+ group ('EmojiAutocompleteQuery.matches' , () {
207+ EmojiCandidate unicode (List <String > names, {String ? emojiCode}) {
208+ emojiCode ?? = '10ffff' ;
209+ return EmojiCandidate (emojiType: ReactionType .unicodeEmoji,
210+ emojiCode: emojiCode,
211+ emojiName: names.first, aliases: names.sublist (1 ),
212+ emojiDisplay: UnicodeEmojiDisplay (
213+ emojiName: names.first,
214+ emojiUnicode: tryParseEmojiCodeToUnicode (emojiCode)! ));
215+ }
216+
217+ bool matchesName (String query, String emojiName) {
218+ return EmojiAutocompleteQuery (query).matches (unicode ([emojiName]));
219+ }
220+
221+ test ('one-word query matches anywhere in name' , () {
222+ check (matchesName ('' , 'smile' )).isTrue ();
223+ check (matchesName ('s' , 'smile' )).isTrue ();
224+ check (matchesName ('sm' , 'smile' )).isTrue ();
225+ check (matchesName ('smile' , 'smile' )).isTrue ();
226+ check (matchesName ('m' , 'smile' )).isTrue ();
227+ check (matchesName ('mile' , 'smile' )).isTrue ();
228+ check (matchesName ('e' , 'smile' )).isTrue ();
229+
230+ check (matchesName ('smiley' , 'smile' )).isFalse ();
231+ check (matchesName ('a' , 'smile' )).isFalse ();
232+
233+ check (matchesName ('o' , 'open_book' )).isTrue ();
234+ check (matchesName ('open' , 'open_book' )).isTrue ();
235+ check (matchesName ('pe' , 'open_book' )).isTrue ();
236+ check (matchesName ('boo' , 'open_book' )).isTrue ();
237+ check (matchesName ('ok' , 'open_book' )).isTrue ();
238+ });
239+
240+ test ('multi-word query matches from start of a word' , () {
241+ check (matchesName ('open_' , 'open_book' )).isTrue ();
242+ check (matchesName ('open_b' , 'open_book' )).isTrue ();
243+ check (matchesName ('open_book' , 'open_book' )).isTrue ();
244+
245+ check (matchesName ('pen_' , 'open_book' )).isFalse ();
246+ check (matchesName ('n_b' , 'open_book' )).isFalse ();
247+
248+ check (matchesName ('blue_dia' , 'large_blue_diamond' )).isTrue ();
249+ });
250+
251+ test ('spaces in query behave as underscores' , () {
252+ check (matchesName ('open ' , 'open_book' )).isTrue ();
253+ check (matchesName ('open b' , 'open_book' )).isTrue ();
254+ check (matchesName ('open book' , 'open_book' )).isTrue ();
255+
256+ check (matchesName ('pen ' , 'open_book' )).isFalse ();
257+ check (matchesName ('n b' , 'open_book' )).isFalse ();
258+
259+ check (matchesName ('blue dia' , 'large_blue_diamond' )).isTrue ();
260+ });
261+
262+ test ('query is lower-cased' , () {
263+ check (matchesName ('Smi' , 'smile' )).isTrue ();
264+ });
265+
266+ test ('query matches aliases same way as primary name' , () {
267+ bool matchesNames (String query, List <String > names) {
268+ return EmojiAutocompleteQuery (query).matches (unicode (names));
269+ }
270+
271+ check (matchesNames ('a' , ['a' , 'b' ])).isTrue ();
272+ check (matchesNames ('b' , ['a' , 'b' ])).isTrue ();
273+ check (matchesNames ('c' , ['a' , 'b' ])).isFalse ();
274+
275+ check (matchesNames ('pe' , ['x' , 'open_book' ])).isTrue ();
276+ check (matchesNames ('ok' , ['x' , 'open_book' ])).isTrue ();
277+
278+ check (matchesNames ('open_' , ['x' , 'open_book' ])).isTrue ();
279+ check (matchesNames ('open b' , ['x' , 'open_book' ])).isTrue ();
280+ check (matchesNames ('pen_' , ['x' , 'open_book' ])).isFalse ();
281+
282+ check (matchesNames ('Smi' , ['x' , 'smile' ])).isTrue ();
283+ });
284+
285+ test ('query matches literal Unicode value' , () {
286+ bool matchesLiteral (String query, String emojiCode, {required String aka}) {
287+ assert (aka == query);
288+ return EmojiAutocompleteQuery (query)
289+ .matches (unicode (['asdf' ], emojiCode: emojiCode));
290+ }
291+
292+ // Matching the code, in hex, doesn't count.
293+ check (matchesLiteral ('1f642' , aka: '1f642' , '1f642' )).isFalse ();
294+
295+ // Matching the Unicode value the code describes does count…
296+ check (matchesLiteral ('🙂' , aka: '\u {1f642}' , '1f642' )).isTrue ();
297+ // … and failing to match it doesn't make a match.
298+ check (matchesLiteral ('🙁' , aka: '\u {1f641}' , '1f642' )).isFalse ();
299+
300+ // Multi-code-point emoji work fine.
301+ check (matchesLiteral ('🏳🌈' , aka: '\u {1f3f3}\u {200d}\u {1f308}' ,
302+ '1f3f3-200d-1f308' )).isTrue ();
303+ // Only exact matches count; no partial matches.
304+ check (matchesLiteral ('🏳' , aka: '\u {1f3f3}' ,
305+ '1f3f3-200d-1f308' )).isFalse ();
306+ check (matchesLiteral ('🌈' , aka: '\u {200d}\u {1f308}' ,
307+ '1f3f3-200d-1f308' )).isFalse ();
308+ check (matchesLiteral ('🏳🌈' , aka: '\u {1f3f3}\u {200d}\u {1f308}' ,
309+ '1f3f3' )).isFalse ();
310+ });
311+
312+ test ('can match realm emoji' , () {
313+ EmojiCandidate realmCandidate (String emojiName) {
314+ return EmojiCandidate (
315+ emojiType: ReactionType .realmEmoji,
316+ emojiCode: '1' , emojiName: emojiName, aliases: null ,
317+ emojiDisplay: ImageEmojiDisplay (
318+ emojiName: emojiName,
319+ resolvedUrl: eg.realmUrl.resolve ('/emoji/1.png' ),
320+ resolvedStillUrl: eg.realmUrl.resolve ('/emoji/1-still.png' )));
321+ }
322+
323+ check (EmojiAutocompleteQuery ('eqeq' )
324+ .matches (realmCandidate ('eqeq' ))).isTrue ();
325+ check (EmojiAutocompleteQuery ('open_' )
326+ .matches (realmCandidate ('open_book' ))).isTrue ();
327+ check (EmojiAutocompleteQuery ('n_b' )
328+ .matches (realmCandidate ('open_book' ))).isFalse ();
329+ check (EmojiAutocompleteQuery ('blue dia' )
330+ .matches (realmCandidate ('large_blue_diamond' ))).isTrue ();
331+ check (EmojiAutocompleteQuery ('Smi' )
332+ .matches (realmCandidate ('smile' ))).isTrue ();
333+ });
334+
335+ test ('can match Zulip extra emoji' , () {
336+ final store = eg.store ();
337+ final zulipCandidate = EmojiCandidate (
338+ emojiType: ReactionType .zulipExtraEmoji,
339+ emojiCode: 'zulip' , emojiName: 'zulip' , aliases: null ,
340+ emojiDisplay: store.emojiDisplayFor (
341+ emojiType: ReactionType .zulipExtraEmoji,
342+ emojiCode: 'zulip' , emojiName: 'zulip' ));
343+
344+ check (EmojiAutocompleteQuery ('z' ).matches (zulipCandidate)).isTrue ();
345+ check (EmojiAutocompleteQuery ('Zulip' ).matches (zulipCandidate)).isTrue ();
346+ check (EmojiAutocompleteQuery ('p' ).matches (zulipCandidate)).isTrue ();
347+ check (EmojiAutocompleteQuery ('x' ).matches (zulipCandidate)).isFalse ();
348+ });
349+ });
205350}
206351
207352extension EmojiDisplayChecks on Subject <EmojiDisplay > {
0 commit comments