From 248fbc6905869444f9d3d2d8af4862bd57951065 Mon Sep 17 00:00:00 2001 From: provokateurin Date: Sat, 25 May 2024 09:34:34 +0200 Subject: [PATCH 1/2] test(nextcloud): Test spreed get reactions Signed-off-by: provokateurin --- .../test/fixtures/spreed/reaction/get.regexp | 16 +++++++++++++ packages/nextcloud/test/spreed_test.dart | 24 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 packages/nextcloud/test/fixtures/spreed/reaction/get.regexp diff --git a/packages/nextcloud/test/fixtures/spreed/reaction/get.regexp b/packages/nextcloud/test/fixtures/spreed/reaction/get.regexp new file mode 100644 index 00000000000..b4252b33092 --- /dev/null +++ b/packages/nextcloud/test/fixtures/spreed/reaction/get.regexp @@ -0,0 +1,16 @@ +POST http://localhost/ocs/v2\.php/apps/spreed/api/v4/room\?roomType=3&invite=&roomName=Test&source=&objectType=&objectId= +accept: application/json +authorization: Bearer mock +ocs-apirequest: true +POST http://localhost/ocs/v2\.php/apps/spreed/api/v1/chat/[a-z0-9]{8}\?message=bla&actorDisplayName=&referenceId=&replyTo=0&silent=0 +accept: application/json +authorization: Bearer mock +ocs-apirequest: true +POST http://localhost/ocs/v2\.php/apps/spreed/api/v1/reaction/[a-z0-9]{8}/[0-9]+\?reaction=%F0%9F%98%80 +accept: application/json +authorization: Bearer mock +ocs-apirequest: true +GET http://localhost/ocs/v2\.php/apps/spreed/api/v1/reaction/[a-z0-9]{8}/[0-9]+ +accept: application/json +authorization: Bearer mock +ocs-apirequest: true \ No newline at end of file diff --git a/packages/nextcloud/test/spreed_test.dart b/packages/nextcloud/test/spreed_test.dart index bc172fd33b2..6d2aa6cc437 100644 --- a/packages/nextcloud/test/spreed_test.dart +++ b/packages/nextcloud/test/spreed_test.dart @@ -503,6 +503,30 @@ void main() { expect(message.reactions, isEmpty); expect(message.reactionsSelf, isNull); }); + + test('Get', () async { + final room = await createTestRoom(); + + final sendMessageResponse = await client1.spreed.chat.sendMessage( + token: room.token, + message: 'bla', + ); + + final addResponse = await client1.spreed.reaction.react( + reaction: '😀', + token: room.token, + messageId: sendMessageResponse.body.ocs.data!.id, + ); + expect(addResponse.body.ocs.data, hasLength(1)); + expect(addResponse.body.ocs.data['😀'], isNotNull); + + final getResponse = await client1.spreed.reaction.getReactions( + token: room.token, + messageId: sendMessageResponse.body.ocs.data!.id, + ); + expect(getResponse.body.ocs.data, hasLength(1)); + expect(getResponse.body.ocs.data['😀'], isNotNull); + }); }); }, retry: retryCount, From 0676a6ed3d6de4be8cae8cf535f922248050da63 Mon Sep 17 00:00:00 2001 From: provokateurin Date: Sat, 25 May 2024 09:20:58 +0200 Subject: [PATCH 2/2] feat(neon_talk): Load full reactions when hovering over reactions Signed-off-by: provokateurin --- .../neon/neon_talk/lib/src/blocs/room.dart | 40 ++- .../neon_talk/lib/src/widgets/reactions.dart | 143 ++++----- .../neon/neon_talk/test/goldens/reactions.png | Bin 6751 -> 6732 bytes .../neon/neon_talk/test/message_test.dart | 271 +++++++++++------- .../neon/neon_talk/test/reactions_test.dart | 51 +++- .../neon/neon_talk/test/room_bloc_test.dart | 108 +++++++ .../neon/neon_talk/test/room_page_test.dart | 2 + packages/neon/neon_talk/test/testing.dart | 2 + 8 files changed, 446 insertions(+), 171 deletions(-) diff --git a/packages/neon/neon_talk/lib/src/blocs/room.dart b/packages/neon/neon_talk/lib/src/blocs/room.dart index a04bd86e02e..4cbecccc1dc 100644 --- a/packages/neon/neon_talk/lib/src/blocs/room.dart +++ b/packages/neon/neon_talk/lib/src/blocs/room.dart @@ -28,9 +28,12 @@ abstract class TalkRoomBloc implements InteractiveBloc { /// Adds an emoji [reaction] to the [message]. void addReaction(spreed.$ChatMessageInterface message, String reaction); - /// Removes an emoji [reaction] to the [message]. + /// Removes an emoji [reaction] from the [message]. void removeReaction(spreed.$ChatMessageInterface message, String reaction); + /// Loads the emoji reactions for the [message]. + void loadReactions(spreed.$ChatMessageInterface message); + /// The current room data. BehaviorSubject> get room; @@ -41,6 +44,9 @@ abstract class TalkRoomBloc implements InteractiveBloc { /// The last message ID that was read by everyone with read-privacy set to public. /// {@endtemplate} BehaviorSubject get lastCommonRead; + + /// Map of emoji reactions for the [messages]. + BehaviorSubject>>> get reactions; } class _TalkRoomBloc extends InteractiveBloc implements TalkRoomBloc { @@ -150,6 +156,9 @@ class _TalkRoomBloc extends InteractiveBloc implements TalkRoomBloc { @override final lastCommonRead = BehaviorSubject(); + @override + final reactions = BehaviorSubject.seeded(BuiltMap()); + @override void dispose() { pollLoop = false; @@ -158,6 +167,7 @@ class _TalkRoomBloc extends InteractiveBloc implements TalkRoomBloc { unawaited(room.close()); unawaited(messages.close()); unawaited(lastCommonRead.close()); + unawaited(reactions.close()); super.dispose(); } @@ -252,6 +262,28 @@ class _TalkRoomBloc extends InteractiveBloc implements TalkRoomBloc { ); } + @override + Future loadReactions(spreed.$ChatMessageInterface message) async { + if (reactions.value.containsKey(message.id)) { + return; + } + + await wrapAction( + () async { + final response = await account.client.spreed.reaction.getReactions( + token: token, + messageId: message.id, + ); + + updateReactions( + message.id, + response.body.ocs.data, + ); + }, + refresh: () async {}, + ); + } + void updateLastCommonRead(String? header) { if (header != null) { lastCommonRead.add(int.parse(header)); @@ -284,6 +316,12 @@ class _TalkRoomBloc extends InteractiveBloc implements TalkRoomBloc { } void updateReactions(int messageId, BuiltMap> reactions) { + this.reactions.add( + this.reactions.value.rebuild((b) { + b[messageId] = reactions; + }), + ); + updateMessage( messageId, (b) => b diff --git a/packages/neon/neon_talk/lib/src/widgets/reactions.dart b/packages/neon/neon_talk/lib/src/widgets/reactions.dart index f31a0d41da2..87d1939f989 100644 --- a/packages/neon/neon_talk/lib/src/widgets/reactions.dart +++ b/packages/neon/neon_talk/lib/src/widgets/reactions.dart @@ -18,82 +18,95 @@ class TalkReactions extends StatelessWidget { @override Widget build(BuildContext context) { + final bloc = NeonProvider.of(context); + const shape = RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(50)), ); - final children = []; - for (final reaction in chatMessage.reactions.entries) { - final isSelf = chatMessage.reactionsSelf?.contains(reaction.key) ?? false; - - children.add( - ActionChip( - backgroundColor: isSelf ? Theme.of(context).colorScheme.primary : null, - shape: shape, - avatar: Text(reaction.key), - label: Text( - reaction.value.toString(), - style: const TextStyle( - fontSize: 12, - fontFamily: 'monospace', - ), - ), - padding: EdgeInsets.zero, - labelPadding: const EdgeInsets.only( - top: -2.5, - bottom: -2.5, - right: 10, - ), - onPressed: () { - final bloc = NeonProvider.of(context); + return MouseRegion( + onEnter: (_) { + bloc.loadReactions(chatMessage); + }, + child: StreamBuilder( + stream: bloc.reactions, + builder: (context, reactionsSnapshot) { + final reactions = reactionsSnapshot.data?[chatMessage.id]; - if (isSelf) { - bloc.removeReaction(chatMessage, reaction.key); - } else { - bloc.addReaction(chatMessage, reaction.key); - } - }, - ), - ); - } + final children = []; + for (final reaction in chatMessage.reactions.entries) { + final isSelf = chatMessage.reactionsSelf?.contains(reaction.key) ?? false; - children.add( - ActionChip( - shape: shape, - avatar: Icon( - Icons.add_reaction_outlined, - color: Theme.of(context).colorScheme.onSurfaceVariant, - size: 16, - ), - label: const SizedBox(), - padding: EdgeInsets.zero, - labelPadding: const EdgeInsets.symmetric(vertical: -2.5), - onPressed: () async { - final reaction = await showDialog( - context: context, - builder: (context) => const NeonEmojiPickerDialog(), - ); - if (reaction == null) { - return; + children.add( + ActionChip( + backgroundColor: isSelf ? Theme.of(context).colorScheme.primary : null, + shape: shape, + avatar: Text(reaction.key), + label: Text( + reaction.value.toString(), + style: const TextStyle( + fontSize: 12, + fontFamily: 'monospace', + ), + ), + tooltip: reactions?[reaction.key]?.map((r) => r.actorDisplayName).join(', '), + padding: EdgeInsets.zero, + labelPadding: const EdgeInsets.only( + top: -2.5, + bottom: -2.5, + right: 10, + ), + onPressed: () { + if (isSelf) { + bloc.removeReaction(chatMessage, reaction.key); + } else { + bloc.addReaction(chatMessage, reaction.key); + } + }, + ), + ); } - if (!context.mounted) { - return; - } + children.add( + ActionChip( + shape: shape, + avatar: Icon( + Icons.add_reaction_outlined, + color: Theme.of(context).colorScheme.onSurfaceVariant, + size: 16, + ), + label: const SizedBox(), + padding: EdgeInsets.zero, + labelPadding: const EdgeInsets.symmetric(vertical: -2.5), + onPressed: () async { + final reaction = await showDialog( + context: context, + builder: (context) => const NeonEmojiPickerDialog(), + ); + if (reaction == null) { + return; + } - NeonProvider.of(context).addReaction(chatMessage, reaction); - }, - ), - ); + if (!context.mounted) { + return; + } - return Row( - children: children - .intersperse( - const SizedBox( - width: 5, + bloc.addReaction(chatMessage, reaction); + }, ), - ) - .toList(), + ); + + return Row( + children: children + .intersperse( + const SizedBox( + width: 5, + ), + ) + .toList(), + ); + }, + ), ); } } diff --git a/packages/neon/neon_talk/test/goldens/reactions.png b/packages/neon/neon_talk/test/goldens/reactions.png index f197244c778ba087cf6872a579ad8595294f8e87..7b2432bf9acd0bcd1cbba5e1c5116ad2c5a60e33 100644 GIT binary patch literal 6732 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 diff --git a/packages/neon/neon_talk/test/message_test.dart b/packages/neon/neon_talk/test/message_test.dart index c7cfc0388f7..10312db5885 100644 --- a/packages/neon/neon_talk/test/message_test.dart +++ b/packages/neon/neon_talk/test/message_test.dart @@ -8,6 +8,7 @@ import 'package:neon_framework/theme.dart'; import 'package:neon_framework/utils.dart'; import 'package:neon_talk/l10n/localizations.dart'; import 'package:neon_talk/l10n/localizations_en.dart'; +import 'package:neon_talk/src/blocs/room.dart'; import 'package:neon_talk/src/widgets/actor_avatar.dart'; import 'package:neon_talk/src/widgets/message.dart'; import 'package:neon_talk/src/widgets/reactions.dart'; @@ -22,12 +23,6 @@ import 'package:rxdart/rxdart.dart'; import 'testing.dart'; -Widget wrapWidget(Widget child) => TestApp( - localizationsDelegates: TalkLocalizations.localizationsDelegates, - supportedLocales: TalkLocalizations.supportedLocales, - child: child, - ); - void main() { setUpAll(() { FakeNeonStorage.setup(); @@ -63,8 +58,10 @@ void main() { when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); await tester.pumpWidget( - wrapWidget( - TalkMessagePreview( + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + child: TalkMessagePreview( actorId: 'test', roomType: spreed.RoomType.group, chatMessage: chatMessage, @@ -83,8 +80,10 @@ void main() { when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); await tester.pumpWidget( - wrapWidget( - TalkMessagePreview( + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + child: TalkMessagePreview( actorId: 'abc', roomType: spreed.RoomType.group, chatMessage: chatMessage, @@ -102,8 +101,10 @@ void main() { when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); await tester.pumpWidget( - wrapWidget( - TalkMessagePreview( + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + child: TalkMessagePreview( actorId: 'test', roomType: spreed.RoomType.oneToOne, chatMessage: chatMessage, @@ -121,8 +122,10 @@ void main() { when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); await tester.pumpWidget( - wrapWidget( - TalkMessagePreview( + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + child: TalkMessagePreview( actorId: 'abc', roomType: spreed.RoomType.oneToOne, chatMessage: chatMessage, @@ -140,8 +143,10 @@ void main() { when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); await tester.pumpWidget( - wrapWidget( - TalkMessagePreview( + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + child: TalkMessagePreview( actorId: 'abc', roomType: spreed.RoomType.group, chatMessage: chatMessage, @@ -159,8 +164,10 @@ void main() { when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); await tester.pumpWidget( - wrapWidget( - TalkMessagePreview( + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + child: TalkMessagePreview( actorId: 'abc', roomType: spreed.RoomType.oneToOne, chatMessage: chatMessage, @@ -180,8 +187,10 @@ void main() { when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); await tester.pumpWidget( - wrapWidget( - TalkMessage( + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + child: TalkMessage( chatMessage: chatMessage, lastCommonRead: null, ), @@ -208,14 +217,20 @@ void main() { when(() => chatMessage.reactions).thenReturn(BuiltMap()); when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); + final roomBloc = MockRoomBloc(); + when(() => roomBloc.reactions).thenAnswer((_) => BehaviorSubject.seeded(BuiltMap())); + await tester.pumpWidget( - wrapWidget( - NeonProvider.value( - value: accountsBloc, - child: TalkMessage( - chatMessage: chatMessage, - lastCommonRead: null, - ), + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + providers: [ + NeonProvider.value(value: accountsBloc), + NeonProvider.value(value: roomBloc), + ], + child: TalkMessage( + chatMessage: chatMessage, + lastCommonRead: null, ), ), ); @@ -229,8 +244,10 @@ void main() { when(() => chatMessage.systemMessage).thenReturn('reaction'); await tester.pumpWidget( - wrapWidget( - TalkSystemMessage( + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + child: TalkSystemMessage( chatMessage: chatMessage, previousChatMessage: null, ), @@ -248,8 +265,10 @@ void main() { when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); await tester.pumpWidget( - wrapWidget( - TalkSystemMessage( + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + child: TalkSystemMessage( chatMessage: chatMessage, previousChatMessage: null, ), @@ -270,8 +289,10 @@ void main() { when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); await tester.pumpWidget( - wrapWidget( - TalkSystemMessage( + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + child: TalkSystemMessage( chatMessage: chatMessage, previousChatMessage: previousChatMessage, ), @@ -305,13 +326,15 @@ void main() { when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); await tester.pumpWidget( - wrapWidget( - NeonProvider.value( - value: accountsBloc, - child: TalkParentMessage( - parentChatMessage: chatMessage, - lastCommonRead: null, - ), + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + providers: [ + NeonProvider.value(value: accountsBloc), + ], + child: TalkParentMessage( + parentChatMessage: chatMessage, + lastCommonRead: null, ), ), ); @@ -345,15 +368,21 @@ void main() { when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); when(() => chatMessage.id).thenReturn(0); + final roomBloc = MockRoomBloc(); + when(() => roomBloc.reactions).thenAnswer((_) => BehaviorSubject.seeded(BuiltMap())); + await tester.pumpWidget( - wrapWidget( - NeonProvider.value( - value: accountsBloc, - child: TalkCommentMessage( - chatMessage: chatMessage, - lastCommonRead: 0, - previousChatMessage: previousChatMessage, - ), + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + providers: [ + NeonProvider.value(value: accountsBloc), + NeonProvider.value(value: roomBloc), + ], + child: TalkCommentMessage( + chatMessage: chatMessage, + lastCommonRead: 0, + previousChatMessage: previousChatMessage, ), ), ); @@ -394,15 +423,21 @@ void main() { when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); when(() => chatMessage.id).thenReturn(0); + final roomBloc = MockRoomBloc(); + when(() => roomBloc.reactions).thenAnswer((_) => BehaviorSubject.seeded(BuiltMap())); + await tester.pumpWidget( - wrapWidget( - NeonProvider.value( - value: accountsBloc, - child: TalkCommentMessage( - chatMessage: chatMessage, - lastCommonRead: 0, - previousChatMessage: previousChatMessage, - ), + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + providers: [ + NeonProvider.value(value: accountsBloc), + NeonProvider.value(value: roomBloc), + ], + child: TalkCommentMessage( + chatMessage: chatMessage, + lastCommonRead: 0, + previousChatMessage: previousChatMessage, ), ), ); @@ -442,14 +477,16 @@ void main() { when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); await tester.pumpWidget( - wrapWidget( - NeonProvider.value( - value: accountsBloc, - child: TalkCommentMessage( - chatMessage: chatMessage, - lastCommonRead: null, - previousChatMessage: previousChatMessage, - ), + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + providers: [ + NeonProvider.value(value: accountsBloc), + ], + child: TalkCommentMessage( + chatMessage: chatMessage, + lastCommonRead: null, + previousChatMessage: previousChatMessage, ), ), ); @@ -479,14 +516,16 @@ void main() { when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); await tester.pumpWidget( - wrapWidget( - NeonProvider.value( - value: accountsBloc, - child: TalkCommentMessage( - chatMessage: chatMessage, - lastCommonRead: null, - isParent: true, - ), + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + providers: [ + NeonProvider.value(value: accountsBloc), + ], + child: TalkCommentMessage( + chatMessage: chatMessage, + lastCommonRead: null, + isParent: true, ), ), ); @@ -533,15 +572,21 @@ void main() { when(() => chatMessage.parent).thenReturn(parentChatMessage); when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); + final roomBloc = MockRoomBloc(); + when(() => roomBloc.reactions).thenAnswer((_) => BehaviorSubject.seeded(BuiltMap())); + await tester.pumpWidget( - wrapWidget( - NeonProvider.value( - value: accountsBloc, - child: TalkCommentMessage( - chatMessage: chatMessage, - lastCommonRead: null, - previousChatMessage: previousChatMessage, - ), + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + providers: [ + NeonProvider.value(value: accountsBloc), + NeonProvider.value(value: roomBloc), + ], + child: TalkCommentMessage( + chatMessage: chatMessage, + lastCommonRead: null, + previousChatMessage: previousChatMessage, ), ), ); @@ -577,15 +622,21 @@ void main() { when(() => chatMessage.reactions).thenReturn(BuiltMap({'😀': 1, '😊': 23})); when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); + final roomBloc = MockRoomBloc(); + when(() => roomBloc.reactions).thenAnswer((_) => BehaviorSubject.seeded(BuiltMap())); + await tester.pumpWidget( - wrapWidget( - NeonProvider.value( - value: accountsBloc, - child: TalkCommentMessage( - chatMessage: chatMessage, - lastCommonRead: null, - previousChatMessage: previousChatMessage, - ), + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + providers: [ + NeonProvider.value(value: accountsBloc), + NeonProvider.value(value: roomBloc), + ], + child: TalkCommentMessage( + chatMessage: chatMessage, + lastCommonRead: null, + previousChatMessage: previousChatMessage, ), ), ); @@ -623,15 +674,21 @@ void main() { when(() => chatMessage.reactions).thenReturn(BuiltMap({'😀': 1, '😊': 23})); when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); + final roomBloc = MockRoomBloc(); + when(() => roomBloc.reactions).thenAnswer((_) => BehaviorSubject.seeded(BuiltMap())); + await tester.pumpWidget( - wrapWidget( - NeonProvider.value( - value: accountsBloc, - child: TalkCommentMessage( - chatMessage: chatMessage, - lastCommonRead: null, - previousChatMessage: previousChatMessage, - ), + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + providers: [ + NeonProvider.value(value: accountsBloc), + NeonProvider.value(value: roomBloc), + ], + child: TalkCommentMessage( + chatMessage: chatMessage, + lastCommonRead: null, + previousChatMessage: previousChatMessage, ), ), ); @@ -669,15 +726,21 @@ void main() { when(() => chatMessage.reactions).thenReturn(BuiltMap({'😀': 1, '😊': 23})); when(() => chatMessage.messageParameters).thenReturn(BuiltMap()); + final roomBloc = MockRoomBloc(); + when(() => roomBloc.reactions).thenAnswer((_) => BehaviorSubject.seeded(BuiltMap())); + await tester.pumpWidget( - wrapWidget( - NeonProvider.value( - value: accountsBloc, - child: TalkCommentMessage( - chatMessage: chatMessage, - lastCommonRead: null, - previousChatMessage: previousChatMessage, - ), + TestApp( + localizationsDelegates: TalkLocalizations.localizationsDelegates, + supportedLocales: TalkLocalizations.supportedLocales, + providers: [ + NeonProvider.value(value: accountsBloc), + NeonProvider.value(value: roomBloc), + ], + child: TalkCommentMessage( + chatMessage: chatMessage, + lastCommonRead: null, + previousChatMessage: previousChatMessage, ), ), ); diff --git a/packages/neon/neon_talk/test/reactions_test.dart b/packages/neon/neon_talk/test/reactions_test.dart index 64e88672db9..d9b7b1d4252 100644 --- a/packages/neon/neon_talk/test/reactions_test.dart +++ b/packages/neon/neon_talk/test/reactions_test.dart @@ -1,4 +1,5 @@ import 'package:built_collection/built_collection.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; @@ -7,30 +8,56 @@ import 'package:neon_framework/utils.dart'; import 'package:neon_talk/src/blocs/room.dart'; import 'package:neon_talk/src/widgets/reactions.dart'; import 'package:nextcloud/spreed.dart' as spreed; +import 'package:rxdart/rxdart.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'testing.dart'; +spreed.Reaction getReaction() { + final reaction = MockReaction(); + when(() => reaction.actorDisplayName).thenReturn('actorDisplayName'); + + return reaction; +} + void main() { late spreed.ChatMessage chatMessage; late TalkRoomBloc bloc; setUp(() { chatMessage = MockChatMessage(); - when(() => chatMessage.reactions).thenReturn(BuiltMap({'😀': 1, '😊': 23})); + when(() => chatMessage.id).thenReturn(0); + when(() => chatMessage.reactions).thenReturn(BuiltMap({'😀': 1, '😊': 2})); when(() => chatMessage.reactionsSelf).thenReturn(BuiltList(['😊'])); bloc = MockRoomBloc(); + when(() => bloc.reactions).thenAnswer( + (_) => BehaviorSubject.seeded( + BuiltMap({ + 0: BuiltMap>({ + '😀': BuiltList([getReaction()]), + '😊': BuiltList([getReaction(), getReaction()]), + }), + }), + ), + ); }); testWidgets('Reactions', (tester) async { await tester.pumpWidget( TestApp( + providers: [ + NeonProvider.value(value: bloc), + ], child: TalkReactions( chatMessage: chatMessage, ), ), ); + await tester.pumpAndSettle(); + + expect(find.byTooltip('actorDisplayName'), findsOne); + expect(find.byTooltip('actorDisplayName, actorDisplayName'), findsOne); await expectLater(find.byType(TalkReactions), matchesGoldenFile('goldens/reactions.png')); }); @@ -93,4 +120,26 @@ void main() { verify(() => bloc.addReaction(chatMessage, '😂')).called(1); }); }); + + testWidgets('Load reactions on hover', (tester) async { + await tester.pumpWidget( + TestApp( + providers: [ + NeonProvider.value(value: bloc), + ], + child: TalkReactions( + chatMessage: chatMessage, + ), + ), + ); + + final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(location: Offset.zero); + addTearDown(gesture.removePointer); + await tester.pump(); + await gesture.moveTo(tester.getCenter(find.byType(TalkReactions))); + await tester.pumpAndSettle(); + + verify(() => bloc.loadReactions(chatMessage)).called(1); + }); } diff --git a/packages/neon/neon_talk/test/room_bloc_test.dart b/packages/neon/neon_talk/test/room_bloc_test.dart index 5c4dd1abe50..ff67d199161 100644 --- a/packages/neon/neon_talk/test/room_bloc_test.dart +++ b/packages/neon/neon_talk/test/room_bloc_test.dart @@ -147,6 +147,33 @@ Account mockTalkAccount() { }, ); }, + 'get': (match, queryParameters) => Response( + json.encode({ + 'ocs': { + 'meta': {'status': '', 'statuscode': 0}, + 'data': { + '😀': [ + { + 'actorDisplayName': '', + 'actorId': 'test', + 'actorType': 'users', + 'timestamp': 0, + }, + { + 'actorDisplayName': '', + 'actorId': 'other', + 'actorType': 'users', + 'timestamp': 0, + }, + ], + }, + }, + }), + 200, + headers: { + 'content-type': 'application/json; charset=utf-8', + }, + ), }, }); } @@ -291,6 +318,17 @@ void main() { ), ]), ); + expect( + roomBloc.reactions.map((a) => a.map((k, v) => MapEntry(k, v.map((k, v) => MapEntry(k, v.length))))), + emitsInOrder(>>[ + BuiltMap(), + BuiltMap({ + 0: BuiltMap({ + '😀': 2, + }), + }), + ]), + ); final message = MockChatMessage(); when(() => message.id).thenReturn(0); @@ -339,6 +377,17 @@ void main() { ), ]), ); + expect( + roomBloc.reactions.map((a) => a.map((k, v) => MapEntry(k, v.map((k, v) => MapEntry(k, v.length))))), + emitsInOrder(>>[ + BuiltMap(), + BuiltMap({ + 2: BuiltMap({ + '😀': 2, + }), + }), + ]), + ); final message = MockChatMessage(); when(() => message.id).thenReturn(2); @@ -346,6 +395,65 @@ void main() { roomBloc.removeReaction(message, '😀'); }); + test('loadReactions', () async { + expect( + roomBloc.messages.transformResult((e) => BuiltList>(e.map((m) => m.reactions))), + emitsInOrder([ + Result>>.loading(), + Result.success( + BuiltList>([ + BuiltMap(), + BuiltMap(), + BuiltMap(), + ]), + ), + Result.success( + BuiltList>([ + BuiltMap(), + BuiltMap({'😀': 2}), + BuiltMap(), + ]), + ), + ]), + ); + expect( + roomBloc.messages.transformResult((e) => BuiltList?>(e.map((m) => m.reactionsSelf))), + emitsInOrder([ + Result?>>.loading(), + Result.success( + BuiltList?>([ + null, + null, + null, + ]), + ), + Result.success( + BuiltList?>([ + null, + BuiltList(['😀']), + null, + ]), + ), + ]), + ); + expect( + roomBloc.reactions.map((a) => a.map((k, v) => MapEntry(k, v.map((k, v) => MapEntry(k, v.length))))), + emitsInOrder(>>[ + BuiltMap(), + BuiltMap({ + 1: BuiltMap({ + '😀': 2, + }), + }), + ]), + ); + + final message = MockChatMessage(); + when(() => message.id).thenReturn(1); + + roomBloc.loadReactions(message); + }); + test('polling', () async { expect( roomBloc.messages.transformResult((e) => BuiltList(e.map((m) => m.id))), diff --git a/packages/neon/neon_talk/test/room_page_test.dart b/packages/neon/neon_talk/test/room_page_test.dart index 4ddd857c6d0..3a95c123205 100644 --- a/packages/neon/neon_talk/test/room_page_test.dart +++ b/packages/neon/neon_talk/test/room_page_test.dart @@ -138,6 +138,8 @@ void main() { ), ); + when(() => bloc.reactions).thenAnswer((_) => BehaviorSubject.seeded(BuiltMap())); + final account = MockAccount(); when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); diff --git a/packages/neon/neon_talk/test/testing.dart b/packages/neon/neon_talk/test/testing.dart index 72f96f3dbbf..30d92d4f9f0 100644 --- a/packages/neon/neon_talk/test/testing.dart +++ b/packages/neon/neon_talk/test/testing.dart @@ -9,6 +9,8 @@ class MockChatMessage extends Mock implements spreed.ChatMessage {} class MockChatMessageWithParent extends Mock implements spreed.ChatMessageWithParent {} +class MockReaction extends Mock implements spreed.Reaction {} + class MockRoomBloc extends Mock implements TalkRoomBloc {} class MockTalkBloc extends Mock implements TalkBloc {}