@@ -307,9 +307,44 @@ void main() {
307
307
check (view.results).single.which (
308
308
isUnicodeResult (names: ['smile' ]));
309
309
});
310
+
311
+ Future <Iterable <EmojiAutocompleteResult >> resultsOf (
312
+ String query, {
313
+ Map <String , String > realmEmoji = const {},
314
+ Map <String , List <String >>? unicodeEmoji,
315
+ }) async {
316
+ final store = prepare (realmEmoji: realmEmoji, unicodeEmoji: unicodeEmoji);
317
+ final view = EmojiAutocompleteView .init (store: store,
318
+ query: EmojiAutocompleteQuery (query));
319
+ bool done = false ;
320
+ view.addListener (() { done = true ; });
321
+ await Future (() {});
322
+ check (done).isTrue ();
323
+ return view.results;
324
+ }
325
+
326
+ test ('results end-to-end' , () async {
327
+ final unicodeEmoji = {
328
+ '1f4d3' : ['notebook' ], '1f516' : ['bookmark' ], '1f4d6' : ['book' ]};
329
+
330
+ // Empty query -> base ordering.
331
+ check (await resultsOf ('' , unicodeEmoji: unicodeEmoji)).deepEquals ([
332
+ isUnicodeResult (names: ['notebook' ]),
333
+ isUnicodeResult (names: ['bookmark' ]),
334
+ isUnicodeResult (names: ['book' ]),
335
+ isZulipResult (),
336
+ ]);
337
+
338
+ // With query, exact match precedes prefix match precedes other.
339
+ check (await resultsOf ('book' , unicodeEmoji: unicodeEmoji)).deepEquals ([
340
+ isUnicodeResult (names: ['book' ]),
341
+ isUnicodeResult (names: ['bookmark' ]),
342
+ isUnicodeResult (names: ['notebook' ]),
343
+ ]);
344
+ });
310
345
});
311
346
312
- group ('EmojiAutocompleteQuery.match ' , () {
347
+ group ('EmojiAutocompleteQuery' , () {
313
348
EmojiCandidate unicode (List <String > names, {String ? emojiCode}) {
314
349
emojiCode ?? = '10ffff' ;
315
350
return EmojiCandidate (emojiType: ReactionType .unicodeEmoji,
@@ -333,63 +368,71 @@ void main() {
333
368
}
334
369
335
370
test ('one-word query matches anywhere in name' , () {
336
- check (matchOfName ('' , 'smile' )).match ;
337
- check (matchOfName ('s' , 'smile' )).match ;
338
- check (matchOfName ('sm' , 'smile' )).match ;
339
- check (matchOfName ('smile' , 'smile' )).match ;
340
- check (matchOfName ('m' , 'smile' )).match ;
341
- check (matchOfName ('mile' , 'smile' )).match ;
342
- check (matchOfName ('e' , 'smile' )).match ;
371
+ check (matchOfName ('' , 'smile' )).prefix ;
372
+ check (matchOfName ('s' , 'smile' )).prefix ;
373
+ check (matchOfName ('sm' , 'smile' )).prefix ;
374
+ check (matchOfName ('smile' , 'smile' )).exact ;
375
+ check (matchOfName ('m' , 'smile' )).other ;
376
+ check (matchOfName ('mile' , 'smile' )).other ;
377
+ check (matchOfName ('e' , 'smile' )).other ;
343
378
344
379
check (matchOfName ('smiley' , 'smile' )).none;
345
380
check (matchOfName ('a' , 'smile' )).none;
346
381
347
- check (matchOfName ('o' , 'open_book' )).match ;
348
- check (matchOfName ('open' , 'open_book' )).match ;
349
- check (matchOfName ('pe' , 'open_book' )).match ;
350
- check (matchOfName ('boo' , 'open_book' )).match ;
351
- check (matchOfName ('ok' , 'open_book' )).match ;
382
+ check (matchOfName ('o' , 'open_book' )).prefix ;
383
+ check (matchOfName ('open' , 'open_book' )).prefix ;
384
+ check (matchOfName ('pe' , 'open_book' )).other ;
385
+ check (matchOfName ('boo' , 'open_book' )).other ;
386
+ check (matchOfName ('ok' , 'open_book' )).other ;
352
387
});
353
388
354
389
test ('multi-word query matches from start of a word' , () {
355
- check (matchOfName ('open_' , 'open_book' )).match ;
356
- check (matchOfName ('open_b' , 'open_book' )).match ;
357
- check (matchOfName ('open_book' , 'open_book' )).match ;
390
+ check (matchOfName ('open_' , 'open_book' )).prefix ;
391
+ check (matchOfName ('open_b' , 'open_book' )).prefix ;
392
+ check (matchOfName ('open_book' , 'open_book' )).exact ;
358
393
359
394
check (matchOfName ('pen_' , 'open_book' )).none;
360
395
check (matchOfName ('n_b' , 'open_book' )).none;
361
396
362
- check (matchOfName ('blue_dia' , 'large_blue_diamond' )).match ;
397
+ check (matchOfName ('blue_dia' , 'large_blue_diamond' )).other ;
363
398
});
364
399
365
400
test ('spaces in query behave as underscores' , () {
366
- check (matchOfName ('open ' , 'open_book' )).match ;
367
- check (matchOfName ('open b' , 'open_book' )).match ;
368
- check (matchOfName ('open book' , 'open_book' )).match ;
401
+ check (matchOfName ('open ' , 'open_book' )).prefix ;
402
+ check (matchOfName ('open b' , 'open_book' )).prefix ;
403
+ check (matchOfName ('open book' , 'open_book' )).exact ;
369
404
370
405
check (matchOfName ('pen ' , 'open_book' )).none;
371
406
check (matchOfName ('n b' , 'open_book' )).none;
372
407
373
- check (matchOfName ('blue dia' , 'large_blue_diamond' )).match ;
408
+ check (matchOfName ('blue dia' , 'large_blue_diamond' )).other ;
374
409
});
375
410
376
411
test ('query is lower-cased' , () {
377
- check (matchOfName ('Smi' , 'smile' )).match ;
412
+ check (matchOfName ('Smi' , 'smile' )).prefix ;
378
413
});
379
414
380
415
test ('query matches aliases same way as primary name' , () {
381
- check (matchOfNames ('a' , ['a' , 'b' ])).match ;
382
- check (matchOfNames ('b' , ['a' , 'b' ])).match ;
416
+ check (matchOfNames ('a' , ['a' , 'b' ])).exact ;
417
+ check (matchOfNames ('b' , ['a' , 'b' ])).exact ;
383
418
check (matchOfNames ('c' , ['a' , 'b' ])).none;
384
419
385
- check (matchOfNames ('pe' , ['x' , 'open_book' ])).match ;
386
- check (matchOfNames ('ok' , ['x' , 'open_book' ])).match ;
420
+ check (matchOfNames ('pe' , ['x' , 'open_book' ])).other ;
421
+ check (matchOfNames ('ok' , ['x' , 'open_book' ])).other ;
387
422
388
- check (matchOfNames ('open_' , ['x' , 'open_book' ])).match ;
389
- check (matchOfNames ('open b' , ['x' , 'open_book' ])).match ;
423
+ check (matchOfNames ('open_' , ['x' , 'open_book' ])).prefix ;
424
+ check (matchOfNames ('open b' , ['x' , 'open_book' ])).prefix ;
390
425
check (matchOfNames ('pen_' , ['x' , 'open_book' ])).none;
391
426
392
- check (matchOfNames ('Smi' , ['x' , 'smile' ])).match;
427
+ check (matchOfNames ('Smi' , ['x' , 'smile' ])).prefix;
428
+ });
429
+
430
+ test ('best match among name and aliases prevails' , () {
431
+ check (matchOfNames ('a' , ['ab' , 'a' , 'ba' , 'x' ])).exact;
432
+ check (matchOfNames ('a' , ['ba' , 'ab' , 'x' ])).prefix;
433
+ check (matchOfNames ('a' , ['ba' , 'ab' ])).prefix;
434
+ check (matchOfNames ('a' , ['ba' , 'x' ])).other;
435
+ check (matchOfNames ('a' , ['x' , 'y' , 'z' ])).none;
393
436
});
394
437
395
438
test ('query matches literal Unicode value' , () {
@@ -403,13 +446,13 @@ void main() {
403
446
check (matchOfLiteral ('1f642' , aka: '1f642' , '1f642' )).none;
404
447
405
448
// Matching the Unicode value the code describes does count…
406
- check (matchOfLiteral ('🙂' , aka: '\u {1f642}' , '1f642' )).match ;
449
+ check (matchOfLiteral ('🙂' , aka: '\u {1f642}' , '1f642' )).exact ;
407
450
// … and failing to match it doesn't make a match.
408
451
check (matchOfLiteral ('🙁' , aka: '\u {1f641}' , '1f642' )).none;
409
452
410
453
// Multi-code-point emoji work fine.
411
454
check (matchOfLiteral ('🏳🌈' , aka: '\u {1f3f3}\u {200d}\u {1f308}' ,
412
- '1f3f3-200d-1f308' )).match ;
455
+ '1f3f3-200d-1f308' )).exact ;
413
456
// Only exact matches count; no partial matches.
414
457
check (matchOfLiteral ('🏳' , aka: '\u {1f3f3}' ,
415
458
'1f3f3-200d-1f308' )).none;
@@ -430,11 +473,11 @@ void main() {
430
473
resolvedStillUrl: eg.realmUrl.resolve ('/emoji/1-still.png' )));
431
474
}
432
475
433
- check (matchOf ('eqeq' , realmCandidate ('eqeq' ))).match ;
434
- check (matchOf ('open_' , realmCandidate ('open_book' ))).match ;
476
+ check (matchOf ('eqeq' , realmCandidate ('eqeq' ))).exact ;
477
+ check (matchOf ('open_' , realmCandidate ('open_book' ))).prefix ;
435
478
check (matchOf ('n_b' , realmCandidate ('open_book' ))).none;
436
- check (matchOf ('blue dia' , realmCandidate ('large_blue_diamond' ))).match ;
437
- check (matchOf ('Smi' , realmCandidate ('smile' ))).match ;
479
+ check (matchOf ('blue dia' , realmCandidate ('large_blue_diamond' ))).other ;
480
+ check (matchOf ('Smi' , realmCandidate ('smile' ))).prefix ;
438
481
});
439
482
440
483
test ('can match Zulip extra emoji' , () {
@@ -446,11 +489,32 @@ void main() {
446
489
emojiType: ReactionType .zulipExtraEmoji,
447
490
emojiCode: 'zulip' , emojiName: 'zulip' ));
448
491
449
- check (matchOf ('z' , zulipCandidate)).match ;
450
- check (matchOf ('Zulip' , zulipCandidate)).match ;
451
- check (matchOf ('p' , zulipCandidate)).match ;
492
+ check (matchOf ('z' , zulipCandidate)).prefix ;
493
+ check (matchOf ('Zulip' , zulipCandidate)).exact ;
494
+ check (matchOf ('p' , zulipCandidate)).other ;
452
495
check (matchOf ('x' , zulipCandidate)).none;
453
496
});
497
+
498
+ int ? rankOf (String query, EmojiCandidate candidate) {
499
+ return EmojiAutocompleteQuery (query).testCandidate (candidate)? .rank;
500
+ }
501
+
502
+ void checkPrecedes (String query, EmojiCandidate a, EmojiCandidate b) {
503
+ check (rankOf (query, a)! ).isLessThan (rankOf (query, b)! );
504
+ }
505
+
506
+ test ('ranks exact before prefix before other match' , () {
507
+ checkPrecedes ('o' , unicode (['o' ]), unicode (['onion' ]));
508
+ checkPrecedes ('o' , unicode (['onion' ]), unicode (['book' ]));
509
+ });
510
+
511
+ test ('full list of ranks' , () {
512
+ check ([
513
+ rankOf ('o' , unicode (['o' ])), // exact
514
+ rankOf ('o' , unicode (['onion' ])), // prefix
515
+ rankOf ('o' , unicode (['book' ])), // other
516
+ ]).deepEquals ([0 , 1 , 2 ]);
517
+ });
454
518
});
455
519
}
456
520
@@ -476,7 +540,9 @@ extension EmojiCandidateChecks on Subject<EmojiCandidate> {
476
540
}
477
541
478
542
extension EmojiMatchQualityChecks on Subject <EmojiMatchQuality ?> {
479
- void get match => equals (EmojiMatchQuality .match);
543
+ void get exact => equals (EmojiMatchQuality .exact);
544
+ void get prefix => equals (EmojiMatchQuality .prefix);
545
+ void get other => equals (EmojiMatchQuality .other);
480
546
void get none => isNull ();
481
547
}
482
548
0 commit comments