Merge pull request #890 from squaddy/bugfix/thread_reactions
fix(llc): thread message reactions not updated
This commit is contained in:
@@ -1,3 +1,9 @@
|
|||||||
|
## Upcoming
|
||||||
|
|
||||||
|
🐞 Fixed
|
||||||
|
|
||||||
|
- [[#890]](https://github.com/GetStream/stream-chat-flutter/pull/890). Fixed Reactions not updating on thread messages. Thanks [bstolinski](https://github.com/bstolinski).
|
||||||
|
-
|
||||||
## 3.4.0
|
## 3.4.0
|
||||||
|
|
||||||
🐞 Fixed
|
🐞 Fixed
|
||||||
|
|||||||
@@ -1695,7 +1695,9 @@ class ChannelClientState {
|
|||||||
void _listenReactionDeleted() {
|
void _listenReactionDeleted() {
|
||||||
_subscriptions.add(_channel.on(EventType.reactionDeleted).listen((event) {
|
_subscriptions.add(_channel.on(EventType.reactionDeleted).listen((event) {
|
||||||
final oldMessage =
|
final oldMessage =
|
||||||
messages.firstWhereOrNull((it) => it.id == event.message?.id);
|
messages.firstWhereOrNull((it) => it.id == event.message?.id) ??
|
||||||
|
threads[event.message?.parentId]
|
||||||
|
?.firstWhereOrNull((e) => e.id == event.message?.id);
|
||||||
final reaction = event.reaction;
|
final reaction = event.reaction;
|
||||||
final ownReactions = oldMessage?.ownReactions
|
final ownReactions = oldMessage?.ownReactions
|
||||||
?.whereNot((it) =>
|
?.whereNot((it) =>
|
||||||
@@ -1715,7 +1717,9 @@ class ChannelClientState {
|
|||||||
void _listenReactions() {
|
void _listenReactions() {
|
||||||
_subscriptions.add(_channel.on(EventType.reactionNew).listen((event) {
|
_subscriptions.add(_channel.on(EventType.reactionNew).listen((event) {
|
||||||
final oldMessage =
|
final oldMessage =
|
||||||
messages.firstWhereOrNull((it) => it.id == event.message?.id);
|
messages.firstWhereOrNull((it) => it.id == event.message?.id) ??
|
||||||
|
threads[event.message?.parentId]
|
||||||
|
?.firstWhereOrNull((e) => e.id == event.message?.id);
|
||||||
final message = event.message!.copyWith(
|
final message = event.message!.copyWith(
|
||||||
ownReactions: oldMessage?.ownReactions,
|
ownReactions: oldMessage?.ownReactions,
|
||||||
);
|
);
|
||||||
@@ -1731,8 +1735,9 @@ class ChannelClientState {
|
|||||||
)
|
)
|
||||||
.listen((event) {
|
.listen((event) {
|
||||||
final oldMessage =
|
final oldMessage =
|
||||||
messages.firstWhereOrNull((it) => it.id == event.message?.id);
|
messages.firstWhereOrNull((it) => it.id == event.message?.id) ??
|
||||||
|
threads[event.message?.parentId]
|
||||||
|
?.firstWhereOrNull((e) => e.id == event.message?.id);
|
||||||
final message = event.message!.copyWith(
|
final message = event.message!.copyWith(
|
||||||
ownReactions: oldMessage?.ownReactions,
|
ownReactions: oldMessage?.ownReactions,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1239,6 +1239,189 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('`.sendReaction in thread`', () {
|
||||||
|
test('should work fine', () async {
|
||||||
|
const type = 'test-reaction-type';
|
||||||
|
final message = Message(
|
||||||
|
id: 'test-message-id',
|
||||||
|
parentId: 'test-parent-id', // is thread message
|
||||||
|
);
|
||||||
|
|
||||||
|
final reaction = Reaction(type: type, messageId: message.id);
|
||||||
|
|
||||||
|
when(() => client.sendReaction(message.id, type)).thenAnswer(
|
||||||
|
(_) async => SendReactionResponse()
|
||||||
|
..message = message
|
||||||
|
..reaction = reaction,
|
||||||
|
);
|
||||||
|
|
||||||
|
expectLater(
|
||||||
|
channel.state?.threadsStream
|
||||||
|
// skipping first seed message list -> [] messages
|
||||||
|
.skip(1)
|
||||||
|
.map((event) => event['test-parent-id']),
|
||||||
|
emitsInOrder([
|
||||||
|
[
|
||||||
|
isSameMessageAs(
|
||||||
|
message.copyWith(
|
||||||
|
status: MessageSendingStatus.sent,
|
||||||
|
reactionCounts: {type: 1},
|
||||||
|
reactionScores: {type: 1},
|
||||||
|
latestReactions: [reaction],
|
||||||
|
ownReactions: [reaction],
|
||||||
|
),
|
||||||
|
matchReactions: true,
|
||||||
|
matchSendingStatus: true,
|
||||||
|
matchParentId: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
final res = await channel.sendReaction(message, type);
|
||||||
|
|
||||||
|
expect(res, isNotNull);
|
||||||
|
expect(res.reaction.type, type);
|
||||||
|
expect(res.reaction.messageId, message.id);
|
||||||
|
|
||||||
|
verify(() => client.sendReaction(message.id, type)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'''should restore previous thread message if `client.sendReaction` throws''',
|
||||||
|
() async {
|
||||||
|
const type = 'test-reaction-type';
|
||||||
|
final message = Message(
|
||||||
|
id: 'test-message-id',
|
||||||
|
parentId: 'test-parent-id', // is thread message
|
||||||
|
);
|
||||||
|
|
||||||
|
final reaction = Reaction(type: type, messageId: message.id);
|
||||||
|
|
||||||
|
when(() => client.sendReaction(message.id, type))
|
||||||
|
.thenThrow(StreamChatNetworkError(ChatErrorCode.inputError));
|
||||||
|
|
||||||
|
expectLater(
|
||||||
|
// skipping first seed message list -> [] messages
|
||||||
|
channel.state?.threadsStream
|
||||||
|
.skip(1)
|
||||||
|
.map((event) => event['test-parent-id']),
|
||||||
|
emitsInOrder([
|
||||||
|
[
|
||||||
|
isSameMessageAs(
|
||||||
|
message.copyWith(
|
||||||
|
status: MessageSendingStatus.sent,
|
||||||
|
reactionCounts: {type: 1},
|
||||||
|
reactionScores: {type: 1},
|
||||||
|
latestReactions: [reaction],
|
||||||
|
ownReactions: [reaction],
|
||||||
|
),
|
||||||
|
matchReactions: true,
|
||||||
|
matchSendingStatus: true,
|
||||||
|
matchParentId: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
isSameMessageAs(
|
||||||
|
message,
|
||||||
|
matchReactions: true,
|
||||||
|
matchSendingStatus: true,
|
||||||
|
matchParentId: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await channel.sendReaction(message, type);
|
||||||
|
} catch (e) {
|
||||||
|
expect(e, isA<StreamChatNetworkError>());
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(() => client.sendReaction(message.id, type)).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'''should override previous thread reaction if present and `enforceUnique` is true''',
|
||||||
|
() async {
|
||||||
|
const userId = 'test-user-id';
|
||||||
|
const messageId = 'test-message-id';
|
||||||
|
const parentId = 'test-parent-id';
|
||||||
|
const prevType = 'test-reaction-type';
|
||||||
|
final prevReaction = Reaction(
|
||||||
|
type: prevType,
|
||||||
|
messageId: messageId,
|
||||||
|
userId: userId,
|
||||||
|
);
|
||||||
|
final message = Message(
|
||||||
|
id: messageId,
|
||||||
|
parentId: parentId,
|
||||||
|
ownReactions: [prevReaction],
|
||||||
|
latestReactions: [prevReaction],
|
||||||
|
reactionScores: const {prevType: 1},
|
||||||
|
reactionCounts: const {prevType: 1},
|
||||||
|
);
|
||||||
|
|
||||||
|
const type = 'test-reaction-type-2';
|
||||||
|
final newReaction = Reaction(
|
||||||
|
type: type,
|
||||||
|
messageId: messageId,
|
||||||
|
userId: userId,
|
||||||
|
);
|
||||||
|
final newMessage = message.copyWith(
|
||||||
|
ownReactions: [newReaction],
|
||||||
|
latestReactions: [newReaction],
|
||||||
|
);
|
||||||
|
|
||||||
|
const enforceUnique = true;
|
||||||
|
|
||||||
|
when(() => client.sendReaction(
|
||||||
|
messageId,
|
||||||
|
type,
|
||||||
|
enforceUnique: enforceUnique,
|
||||||
|
)).thenAnswer(
|
||||||
|
(_) async => SendReactionResponse()
|
||||||
|
..message = newMessage
|
||||||
|
..reaction = newReaction,
|
||||||
|
);
|
||||||
|
|
||||||
|
expectLater(
|
||||||
|
// skipping first seed message list -> [] messages
|
||||||
|
channel.state?.threadsStream
|
||||||
|
.skip(1)
|
||||||
|
.map((event) => event['test-parent-id']),
|
||||||
|
emitsInOrder([
|
||||||
|
[
|
||||||
|
isSameMessageAs(
|
||||||
|
newMessage.copyWith(status: MessageSendingStatus.sent),
|
||||||
|
matchReactions: true,
|
||||||
|
matchSendingStatus: true,
|
||||||
|
matchParentId: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
final res = await channel.sendReaction(
|
||||||
|
message,
|
||||||
|
type,
|
||||||
|
enforceUnique: enforceUnique,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(res, isNotNull);
|
||||||
|
expect(res.reaction.type, type);
|
||||||
|
expect(res.reaction.messageId, messageId);
|
||||||
|
|
||||||
|
verify(() => client.sendReaction(
|
||||||
|
messageId,
|
||||||
|
type,
|
||||||
|
enforceUnique: enforceUnique,
|
||||||
|
)).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
group('`.deleteReaction`', () {
|
group('`.deleteReaction`', () {
|
||||||
test('should work fine', () async {
|
test('should work fine', () async {
|
||||||
const userId = 'test-user-id';
|
const userId = 'test-user-id';
|
||||||
@@ -1343,6 +1526,121 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('`.deleteReaction in thread`', () {
|
||||||
|
test('should work fine', () async {
|
||||||
|
const userId = 'test-user-id';
|
||||||
|
const messageId = 'test-message-id';
|
||||||
|
const parentId = 'test-parent-id';
|
||||||
|
const type = 'test-reaction-type';
|
||||||
|
final reaction = Reaction(
|
||||||
|
type: type,
|
||||||
|
messageId: messageId,
|
||||||
|
userId: userId,
|
||||||
|
);
|
||||||
|
final message = Message(
|
||||||
|
id: messageId,
|
||||||
|
parentId: parentId, // is thread
|
||||||
|
ownReactions: [reaction],
|
||||||
|
latestReactions: [reaction],
|
||||||
|
reactionScores: const {type: 1},
|
||||||
|
reactionCounts: const {type: 1},
|
||||||
|
);
|
||||||
|
|
||||||
|
when(() => client.deleteReaction(messageId, type))
|
||||||
|
.thenAnswer((_) async => EmptyResponse());
|
||||||
|
|
||||||
|
expectLater(
|
||||||
|
// skipping first seed message list -> [] messages
|
||||||
|
channel.state?.threadsStream
|
||||||
|
.skip(1)
|
||||||
|
.map((event) => event['test-parent-id']),
|
||||||
|
emitsInOrder([
|
||||||
|
[
|
||||||
|
isSameMessageAs(
|
||||||
|
message.copyWith(
|
||||||
|
status: MessageSendingStatus.sent,
|
||||||
|
latestReactions: [],
|
||||||
|
ownReactions: [],
|
||||||
|
),
|
||||||
|
matchReactions: true,
|
||||||
|
matchSendingStatus: true,
|
||||||
|
matchParentId: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
final res = await channel.deleteReaction(message, reaction);
|
||||||
|
|
||||||
|
expect(res, isNotNull);
|
||||||
|
|
||||||
|
verify(() => client.deleteReaction(messageId, type)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'should restore prev message state if `client.deleteReaction` throws',
|
||||||
|
() async {
|
||||||
|
const userId = 'test-user-id';
|
||||||
|
const messageId = 'test-message-id';
|
||||||
|
const parentId = 'test-parent-id';
|
||||||
|
const type = 'test-reaction-type';
|
||||||
|
final reaction = Reaction(
|
||||||
|
type: type,
|
||||||
|
messageId: messageId,
|
||||||
|
userId: userId,
|
||||||
|
);
|
||||||
|
final message = Message(
|
||||||
|
id: messageId,
|
||||||
|
parentId: parentId,
|
||||||
|
ownReactions: [reaction],
|
||||||
|
latestReactions: [reaction],
|
||||||
|
reactionScores: const {type: 1},
|
||||||
|
reactionCounts: const {type: 1},
|
||||||
|
);
|
||||||
|
|
||||||
|
when(() => client.deleteReaction(messageId, type))
|
||||||
|
.thenThrow(StreamChatNetworkError(ChatErrorCode.inputError));
|
||||||
|
|
||||||
|
expectLater(
|
||||||
|
// skipping first seed message list -> [] messages
|
||||||
|
channel.state?.threadsStream
|
||||||
|
.skip(1)
|
||||||
|
.map((event) => event['test-parent-id']),
|
||||||
|
emitsInOrder([
|
||||||
|
[
|
||||||
|
isSameMessageAs(
|
||||||
|
message.copyWith(
|
||||||
|
status: MessageSendingStatus.sent,
|
||||||
|
latestReactions: [],
|
||||||
|
ownReactions: [],
|
||||||
|
),
|
||||||
|
matchReactions: true,
|
||||||
|
matchSendingStatus: true,
|
||||||
|
matchParentId: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
isSameMessageAs(
|
||||||
|
message,
|
||||||
|
matchReactions: true,
|
||||||
|
matchSendingStatus: true,
|
||||||
|
matchParentId: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await channel.deleteReaction(message, reaction);
|
||||||
|
} catch (e) {
|
||||||
|
expect(e, isA<StreamChatNetworkError>());
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(() => client.deleteReaction(messageId, type)).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('`.update`', () async {
|
test('`.update`', () async {
|
||||||
const channelData = {
|
const channelData = {
|
||||||
'name': 'Stream Team',
|
'name': 'Stream Team',
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ Matcher isSameMessageAs(
|
|||||||
bool matchSendingStatus = false,
|
bool matchSendingStatus = false,
|
||||||
bool matchAttachments = false,
|
bool matchAttachments = false,
|
||||||
bool matchAttachmentsUploadState = false,
|
bool matchAttachmentsUploadState = false,
|
||||||
|
bool matchParentId = false,
|
||||||
}) =>
|
}) =>
|
||||||
_IsSameMessageAs(
|
_IsSameMessageAs(
|
||||||
targetMessage: targetMessage,
|
targetMessage: targetMessage,
|
||||||
@@ -57,6 +58,7 @@ Matcher isSameMessageAs(
|
|||||||
matchSendingStatus: matchSendingStatus,
|
matchSendingStatus: matchSendingStatus,
|
||||||
matchAttachments: matchAttachments,
|
matchAttachments: matchAttachments,
|
||||||
matchAttachmentsUploadState: matchAttachmentsUploadState,
|
matchAttachmentsUploadState: matchAttachmentsUploadState,
|
||||||
|
matchParentId: matchParentId,
|
||||||
);
|
);
|
||||||
|
|
||||||
class _IsSameMessageAs extends Matcher {
|
class _IsSameMessageAs extends Matcher {
|
||||||
@@ -67,6 +69,7 @@ class _IsSameMessageAs extends Matcher {
|
|||||||
this.matchSendingStatus = false,
|
this.matchSendingStatus = false,
|
||||||
this.matchAttachments = false,
|
this.matchAttachments = false,
|
||||||
this.matchAttachmentsUploadState = false,
|
this.matchAttachmentsUploadState = false,
|
||||||
|
this.matchParentId = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Message targetMessage;
|
final Message targetMessage;
|
||||||
@@ -75,6 +78,7 @@ class _IsSameMessageAs extends Matcher {
|
|||||||
final bool matchSendingStatus;
|
final bool matchSendingStatus;
|
||||||
final bool matchAttachments;
|
final bool matchAttachments;
|
||||||
final bool matchAttachmentsUploadState;
|
final bool matchAttachmentsUploadState;
|
||||||
|
final bool matchParentId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Description describe(Description description) =>
|
Description describe(Description description) =>
|
||||||
@@ -123,6 +127,9 @@ class _IsSameMessageAs extends Matcher {
|
|||||||
|
|
||||||
matches &= matchAttachments();
|
matches &= matchAttachments();
|
||||||
}
|
}
|
||||||
|
if (matchParentId) {
|
||||||
|
matches &= message.parentId == targetMessage.parentId;
|
||||||
|
}
|
||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user