Skip to content

Commit f27a038

Browse files
committed
autocomplete: Add "alphabetical order" criterion
In @-mention autocomplete, users are suggested based on: 1. Recent activity in the current topic/stream. 2. Recent DM conversations. 3. Human vs. Bot users (human users come first). 4. Alphabetical order. Fixes: #228
1 parent 8ac0e16 commit f27a038

File tree

2 files changed

+64
-8
lines changed

2 files changed

+64
-8
lines changed

lib/model/autocomplete.dart

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,10 @@ class MentionAutocompleteView extends ChangeNotifier {
244244
final dmsResult = compareByDms(userA, userB, store: store);
245245
if (dmsResult != 0) return dmsResult;
246246

247-
return compareByBotStatus(userA, userB);
247+
final botStatusResult = compareByBotStatus(userA, userB);
248+
if (botStatusResult != 0) return botStatusResult;
249+
250+
return compareByAlphabeticalOrder(userA, userB, store: store);
248251
}
249252

250253
/// Determines which of the two users has more recent activity (messages sent
@@ -326,6 +329,21 @@ class MentionAutocompleteView extends ChangeNotifier {
326329
};
327330
}
328331

332+
/// Compares the two users by [User.fullName] case-insensitively.
333+
///
334+
/// Returns a negative number if `userA`'s `fullName` comes first alphabetically,
335+
/// returns a positive number if `userB`'s `fullName` comes first alphabetically,
336+
/// and returns `0` if both users have identical `fullName`.
337+
@visibleForTesting
338+
static int compareByAlphabeticalOrder(User userA, User userB,
339+
{required PerAccountStore store}) {
340+
final userAName = store.autocompleteViewManager.autocompleteDataCache
341+
.normalizedNameForUser(userA);
342+
final userBName = store.autocompleteViewManager.autocompleteDataCache
343+
.normalizedNameForUser(userB);
344+
return userAName.compareTo(userBName);
345+
}
346+
329347
@override
330348
void dispose() {
331349
store.autocompleteViewManager.unregisterMentionAutocomplete(this);

test/model/autocomplete_test.dart

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,29 @@ void main() {
510510
});
511511
});
512512

513+
group('compareByAlphabeticalOrder', () {
514+
int compareAB(String aName, String bName) => MentionAutocompleteView.compareByAlphabeticalOrder(
515+
eg.user(fullName: aName), eg.user(fullName: bName), store: store);
516+
517+
test("userA's fullName comes first than userB's fullName -> favor userA", () async {
518+
await prepare();
519+
check(compareAB('alice', 'brian')).isLessThan(0);
520+
check(compareAB('alice', 'BRIAN')).isLessThan(0);
521+
});
522+
523+
test("userB's fullName comes first than userA's fullName -> favor userB", () async {
524+
await prepare();
525+
check(compareAB('brian', 'alice')).isGreaterThan(0);
526+
check(compareAB('BRIAN', 'alice')).isGreaterThan(0);
527+
});
528+
529+
test('both users have identical fullName -> favor none', () async {
530+
await prepare();
531+
check(compareAB('alice', 'alice')).equals(0);
532+
check(compareAB('BRIAN', 'brian')).equals(0);
533+
});
534+
});
535+
513536
group('ranking across signals', () {
514537
void checkPrecedes(Narrow narrow, User userA, Iterable<User> usersB) {
515538
final view = MentionAutocompleteView.init(store: store, narrow: narrow);
@@ -530,15 +553,17 @@ void main() {
530553
}
531554

532555
test('TopicNarrow: topic recency > stream recency > DM recency '
533-
'> human vs. bot user', () async {
556+
'> human vs. bot user > alphabetical order', () async {
534557
final users = [
535-
eg.user(),
558+
eg.user(fullName: 'Z'),
536559
eg.user(),
537560
eg.user(isBot: true),
538561
eg.user(),
539562
eg.user(),
540563
eg.user(),
541564
eg.user(isBot: true),
565+
eg.user(fullName: 'ab'),
566+
eg.user(fullName: 'bc'),
542567
];
543568
final stream = eg.stream();
544569
final narrow = TopicNarrow(stream.streamId, 'this');
@@ -555,16 +580,20 @@ void main() {
555580
checkPrecedes(narrow, users[2], users.skip(3));
556581
checkRankEqual(narrow, [users[3], users[4]]);
557582
checkPrecedes(narrow, users[5], users.skip(6));
583+
checkPrecedes(narrow, users[7], users.skip(8));
558584
});
559585

560-
test('ChannelNarrow: stream recency > DM recency > human vs. bot user', () async {
586+
test('ChannelNarrow: stream recency > DM recency > human vs. bot user '
587+
'> alphabetical order', () async {
561588
final users = [
562-
eg.user(isBot: true),
589+
eg.user(isBot: true, fullName: 'Z'),
563590
eg.user(),
564591
eg.user(),
565592
eg.user(),
566593
eg.user(),
567594
eg.user(isBot: true),
595+
eg.user(fullName: 'ab', isBot: true),
596+
eg.user(fullName: 'bc', isBot: true),
568597
];
569598
final stream = eg.stream();
570599
final narrow = ChannelNarrow(stream.streamId);
@@ -579,17 +608,20 @@ void main() {
579608
checkPrecedes(narrow, users[1], users.skip(2));
580609
checkRankEqual(narrow, [users[2], users[3]]);
581610
checkPrecedes(narrow, users[4], users.skip(5));
611+
checkPrecedes(narrow, users[6], users.skip(7));
582612
});
583613

584614
test('DmNarrow: DM recency > this-conversation recency or stream recency '
585-
'or human vs. bot user', () async {
615+
'or human vs. bot user or alphabetical order', () async {
586616
final users = [
587-
eg.user(isBot: true),
617+
eg.user(isBot: true, fullName: 'Z'),
588618
eg.user(),
589619
eg.user(),
590620
eg.user(),
591621
eg.user(),
592622
eg.user(isBot: true),
623+
eg.user(fullName: 'ab'),
624+
eg.user(fullName: 'bc'),
593625
];
594626
await prepare(users: users, messages: [
595627
eg.dmMessage(from: users[3], to: [eg.selfUser]),
@@ -610,6 +642,7 @@ void main() {
610642
checkPrecedes(narrow, users[1], users.skip(3));
611643
checkPrecedes(narrow, users[2], users.skip(3));
612644
checkPrecedes(narrow, users[4], users.skip(5));
645+
checkPrecedes(narrow, users[6], users.skip(7));
613646
}
614647
});
615648

@@ -648,6 +681,8 @@ void main() {
648681
eg.user(userId: 5, fullName: 'User Five'),
649682
eg.user(userId: 6, fullName: 'User Six', isBot: true),
650683
eg.user(userId: 7, fullName: 'User Seven'),
684+
eg.user(userId: 8, fullName: 'User Xy', isBot: true),
685+
eg.user(userId: 9, fullName: 'User Xz', isBot: true),
651686
];
652687

653688
await prepare(users: users, messages: [
@@ -664,8 +699,9 @@ void main() {
664699
// 1. Users most recent in the current topic/stream.
665700
// 2. Users most recent in the DM conversations.
666701
// 3. Human vs. Bot users (human users come first).
702+
// 4. Alphabetical order.
667703
check(await getResults(topicNarrow, MentionAutocompleteQuery('')))
668-
.deepEquals([1, 5, 4, 2, 3, 7, 6]);
704+
.deepEquals([1, 5, 4, 2, 7, 3, 6, 8, 9]);
669705

670706
// Check the ranking applies also to results filtered by a query.
671707
check(await getResults(topicNarrow, MentionAutocompleteQuery('t')))
@@ -674,6 +710,8 @@ void main() {
674710
.deepEquals([5, 4]);
675711
check(await getResults(topicNarrow, MentionAutocompleteQuery('s')))
676712
.deepEquals([7, 6]);
713+
check(await getResults(topicNarrow, MentionAutocompleteQuery('user x')))
714+
.deepEquals([8, 9]);
677715
});
678716
});
679717
}

0 commit comments

Comments
 (0)