Skip to content

Commit

Permalink
WIP emoji: Order "popular" emoji canonically amongst themselves; TODO…
Browse files Browse the repository at this point in the history
… breaks an action_sheet test

As a bonus, this provides the popular emoji as candidates even
when we haven't yet fetched the server's emoji data.
  • Loading branch information
gnprice committed Dec 8, 2024
1 parent d4a5a04 commit c06617f
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 0 deletions.
6 changes: 6 additions & 0 deletions lib/model/emoji.dart
Original file line number Diff line number Diff line change
Expand Up @@ -283,12 +283,18 @@ class EmojiStoreImpl with EmojiStore {
List<EmojiCandidate> _generateAllCandidates() {
final results = <EmojiCandidate>[];

// Include the "popular" emoji, in their canonical order
// relative to each other.
results.addAll(zulipPopularEmojis);

final namesOverridden = {
for (final emoji in realmEmoji.values) emoji.name,
'zulip',
};
// TODO(log) if _serverEmojiData missing
for (final entry in (_serverEmojiData ?? {}).entries) {
if (_popularEmojiCodes.contains(entry.key)) continue;

final allNames = entry.value;
final String emojiName;
final List<String>? aliases;
Expand Down
88 changes: 88 additions & 0 deletions test/model/emoji_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ void main() {
..aliases.isEmpty();
}

List<Condition<Object?>> arePopularCandidates = zulipPopularEmojis.map(
(c) => isUnicodeCandidate(c.emojiCode, null)).toList();

group('allEmojiCandidates', () {
// TODO test emojiDisplay of candidates matches emojiDisplayFor

Expand All @@ -123,6 +126,40 @@ void main() {
return store;
}

test('popular emoji appear even when no server emoji data', () {
final store = prepare(unicodeEmoji: null);
check(store.allEmojiCandidates()).deepEquals([
...arePopularCandidates,
isZulipCandidate(),
]);
});

test('popular emoji appear in their canonical order', () {
// In the server's emoji data, have the popular emoji in a permuted order,
// and interspersed with other emoji.
final store = prepare(unicodeEmoji: {
'1f603': ['smiley'],
for (final candidate in zulipPopularEmojis.skip(3))
candidate.emojiCode: [candidate.emojiName, ...candidate.aliases],
'1f34a': ['orange', 'tangerine', 'mandarin'],
for (final candidate in zulipPopularEmojis.take(3))
candidate.emojiCode: [candidate.emojiName, ...candidate.aliases],
'1f516': ['bookmark'],
});
// In the allEmojiCandidates result, the popular emoji come first
// and are in their canonical order, even though the other Unicode emoji
// are in the same order they were given in.
check(store.allEmojiCandidates()).deepEquals([
for (final candidate in zulipPopularEmojis)
isUnicodeCandidate(candidate.emojiCode,
[candidate.emojiName, ...candidate.aliases]),
isUnicodeCandidate('1f603', ['smiley']),
isUnicodeCandidate('1f34a', ['orange', 'tangerine', 'mandarin']),
isUnicodeCandidate('1f516', ['bookmark']),
isZulipCandidate(),
]);
});

test('realm emoji overrides Unicode emoji', () {
final store = prepare(realmEmoji: {
'1': eg.realmEmojiItem(emojiCode: '1', emojiName: 'smiley'),
Expand All @@ -131,6 +168,7 @@ void main() {
'1f603': ['smiley'],
});
check(store.allEmojiCandidates()).deepEquals([
...arePopularCandidates,
isUnicodeCandidate('1f516', ['bookmark']),
isRealmCandidate(emojiCode: '1', emojiName: 'smiley'),
isZulipCandidate(),
Expand All @@ -144,6 +182,7 @@ void main() {
'1f34a': ['orange', 'tangerine', 'mandarin'],
});
check(store.allEmojiCandidates()).deepEquals([
...arePopularCandidates,
isUnicodeCandidate('1f34a', ['orange', 'mandarin']),
isRealmCandidate(emojiCode: '1', emojiName: 'tangerine'),
isZulipCandidate(),
Expand All @@ -157,6 +196,7 @@ void main() {
'1f34a': ['orange', 'tangerine', 'mandarin'],
});
check(store.allEmojiCandidates()).deepEquals([
...arePopularCandidates,
isUnicodeCandidate('1f34a', ['tangerine', 'mandarin']),
isRealmCandidate(emojiCode: '1', emojiName: 'orange'),
isZulipCandidate(),
Expand All @@ -166,13 +206,15 @@ void main() {
test('updates on setServerEmojiData', () {
final store = prepare();
check(store.allEmojiCandidates()).deepEquals([
...arePopularCandidates,
isZulipCandidate(),
]);

store.setServerEmojiData(ServerEmojiData(codeToNames: {
'1f516': ['bookmark'],
}));
check(store.allEmojiCandidates()).deepEquals([
...arePopularCandidates,
isUnicodeCandidate('1f516', ['bookmark']),
isZulipCandidate(),
]);
Expand All @@ -181,13 +223,15 @@ void main() {
test('updates on RealmEmojiUpdateEvent', () {
final store = prepare();
check(store.allEmojiCandidates()).deepEquals([
...arePopularCandidates,
isZulipCandidate(),
]);

store.handleEvent(RealmEmojiUpdateEvent(id: 1, realmEmoji: {
'1': eg.realmEmojiItem(emojiCode: '1', emojiName: 'happy'),
}));
check(store.allEmojiCandidates()).deepEquals([
...arePopularCandidates,
isRealmCandidate(emojiCode: '1', emojiName: 'happy'),
isZulipCandidate(),
]);
Expand Down Expand Up @@ -220,6 +264,9 @@ void main() {
isZulipCandidate());
}

List<Condition<Object?>> arePopularResults = zulipPopularEmojis.map(
(c) => isUnicodeResult(emojiCode: c.emojiCode)).toList();

PerAccountStore prepare({
Map<String, String> realmEmoji = const {},
Map<String, List<String>>? unicodeEmoji,
Expand All @@ -245,6 +292,7 @@ void main() {
await Future(() {});
check(done).isTrue();
check(view.results).deepEquals([
...arePopularResults,
isRealmResult(emojiName: 'happy'),
isZulipResult(),
isUnicodeResult(names: ['bookmark']),
Expand Down Expand Up @@ -286,6 +334,45 @@ void main() {
return view.results;
}

test('results preserve order of popular emoji within each rank', () async {
// In other words, the sorting by rank is a stable sort.

// Full results list matches allEmojiCandidates.
check(prepare().allEmojiCandidates())
.deepEquals([...arePopularCandidates, isZulipCandidate()]);
check(await resultsOf(''))
.deepEquals([...arePopularResults, isZulipResult()]);

// Same list written out explicitly, for comparison with the cases below.
check(await resultsOf('')).deepEquals([
isUnicodeResult(names: ['+1', 'thumbs_up', 'like']),
isUnicodeResult(names: ['tada']),
isUnicodeResult(names: ['smile']),
isUnicodeResult(names: ['heart', 'love', 'love_you']),
isUnicodeResult(names: ['working_on_it', 'hammer_and_wrench', 'tools']),
isUnicodeResult(names: ['octopus']),
isZulipResult(),
]);

check(await resultsOf('t')).deepEquals([
// prefix
isUnicodeResult(names: ['+1', 'thumbs_up', 'like']),
isUnicodeResult(names: ['tada']),
isUnicodeResult(names: ['working_on_it', 'hammer_and_wrench', 'tools']),
// other
isUnicodeResult(names: ['heart', 'love', 'love_you']),
isUnicodeResult(names: ['octopus']),
]);

check(await resultsOf('h')).deepEquals([
// prefix
isUnicodeResult(names: ['heart', 'love', 'love_you']),
isUnicodeResult(names: ['working_on_it', 'hammer_and_wrench', 'tools']),
// other
isUnicodeResult(names: ['+1', 'thumbs_up', 'like']),
]);
});

test('results end-to-end', () async {
// (See more detailed rank tests below, on EmojiAutocompleteQuery.)

Expand All @@ -294,6 +381,7 @@ void main() {

// Empty query -> base ordering.
check(await resultsOf('', unicodeEmoji: unicodeEmoji)).deepEquals([
...arePopularResults,
isZulipResult(),
isUnicodeResult(names: ['notebook']),
isUnicodeResult(names: ['bookmark']),
Expand Down

0 comments on commit c06617f

Please sign in to comment.