diff --git a/lib/model/emoji.dart b/lib/model/emoji.dart index 670c574c12..9dd6bd9732 100644 --- a/lib/model/emoji.dart +++ b/lib/model/emoji.dart @@ -115,13 +115,7 @@ mixin EmojiStore { /// /// See description in the web code: /// https://github.com/zulip/zulip/blob/83a121c7e/web/shared/src/typeahead.ts#L3-L21 - // Someday this list may start varying rather than being hard-coded, - // and then this will become a non-static member on EmojiStore. - // For now, though, the fact it's constant is convenient when writing - // tests of the logic that uses this data; so we guarantee it in the API. - static Iterable get popularEmojiCandidates { - return EmojiStoreImpl._popularCandidates; - } + Iterable popularEmojiCandidates(); Iterable allEmojiCandidates(); @@ -218,36 +212,54 @@ class EmojiStoreImpl extends PerAccountStoreBase with EmojiStore { /// retrieving the data. Map>? _serverEmojiData; - static final _popularCandidates = _generatePopularCandidates(); + List? _popularCandidates; - static List _generatePopularCandidates() { - EmojiCandidate candidate(String emojiCode, String emojiUnicode, - List names) { - final emojiName = names.removeAt(0); - assert(emojiUnicode == tryParseEmojiCodeToUnicode(emojiCode)); + List _generatePopularCandidates() { + EmojiCandidate candidate(String emojiCode, List names) { + final [emojiName, ...aliases] = names; + final emojiUnicode = tryParseEmojiCodeToUnicode(emojiCode); + assert(emojiUnicode != null); return EmojiCandidate(emojiType: ReactionType.unicodeEmoji, - emojiCode: emojiCode, emojiName: emojiName, aliases: names, + emojiCode: emojiCode, emojiName: emojiName, aliases: aliases, emojiDisplay: UnicodeEmojiDisplay( - emojiName: emojiName, emojiUnicode: emojiUnicode)); + emojiName: emojiName, emojiUnicode: emojiUnicode!)); } - return [ - // This list should match web: - // https://github.com/zulip/zulip/blob/83a121c7e/web/shared/src/typeahead.ts#L22-L29 - candidate('1f44d', '👍', ['+1', 'thumbs_up', 'like']), - candidate('1f389', '🎉', ['tada']), - candidate('1f642', '🙂', ['smile']), - candidate( '2764', '❤', ['heart', 'love', 'love_you']), - candidate('1f6e0', '🛠', ['working_on_it', 'hammer_and_wrench', 'tools']), - candidate('1f419', '🐙', ['octopus']), - ]; + if (_serverEmojiData == null) return []; + + final result = []; + for (final emojiCode in _popularEmojiCodesList) { + final names = _serverEmojiData![emojiCode]; + if (names == null) continue; + result.add(candidate(emojiCode, names)); + } + return result; } - static final _popularEmojiCodes = (() { - assert(_popularCandidates.every((c) => - c.emojiType == ReactionType.unicodeEmoji)); - return Set.of(_popularCandidates.map((c) => c.emojiCode)); + @override + Iterable popularEmojiCandidates() { + return _popularCandidates ??= _generatePopularCandidates(); + } + + /// Codes for the popular emoji, in order; all are Unicode emoji. + // This list should match web: + // https://github.com/zulip/zulip/blob/83a121c7e/web/shared/src/typeahead.ts#L22-L29 + static final List _popularEmojiCodesList = (() { + String check(String emojiCode, String emojiUnicode) { + assert(emojiUnicode == tryParseEmojiCodeToUnicode(emojiCode)); + return emojiCode; + } + return [ + check('1f44d', '👍'), + check('1f389', '🎉'), + check('1f642', '🙂'), + check('2764', '❤'), + check('1f6e0', '🛠'), + check('1f419', '🐙'), + ]; })(); + static final Set _popularEmojiCodes = Set.of(_popularEmojiCodesList); + static bool _isPopularEmoji(EmojiCandidate candidate) { return candidate.emojiType == ReactionType.unicodeEmoji && _popularEmojiCodes.contains(candidate.emojiCode); @@ -307,7 +319,7 @@ class EmojiStoreImpl extends PerAccountStoreBase with EmojiStore { // Include the "popular" emoji, in their canonical order // relative to each other. - results.addAll(_popularCandidates); + results.addAll(popularEmojiCandidates()); final namesOverridden = { for (final emoji in activeRealmEmoji) emoji.name, @@ -366,6 +378,7 @@ class EmojiStoreImpl extends PerAccountStoreBase with EmojiStore { @override void setServerEmojiData(ServerEmojiData data) { _serverEmojiData = data.codeToNames; + _popularCandidates = null; _allEmojiCandidates = null; } diff --git a/lib/model/store.dart b/lib/model/store.dart index b3b1b62b98..ff4561e56c 100644 --- a/lib/model/store.dart +++ b/lib/model/store.dart @@ -609,6 +609,9 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor notifyListeners(); } + @override + Iterable popularEmojiCandidates() => _emoji.popularEmojiCandidates(); + @override Iterable allEmojiCandidates() => _emoji.allEmojiCandidates(); diff --git a/lib/widgets/action_sheet.dart b/lib/widgets/action_sheet.dart index 88114d48bb..199ec434fa 100644 --- a/lib/widgets/action_sheet.dart +++ b/lib/widgets/action_sheet.dart @@ -556,6 +556,8 @@ void showMessageActionSheet({required BuildContext context, required Message mes final pageContext = PageRoot.contextOf(context); final store = PerAccountStoreWidget.of(pageContext); + final popularEmojiLoaded = store.popularEmojiCandidates().isNotEmpty; + // The UI that's conditioned on this won't live-update during this appearance // of the action sheet (we avoid calling composeBoxControllerOf in a build // method; see its doc). @@ -569,7 +571,8 @@ void showMessageActionSheet({required BuildContext context, required Message mes final showMarkAsUnreadButton = markAsUnreadSupported && isMessageRead; final optionButtons = [ - ReactionButtons(message: message, pageContext: pageContext), + if (popularEmojiLoaded) + ReactionButtons(message: message, pageContext: pageContext), StarButton(message: message, pageContext: pageContext), if (isComposeBoxOffered) QuoteAndReplyButton(message: message, pageContext: pageContext), @@ -667,11 +670,18 @@ class ReactionButtons extends StatelessWidget { @override Widget build(BuildContext context) { - assert(EmojiStore.popularEmojiCandidates.every( + final store = PerAccountStoreWidget.of(pageContext); + final popularEmojiCandidates = store.popularEmojiCandidates(); + assert(popularEmojiCandidates.every( (emoji) => emoji.emojiType == ReactionType.unicodeEmoji)); + // (if this is empty, the widget isn't built in the first place) + assert(popularEmojiCandidates.isNotEmpty); + // UI not designed to handle more than 6 popular emoji. + // (We might have fewer if ServerEmojiData is lacking expected data, + // but that looks fine in manual testing, even when there's just one.) + assert(popularEmojiCandidates.length <= 6); final zulipLocalizations = ZulipLocalizations.of(context); - final store = PerAccountStoreWidget.of(pageContext); final designVariables = DesignVariables.of(context); bool hasSelfVote(EmojiCandidate emoji) { @@ -687,7 +697,7 @@ class ReactionButtons extends StatelessWidget { color: designVariables.contextMenuItemBg.withFadedAlpha(0.12)), child: Row(children: [ Flexible(child: Row(spacing: 1, children: List.unmodifiable( - EmojiStore.popularEmojiCandidates.mapIndexed((index, emoji) => + popularEmojiCandidates.mapIndexed((index, emoji) => _buildButton( context: context, emoji: emoji, diff --git a/test/api/route/realm_test.dart b/test/api/route/realm_test.dart index c1cc18b98b..5d11a9d51f 100644 --- a/test/api/route/realm_test.dart +++ b/test/api/route/realm_test.dart @@ -22,7 +22,7 @@ void main() { } final fakeResult = ServerEmojiData(codeToNames: { - '1f642': ['smile'], + '1f642': ['slight_smile'], '1f34a': ['orange', 'tangerine', 'mandarin'], }); diff --git a/test/example_data.dart b/test/example_data.dart index fc3acfc5a4..a7b0c6fb81 100644 --- a/test/example_data.dart +++ b/test/example_data.dart @@ -115,6 +115,42 @@ GetServerSettingsResult serverSettings({ ); } +ServerEmojiData serverEmojiDataPopular = ServerEmojiData(codeToNames: { + '1f44d': ['+1', 'thumbs_up', 'like'], + '1f389': ['tada'], + '1f642': ['slight_smile'], + '2764': ['heart', 'love', 'love_you'], + '1f6e0': ['working_on_it', 'hammer_and_wrench', 'tools'], + '1f419': ['octopus'], +}); + +ServerEmojiData serverEmojiDataPopularPlus(ServerEmojiData data) { + final a = serverEmojiDataPopular; + final b = data; + final result = ServerEmojiData( + codeToNames: {...a.codeToNames, ...b.codeToNames}, + ); + assert( + result.codeToNames.length == a.codeToNames.length + b.codeToNames.length, + 'eg.serverEmojiDataPopularPlus called with data that collides with eg.serverEmojiDataPopular', + ); + return result; +} + +/// Like [serverEmojiDataPopular], but with the legacy '1f642': ['smile'] +/// instead of '1f642': ['slight_smile']; see zulip/zulip@9feba0f16f. +/// +/// zulip/zulip@9feba0f16f is a Server 11 commit. +// TODO(server-11) can drop this +ServerEmojiData serverEmojiDataPopularLegacy = ServerEmojiData(codeToNames: { + '1f44d': ['+1', 'thumbs_up', 'like'], + '1f389': ['tada'], + '1f642': ['smile'], + '2764': ['heart', 'love', 'love_you'], + '1f6e0': ['working_on_it', 'hammer_and_wrench', 'tools'], + '1f419': ['octopus'], +}); + RealmEmojiItem realmEmojiItem({ required String emojiCode, required String emojiName, diff --git a/test/model/emoji_test.dart b/test/model/emoji_test.dart index 7916af0849..e9ad051285 100644 --- a/test/model/emoji_test.dart +++ b/test/model/emoji_test.dart @@ -13,9 +13,9 @@ void main() { group('emojiDisplayFor', () { test('Unicode emoji', () { check(eg.store().emojiDisplayFor(emojiType: ReactionType.unicodeEmoji, - emojiCode: '1f642', emojiName: 'smile') + emojiCode: '1f642', emojiName: 'slight_smile') ).isA() - ..emojiName.equals('smile') + ..emojiName.equals('slight_smile') ..emojiUnicode.equals('🙂'); }); @@ -78,7 +78,10 @@ void main() { }); }); - final popularCandidates = EmojiStore.popularEmojiCandidates; + final popularCandidates = ( + eg.store()..setServerEmojiData(eg.serverEmojiDataPopular) + ).popularEmojiCandidates(); + assert(popularCandidates.length == 6); Condition isUnicodeCandidate(String? emojiCode, List? names) { return (it_) { @@ -118,13 +121,17 @@ void main() { PerAccountStore prepare({ Map realmEmoji = const {}, + bool addServerDataForPopular = true, Map>? unicodeEmoji, }) { final store = eg.store( initialSnapshot: eg.initialSnapshot(realmEmoji: realmEmoji)); - if (unicodeEmoji != null) { - store.setServerEmojiData(ServerEmojiData(codeToNames: unicodeEmoji)); - } + + final extraEmojiData = ServerEmojiData(codeToNames: unicodeEmoji ?? {}); + final emojiData = addServerDataForPopular + ? eg.serverEmojiDataPopularPlus(extraEmojiData) + : extraEmojiData; + store.setServerEmojiData(emojiData); return store; } @@ -139,7 +146,8 @@ void main() { 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: { + assert(popularCandidates.length == 6); + final store = prepare(addServerDataForPopular: false, unicodeEmoji: { '1f603': ['smiley'], for (final candidate in popularCandidates.skip(3)) candidate.emojiCode: [candidate.emojiName, ...candidate.aliases], @@ -252,9 +260,10 @@ void main() { isZulipCandidate(), ]); - store.setServerEmojiData(ServerEmojiData(codeToNames: { - '1f516': ['bookmark'], - })); + store.setServerEmojiData(eg.serverEmojiDataPopularPlus( + ServerEmojiData(codeToNames: { + '1f516': ['bookmark'], + }))); check(store.allEmojiCandidates()).deepEquals([ ...arePopularCandidates, isUnicodeCandidate('1f516', ['bookmark']), @@ -318,9 +327,9 @@ void main() { for (final MapEntry(:key, :value) in realmEmoji.entries) key: eg.realmEmojiItem(emojiCode: key, emojiName: value), })); - if (unicodeEmoji != null) { - store.setServerEmojiData(ServerEmojiData(codeToNames: unicodeEmoji)); - } + final extraEmojiData = ServerEmojiData(codeToNames: unicodeEmoji ?? {}); + ServerEmojiData emojiData = eg.serverEmojiDataPopularPlus(extraEmojiData); + store.setServerEmojiData(emojiData); return store; } @@ -343,7 +352,7 @@ void main() { test('results update after query change', () async { final store = prepare( - realmEmoji: {'1': 'happy'}, unicodeEmoji: {'1f642': ['smile']}); + realmEmoji: {'1': 'happy'}, unicodeEmoji: {'1f516': ['bookmark']}); final view = EmojiAutocompleteView.init(store: store, query: EmojiAutocompleteQuery('hap')); bool done = false; @@ -354,11 +363,11 @@ void main() { isRealmResult(emojiName: 'happy')); done = false; - view.query = EmojiAutocompleteQuery('sm'); + view.query = EmojiAutocompleteQuery('bo'); await Future(() {}); check(done).isTrue(); check(view.results).single.which( - isUnicodeResult(names: ['smile'])); + isUnicodeResult(names: ['bookmark'])); }); Future> resultsOf( @@ -389,7 +398,7 @@ void main() { check(await resultsOf('')).deepEquals([ isUnicodeResult(names: ['+1', 'thumbs_up', 'like']), isUnicodeResult(names: ['tada']), - isUnicodeResult(names: ['smile']), + isUnicodeResult(names: ['slight_smile']), isUnicodeResult(names: ['heart', 'love', 'love_you']), isUnicodeResult(names: ['working_on_it', 'hammer_and_wrench', 'tools']), isUnicodeResult(names: ['octopus']), @@ -402,6 +411,7 @@ void main() { isUnicodeResult(names: ['tada']), isUnicodeResult(names: ['working_on_it', 'hammer_and_wrench', 'tools']), // other + isUnicodeResult(names: ['slight_smile']), isUnicodeResult(names: ['heart', 'love', 'love_you']), isUnicodeResult(names: ['octopus']), ]); @@ -412,6 +422,7 @@ void main() { isUnicodeResult(names: ['working_on_it', 'hammer_and_wrench', 'tools']), // other isUnicodeResult(names: ['+1', 'thumbs_up', 'like']), + isUnicodeResult(names: ['slight_smile']), ]); }); diff --git a/test/model/store_test.dart b/test/model/store_test.dart index ef0a7a72be..eba1505747 100644 --- a/test/model/store_test.dart +++ b/test/model/store_test.dart @@ -705,7 +705,7 @@ void main() { final emojiDataUrl = Uri.parse('https://cdn.example/emoji.json'); final data = { - '1f642': ['smile'], + '1f642': ['slight_smile'], '1f34a': ['orange', 'tangerine', 'mandarin'], }; diff --git a/test/widgets/action_sheet_test.dart b/test/widgets/action_sheet_test.dart index 8aeeec4eed..74df8e5457 100644 --- a/test/widgets/action_sheet_test.dart +++ b/test/widgets/action_sheet_test.dart @@ -52,6 +52,7 @@ late FakeApiConnection connection; Future setupToMessageActionSheet(WidgetTester tester, { required Message message, required Narrow narrow, + bool useLegacy = false, }) async { addTearDown(testBinding.reset); assert(narrow.containsMessage(message)); @@ -70,6 +71,9 @@ Future setupToMessageActionSheet(WidgetTester tester, { await store.addSubscription(eg.subscription(stream)); } connection = store.connection as FakeApiConnection; + store.setServerEmojiData(useLegacy + ? eg.serverEmojiDataPopularLegacy + : eg.serverEmojiDataPopular); connection.prepare(json: eg.newestGetMessagesResult( foundOldest: true, messages: [message]).toJson()); @@ -811,72 +815,87 @@ void main() { group('message action sheet', () { group('ReactionButtons', () { - final popularCandidates = EmojiStore.popularEmojiCandidates; + for (final useLegacy in [false, true]) { + final popularCandidates = + (eg.store()..setServerEmojiData( + useLegacy + ? eg.serverEmojiDataPopularLegacy + : eg.serverEmojiDataPopular)) + .popularEmojiCandidates(); + for (final emoji in popularCandidates) { + final emojiDisplay = emoji.emojiDisplay as UnicodeEmojiDisplay; + + Future tapButton(WidgetTester tester) async { + await tester.tap(find.descendant( + of: find.byType(BottomSheet), + matching: find.text(emojiDisplay.emojiUnicode))); + } + + testWidgets('${emoji.emojiName} adding success; useLegacy: $useLegacy', (tester) async { + final message = eg.streamMessage(); + await setupToMessageActionSheet(tester, + message: message, + narrow: TopicNarrow.ofMessage(message), + useLegacy: useLegacy); + + connection.prepare(json: {}); + await tapButton(tester); + await tester.pump(Duration.zero); + + check(connection.lastRequest).isA() + ..method.equals('POST') + ..url.path.equals('/api/v1/messages/${message.id}/reactions') + ..bodyFields.deepEquals({ + 'reaction_type': 'unicode_emoji', + 'emoji_code': emoji.emojiCode, + 'emoji_name': emoji.emojiName, + }); + }); - for (final emoji in popularCandidates) { - final emojiDisplay = emoji.emojiDisplay as UnicodeEmojiDisplay; + testWidgets('${emoji.emojiName} removing success', (tester) async { + final message = eg.streamMessage( + reactions: [Reaction( + emojiName: emoji.emojiName, + emojiCode: emoji.emojiCode, + reactionType: ReactionType.unicodeEmoji, + userId: eg.selfAccount.userId)] + ); + await setupToMessageActionSheet(tester, + message: message, + narrow: TopicNarrow.ofMessage(message), + useLegacy: useLegacy); + + connection.prepare(json: {}); + await tapButton(tester); + await tester.pump(Duration.zero); + + check(connection.lastRequest).isA() + ..method.equals('DELETE') + ..url.path.equals('/api/v1/messages/${message.id}/reactions') + ..bodyFields.deepEquals({ + 'reaction_type': 'unicode_emoji', + 'emoji_code': emoji.emojiCode, + 'emoji_name': emoji.emojiName, + }); + }); - Future tapButton(WidgetTester tester) async { - await tester.tap(find.descendant( - of: find.byType(BottomSheet), - matching: find.text(emojiDisplay.emojiUnicode))); + testWidgets('${emoji.emojiName} request has an error', (tester) async { + final message = eg.streamMessage(); + await setupToMessageActionSheet(tester, + message: message, + narrow: TopicNarrow.ofMessage(message), + useLegacy: useLegacy); + + connection.prepare( + apiException: eg.apiBadRequest(message: 'Invalid message(s)')); + await tapButton(tester); + await tester.pump(Duration.zero); // error arrives; error dialog shows + + await tester.tap(find.byWidget(checkErrorDialog(tester, + expectedTitle: 'Adding reaction failed', + expectedMessage: 'Invalid message(s)'))); + }); } - - testWidgets('${emoji.emojiName} adding success', (tester) async { - final message = eg.streamMessage(); - await setupToMessageActionSheet(tester, message: message, narrow: TopicNarrow.ofMessage(message)); - - connection.prepare(json: {}); - await tapButton(tester); - await tester.pump(Duration.zero); - - check(connection.lastRequest).isA() - ..method.equals('POST') - ..url.path.equals('/api/v1/messages/${message.id}/reactions') - ..bodyFields.deepEquals({ - 'reaction_type': 'unicode_emoji', - 'emoji_code': emoji.emojiCode, - 'emoji_name': emoji.emojiName, - }); - }); - - testWidgets('${emoji.emojiName} removing success', (tester) async { - final message = eg.streamMessage( - reactions: [Reaction( - emojiName: emoji.emojiName, - emojiCode: emoji.emojiCode, - reactionType: ReactionType.unicodeEmoji, - userId: eg.selfAccount.userId)] - ); - await setupToMessageActionSheet(tester, message: message, narrow: TopicNarrow.ofMessage(message)); - - connection.prepare(json: {}); - await tapButton(tester); - await tester.pump(Duration.zero); - - check(connection.lastRequest).isA() - ..method.equals('DELETE') - ..url.path.equals('/api/v1/messages/${message.id}/reactions') - ..bodyFields.deepEquals({ - 'reaction_type': 'unicode_emoji', - 'emoji_code': emoji.emojiCode, - 'emoji_name': emoji.emojiName, - }); - }); - - testWidgets('${emoji.emojiName} request has an error', (tester) async { - final message = eg.streamMessage(); - await setupToMessageActionSheet(tester, message: message, narrow: TopicNarrow.ofMessage(message)); - - connection.prepare( - apiException: eg.apiBadRequest(message: 'Invalid message(s)')); - await tapButton(tester); - await tester.pump(Duration.zero); // error arrives; error dialog shows - - await tester.tap(find.byWidget(checkErrorDialog(tester, - expectedTitle: 'Adding reaction failed', - expectedMessage: 'Invalid message(s)'))); - }); } }); diff --git a/test/widgets/emoji_reaction_test.dart b/test/widgets/emoji_reaction_test.dart index d8faed191f..de3ad7227c 100644 --- a/test/widgets/emoji_reaction_test.dart +++ b/test/widgets/emoji_reaction_test.dart @@ -161,7 +161,7 @@ void main() { // Base JSON for various unicode emoji reactions. Just missing user_id. final u1 = {'emoji_name': '+1', 'emoji_code': '1f44d', 'reaction_type': 'unicode_emoji'}; final u2 = {'emoji_name': 'family_man_man_girl_boy', 'emoji_code': '1f468-200d-1f468-200d-1f467-200d-1f466', 'reaction_type': 'unicode_emoji'}; - final u3 = {'emoji_name': 'smile', 'emoji_code': '1f642', 'reaction_type': 'unicode_emoji'}; + final u3 = {'emoji_name': 'slight_smile', 'emoji_code': '1f642', 'reaction_type': 'unicode_emoji'}; final u4 = {'emoji_name': 'tada', 'emoji_code': '1f389', 'reaction_type': 'unicode_emoji'}; final u5 = {'emoji_name': 'exploding_head', 'emoji_code': '1f92f', 'reaction_type': 'unicode_emoji'}; @@ -239,7 +239,7 @@ void main() { await setupChipsInBox(tester, reactions: [ Reaction.fromJson({ 'user_id': eg.selfUser.userId, - 'emoji_name': 'smile', 'emoji_code': '1f642', 'reaction_type': 'unicode_emoji'}), + 'emoji_name': 'slight_smile', 'emoji_code': '1f642', 'reaction_type': 'unicode_emoji'}), Reaction.fromJson({ 'user_id': eg.otherUser.userId, 'emoji_name': 'tada', 'emoji_code': '1f389', 'reaction_type': 'unicode_emoji'}), @@ -251,7 +251,7 @@ void main() { return material.color; } - check(backgroundColor('smile')).isNotNull() + check(backgroundColor('slight_smile')).isNotNull() .isSameColorAs(EmojiReactionTheme.light.bgSelected); check(backgroundColor('tada')).isNotNull() .isSameColorAs(EmojiReactionTheme.light.bgUnselected); @@ -261,13 +261,13 @@ void main() { await tester.pump(kThemeAnimationDuration * 0.4); final expectedLerped = EmojiReactionTheme.light.lerp(EmojiReactionTheme.dark, 0.4); - check(backgroundColor('smile')).isNotNull() + check(backgroundColor('slight_smile')).isNotNull() .isSameColorAs(expectedLerped.bgSelected); check(backgroundColor('tada')).isNotNull() .isSameColorAs(expectedLerped.bgUnselected); await tester.pump(kThemeAnimationDuration * 0.6); - check(backgroundColor('smile')).isNotNull() + check(backgroundColor('slight_smile')).isNotNull() .isSameColorAs(EmojiReactionTheme.dark.bgSelected); check(backgroundColor('tada')).isNotNull() .isSameColorAs(EmojiReactionTheme.dark.bgUnselected); @@ -299,7 +299,9 @@ void main() { // - Non-animated image emoji is selected when intended group('EmojiPicker', () { - final popularCandidates = EmojiStore.popularEmojiCandidates; + final popularCandidates = + (eg.store()..setServerEmojiData(eg.serverEmojiDataPopular)) + .popularEmojiCandidates(); Future setupEmojiPicker(WidgetTester tester, { required StreamMessage message, @@ -330,6 +332,11 @@ void main() { await tester.pumpWidget(TestZulipApp(accountId: eg.selfAccount.id, child: MessageListPage(initNarrow: narrow))); + store.setServerEmojiData(eg.serverEmojiDataPopularPlus( + ServerEmojiData(codeToNames: { + '1f4a4': ['zzz', 'sleepy'], // (just 'zzz' in real data) + }))); + // global store, per-account store, and message list get loaded await tester.pumpAndSettle(); // request the message action sheet @@ -337,9 +344,6 @@ void main() { // sheet appears onscreen; default duration of bottom-sheet enter animation await tester.pump(const Duration(milliseconds: 250)); - store.setServerEmojiData(ServerEmojiData(codeToNames: { - '1f4a4': ['zzz', 'sleepy'], // (just 'zzz' in real data) - })); await store.handleEvent(RealmEmojiUpdateEvent(id: 1, realmEmoji: { '1': eg.realmEmojiItem(emojiCode: '1', emojiName: 'buzzing'), }));