Merge branch 'develop' into fix/message-search-pagination

This commit is contained in:
Sahil Kumar
2021-09-08 15:51:33 +05:30
committed by GitHub
49 changed files with 1433 additions and 321 deletions
@@ -107,6 +107,48 @@ The 'exists' filter matches values that exist, or don't exist, based on the spec
Filter.exists('name', true)
```
#### Filter.contains
The 'contains' filter matches any list that contains the specified value.
```dart
Filter.contains('teams', 'red')
```
#### Filter.empty
The 'empty' filter constructor returns an empty filter. It's the equivalent of an empty map `{}`;
```dart
Filter.empty();
```
#### Filter.raw
The 'raw' filter constructor lets you specify a raw filter. We suggest using this only if you can't manage to build what you want using the other constructors.
```dart
Filter.raw(value: {
'members': [
..._selectedUsers.map((e) => e.id),
chatState.currentUser!.id,
],
'distinct': true,
});
```
#### Filter.custom
The 'custom' filter is used to create a custom filter in case it does not exists or it's not been added to the SDK yet.
Note that the filter must be supported by the Stream backend in order to work.
```dart
Filter.custom(
operator: '\$max',
value: 10,
)
```
### Group Queries
#### Filter.and
+13 -1
View File
@@ -1,8 +1,20 @@
## Upcoming
🛑️ Breaking Changes from `2.2.1`
- Added 6 new methods in `ChatPersistenceClient`.
- `bulkUpdateMessages`
- `bulkUpdatePinnedMessages`
- `bulkUpdateMembers`
- `bulkUpdateReads`
- `updatePinnedMessageReactions`
- `deletePinnedMessageReactionsByMessageId`
✅ Added
- Add support for `next`, `previous` value pagination in `client.search`, [read more.](https://getstream.io/chat/docs/other-rest/search/#pagination)
- Added `Filter.contains` and `Filter.empty`
- Added support for `next`, `previous` value pagination in `client.search`
, [read more.](https://getstream.io/chat/docs/other-rest/search/#pagination)
## 2.2.1
@@ -51,6 +51,9 @@ enum FilterOperator {
/// Matches none of the values specified in an array.
nor,
/// Matches any list that contains the specified value
contains,
}
/// Helper extension for [FilterOperator]
@@ -71,6 +74,7 @@ extension FilterOperatorX on FilterOperator {
FilterOperator.and: '\$and',
FilterOperator.or: '\$or',
FilterOperator.nor: '\$nor',
FilterOperator.contains: '\$contains',
}[this]!;
}
@@ -157,6 +161,10 @@ class Filter extends Equatable {
factory Filter.exists(String key, {bool exists = true}) =>
Filter._(operator: FilterOperator.exists, key: key, value: exists);
/// Matches any list that contains the specified values
factory Filter.contains(String key, Object value) =>
Filter._(operator: FilterOperator.contains, key: key, value: value);
/// Creates a custom [Filter] if there isn't one already available.
const factory Filter.custom({
required Object value,
@@ -164,6 +172,9 @@ class Filter extends Equatable {
String? key,
}) = Filter.__;
/// An empty filter
factory Filter.empty() => const Filter.raw(value: {});
/// Creates a custom [Filter] from a raw map value
///
/// ```dart
@@ -143,11 +143,19 @@ abstract class ChatPersistenceClient {
/// Updates the message data of a particular channel [cid] with
/// the new [messages] data
Future<void> updateMessages(String cid, List<Message> messages);
Future<void> updateMessages(String cid, List<Message> messages) =>
bulkUpdateMessages({cid: messages});
/// Bulk updates the message data of multiple channels.
Future<void> bulkUpdateMessages(Map<String, List<Message>> messages);
/// Updates the pinned message data of a particular channel [cid] with
/// the new [messages] data
Future<void> updatePinnedMessages(String cid, List<Message> messages);
Future<void> updatePinnedMessages(String cid, List<Message> messages) =>
bulkUpdatePinnedMessages({cid: messages});
/// Bulk updates the message data of multiple channels.
Future<void> bulkUpdatePinnedMessages(Map<String, List<Message>> messages);
/// Returns all the threads by parent message of a particular channel by
/// providing channel [cid]
@@ -158,11 +166,19 @@ abstract class ChatPersistenceClient {
/// Updates all the members of a particular channle [cid]
/// with the new [members] data
Future<void> updateMembers(String cid, List<Member> members);
Future<void> updateMembers(String cid, List<Member> members) =>
bulkUpdateMembers({cid: members});
/// Bulk updates the members data of multiple channels.
Future<void> bulkUpdateMembers(Map<String, List<Member>> members);
/// Updates the read data of a particular channel [cid] with
/// the new [reads] data
Future<void> updateReads(String cid, List<Read> reads);
Future<void> updateReads(String cid, List<Read> reads) =>
bulkUpdateReads({cid: reads});
/// Bulk updates the read data of multiple channels.
Future<void> bulkUpdateReads(Map<String, List<Read>> reads);
/// Updates the users data with the new [users] data
Future<void> updateUsers(List<User> users);
@@ -170,9 +186,15 @@ abstract class ChatPersistenceClient {
/// Updates the reactions data with the new [reactions] data
Future<void> updateReactions(List<Reaction> reactions);
/// Updates the pinned message reactions data with the new [reactions] data
Future<void> updatePinnedMessageReactions(List<Reaction> reactions);
/// Deletes all the reactions by [messageIds]
Future<void> deleteReactionsByMessageId(List<String> messageIds);
/// Deletes all the pinned messages reactions by [messageIds]
Future<void> deletePinnedMessageReactionsByMessageId(List<String> messageIds);
/// Deletes all the members by channel [cids]
Future<void> deleteMembersByCids(List<String> cids);
@@ -182,85 +204,92 @@ abstract class ChatPersistenceClient {
/// Update list of channel states
Future<void> updateChannelStates(List<ChannelState> channelStates) async {
final deleteReactions = deleteReactionsByMessageId(channelStates
.expand((it) => it.messages)
.map((m) => m.id)
.toList(growable: false));
final reactionsToDelete = <String>[];
final pinnedReactionsToDelete = <String>[];
final membersToDelete = <String>[];
final cleanedChannelStates =
channelStates.where((it) => it.channel != null);
final channels = <ChannelModel>[];
final channelWithMessages = <String, List<Message>>{};
final channelWithPinnedMessages = <String, List<Message>>{};
final channelWithReads = <String, List<Read>>{};
final channelWithMembers = <String, List<Member>>{};
final deleteMembers = deleteMembersByCids(
cleanedChannelStates.map((it) => it.channel!.cid).toList(growable: false),
);
final users = <User>[];
final reactions = <Reaction>[];
final pinnedReactions = <Reaction>[];
for (final state in channelStates) {
final channel = state.channel;
if (channel != null) {
channels.add(channel);
final cid = channel.cid;
final reads = state.read;
final members = state.members;
final messages = state.messages;
final pinnedMessages = state.pinnedMessages;
// Preparing deletion data
membersToDelete.add(cid);
reactionsToDelete.addAll(state.messages.map((it) => it.id));
pinnedReactionsToDelete.addAll(state.pinnedMessages.map((it) => it.id));
// preparing addition data
channelWithReads[cid] = reads;
channelWithMembers[cid] = members;
channelWithMessages[cid] = messages;
channelWithPinnedMessages[cid] = pinnedMessages;
List<Reaction> expandReactions(Message message) {
final own = message.ownReactions;
final latest = message.latestReactions;
return [
if (own != null) ...own.where((r) => r.userId != null),
if (latest != null) ...latest.where((r) => r.userId != null),
];
}
reactions.addAll(messages.expand(expandReactions));
pinnedReactions.addAll(pinnedMessages.expand(expandReactions));
users.addAll([
channel.createdBy,
...reads.map((it) => it.user),
...members.map((it) => it.user),
...reactions.map((it) => it.user),
...pinnedReactions.map((it) => it.user),
].withNullifyer);
}
}
// Removing old members and reactions data as they may have
// changes over the time.
await Future.wait([
deleteReactions,
deleteMembers,
deleteMembersByCids(membersToDelete),
deleteReactionsByMessageId(reactionsToDelete),
deletePinnedMessageReactionsByMessageId(pinnedReactionsToDelete),
]);
final channels = cleanedChannelStates.map((it) => it.channel).withNullifyer;
final reactions = cleanedChannelStates
.expand((it) => it.messages)
.expand((it) => [
if (it.ownReactions != null)
...it.ownReactions!.where((r) => r.userId != null),
if (it.latestReactions != null)
...it.latestReactions!.where((r) => r.userId != null),
])
.withNullifyer;
final users = cleanedChannelStates
.map((cs) => [
cs.channel?.createdBy,
...cs.messages
.map((m) => [
m.user,
if (m.latestReactions != null)
...m.latestReactions!.map((r) => r.user),
if (m.ownReactions != null)
...m.ownReactions!.map((r) => r.user),
])
.expand((v) => v),
...cs.read.map((r) => r.user),
...cs.members.map((m) => m.user),
])
.expand((it) => it)
.withNullifyer;
final updateMessagesFuture = cleanedChannelStates.map((it) {
final cid = it.channel!.cid;
final messages = it.messages;
return updateMessages(cid, messages.toList(growable: false));
}).toList(growable: false);
final updatePinnedMessagesFuture = cleanedChannelStates.map((it) {
final cid = it.channel!.cid;
final messages = it.pinnedMessages;
return updatePinnedMessages(cid, messages.toList(growable: false));
}).toList(growable: false);
final updateReadsFuture = cleanedChannelStates.map((it) {
final cid = it.channel!.cid;
final reads = it.read;
return updateReads(cid, reads.toList(growable: false));
}).toList(growable: false);
final updateMembersFuture = cleanedChannelStates.map((it) {
final cid = it.channel!.cid;
final members = it.members;
return updateMembers(cid, members.toList(growable: false));
}).toList(growable: false);
// Updating first as does not depend on any other table.
await Future.wait([
...updateMessagesFuture,
...updatePinnedMessagesFuture,
...updateReadsFuture,
...updateMembersFuture,
updateUsers(users.toList(growable: false)),
updateChannels(channels.toList(growable: false)),
]);
// All has a foreign key relation with channels table.
await Future.wait([
bulkUpdateReads(channelWithReads),
bulkUpdateMembers(channelWithMembers),
bulkUpdateMessages(channelWithMessages),
bulkUpdatePinnedMessages(channelWithPinnedMessages),
]);
// Both has a foreign key relation with messages, pinnedMessages table.
await Future.wait([
updateReactions(reactions.toList(growable: false)),
updatePinnedMessageReactions(
pinnedReactions.toList(growable: false),
),
]);
}
}
@@ -138,6 +138,20 @@ void main() {
expect(filter.value, value);
});
test('empty', () {
final filter = Filter.empty();
expect(filter.value, {});
});
test('contains', () {
const key = 'testKey';
const values = 'testValue';
final filter = Filter.contains(key, values);
expect(filter.key, key);
expect(filter.value, values);
expect(filter.operator, FilterOperator.contains.rawValue);
});
group('groupedOperator', () {
final filter1 = Filter.equal('testKey', 'testValue');
final filter2 = Filter.in_('testKey', const ['testValue']);
@@ -38,6 +38,11 @@ class TestPersistenceClient extends ChatPersistenceClient {
Future<void> deleteReactionsByMessageId(List<String> messageIds) =>
Future.value();
@override
Future<void> deletePinnedMessageReactionsByMessageId(
List<String> messageIds) =>
Future.value();
@override
Future<void> disconnect({bool flush = false}) => throw UnimplementedError();
@@ -101,26 +106,30 @@ class TestPersistenceClient extends ChatPersistenceClient {
Future<void> updateLastSyncAt(DateTime lastSyncAt) =>
throw UnimplementedError();
@override
Future<void> updateMembers(String cid, List<Member> members) =>
Future.value();
@override
Future<void> updateMessages(String cid, List<Message> messages) =>
Future.value();
@override
Future<void> updatePinnedMessages(String cid, List<Message> messages) =>
Future.value();
@override
Future<void> updateReactions(List<Reaction> reactions) => Future.value();
@override
Future<void> updateReads(String cid, List<Read> reads) => Future.value();
Future<void> updatePinnedMessageReactions(List<Reaction> reactions) =>
Future.value();
@override
Future<void> updateUsers(List<User> users) => Future.value();
@override
Future<void> bulkUpdateMembers(Map<String, List<Member>> members) =>
Future.value();
@override
Future<void> bulkUpdateMessages(Map<String, List<Message>> messages) =>
Future.value();
@override
Future<void> bulkUpdatePinnedMessages(Map<String, List<Message>> messages) =>
Future.value();
@override
Future<void> bulkUpdateReads(Map<String, List<Read>> reads) => Future.value();
}
void main() {
@@ -4,8 +4,8 @@ import 'package:stream_chat_flutter/src/group_avatar.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_image.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_image_paint.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_image.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_image_paint.png)
///
/// It shows the current [Channel] image.
///
@@ -8,8 +8,8 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header_paint.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_header.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_header_paint.png)
///
/// It shows the current [Channel] information.
///
@@ -22,8 +22,8 @@ typedef ChannelPreviewBuilder = Widget Function(BuildContext, Channel);
/// Callback for when 'View Info' is tapped
typedef ViewInfoCallback = void Function(Channel);
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_list_view.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_list_view_paint.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_list_view.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_list_view_paint.png)
///
/// It shows the list of current channels.
///
@@ -8,8 +8,8 @@ import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview_paint.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_preview.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_preview_paint.png)
///
/// It shows the current [Channel] preview.
///
@@ -110,11 +110,14 @@ const _kMinMediaPickerSize = 360.0;
const _kDefaultMaxAttachmentSize = 20971520; // 20MB in Bytes
/// Inactive state
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_input.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_input_paint.png)
///
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_input.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_input_paint.png)
///
/// Focused state
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_input2.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_input2_paint.png)
///
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_input2.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_input2_paint.png)
///
/// Widget used to enter the message and add attachments
///
@@ -91,8 +91,8 @@ class MessageDetails {
final int index;
}
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_listview.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_listview_paint.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_listview.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_listview_paint.png)
///
/// It shows the list of messages of the current channel.
///
@@ -40,8 +40,8 @@ enum DisplayWidget {
show,
}
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_widget.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_widget_paint.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_widget.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_widget_paint.png)
///
/// It shows a message with reactions, replies and user avatar.
///
@@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/reaction_picker.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/reaction_picker_paint.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/reaction_picker.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/reaction_picker_paint.png)
///
/// It shows a reaction picker
///
@@ -4,8 +4,8 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/thread_header.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/thread_header_paint.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/thread_header.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/thread_header_paint.png)
///
/// It shows the current thread information.
///
@@ -1,3 +1,10 @@
## Upcoming
- [[#604]](https://github.com/GetStream/stream-chat-flutter/issues/604) Fix cascade deletion by
enabling `pragma foreign_keys`.
- Added a new table `PinnedMessageReactions` and dao `PinnedMessageReactionDao` specifically for pinned messages.
- Updated `stream_chat`: `^2.2.0` -> `TODO`
## 2.2.0
- Updated llc dependency
@@ -11,14 +18,17 @@
## 2.1.0
✅ Added
- Added support for `Message.i18n`
- Added support for `User.language`
## 2.0.0
* Migrate this package to null safety
* Minor fixes and improvements
## 2.0.0-nullsafety.8
* Updated llc dependency
* Upgraded moor dependencies and generated files with the latest dependency
@@ -42,10 +42,9 @@ class ChannelDao extends DatabaseAccessor<MoorChatDatabase>
/// Updates all the channels using the new [channelList] data
Future<void> updateChannels(List<ChannelModel> channelList) => batch(
(it) => it.insertAll(
(it) => it.insertAllOnConflictUpdate(
channels,
channelList.map((c) => c.toEntity()).toList(),
mode: InsertMode.insertOrReplace,
),
);
}
@@ -45,13 +45,12 @@ class ChannelQueryDao extends DatabaseAccessor<MoorChatDatabase>
}
await batch((it) {
it.insertAll(
it.insertAllOnConflictUpdate(
channelQueries,
cids
.map((cid) =>
ChannelQueryEntity(queryHash: hash, channelCid: cid))
.toList(),
mode: InsertMode.insertOrReplace,
);
});
});
@@ -26,7 +26,7 @@ class ConnectionEventDao extends DatabaseAccessor<MoorChatDatabase>
/// Update stored connection event with latest data
Future<int> updateConnectionEvent(Event event) => transaction(() async {
final connectionInfo = await select(connectionEvents).getSingleOrNull();
return into(connectionEvents).insert(
return into(connectionEvents).insertOnConflictUpdate(
ConnectionEventEntity(
id: 1,
type: event.type,
@@ -38,7 +38,6 @@ class ConnectionEventDao extends DatabaseAccessor<MoorChatDatabase>
unreadChannels:
event.unreadChannels ?? connectionInfo?.unreadChannels,
),
mode: InsertMode.insertOrReplace,
);
});
@@ -4,6 +4,7 @@ export 'connection_event_dao.dart';
export 'member_dao.dart';
export 'message_dao.dart';
export 'pinned_message_dao.dart';
export 'pinned_message_reaction_dao.dart';
export 'reaction_dao.dart';
export 'read_dao.dart';
export 'user_dao.dart';
@@ -30,14 +30,19 @@ class MemberDao extends DatabaseAccessor<MoorChatDatabase>
}).get();
/// Updates all the members using the new [memberList] data
Future<void> updateMembers(String cid, List<Member> memberList) async =>
batch(
(it) => it.insertAll(
members,
memberList.map((m) => m.toEntity(cid: cid)).toList(),
mode: InsertMode.insertOrReplace,
),
);
Future<void> updateMembers(String cid, List<Member> memberList) =>
bulkUpdateMembers({cid: memberList});
/// Bulk updates the members data of multiple channels
Future<void> bulkUpdateMembers(Map<String, List<Member>> channelWithMembers) {
final entities = channelWithMembers.entries
.map((entry) => entry.value.map(
(member) => member.toEntity(cid: entry.key),
))
.expand((it) => it)
.toList(growable: false);
return batch((batch) => batch.insertAllOnConflictUpdate(members, entities));
}
/// Deletes all the members whose [Members.channelCid] is present in [cids]
Future<void> deleteMemberByCids(List<String> cids) async => batch((it) {
@@ -170,13 +170,21 @@ class MessageDao extends DatabaseAccessor<MoorChatDatabase>
/// Updates the message data of a particular channel with
/// the new [messageList] data
Future<void> updateMessages(String cid, List<Message> messageList) => batch(
(batch) {
batch.insertAll(
messages,
messageList.map((it) => it.toEntity(cid: cid)).toList(),
mode: InsertMode.insertOrReplace,
);
},
);
Future<void> updateMessages(String cid, List<Message> messageList) =>
bulkUpdateMessages({cid: messageList});
/// Bulk updates the message data of multiple channels
Future<void> bulkUpdateMessages(
Map<String, List<Message>> channelWithMessages,
) {
final entities = channelWithMessages.entries
.map((entry) => entry.value.map(
(message) => message.toEntity(cid: entry.key),
))
.expand((it) => it)
.toList(growable: false);
return batch(
(batch) => batch.insertAllOnConflictUpdate(messages, entities),
);
}
}
@@ -39,8 +39,10 @@ class PinnedMessageDao extends DatabaseAccessor<MoorChatDatabase>
final userEntity = rows.readTableOrNull(users);
final pinnedByEntity = rows.readTableOrNull(_pinnedByUsers);
final msgEntity = rows.readTable(pinnedMessages);
final latestReactions = await _db.reactionDao.getReactions(msgEntity.id);
final ownReactions = await _db.reactionDao.getReactionsByUserId(
final latestReactions =
await _db.pinnedMessageReactionDao.getReactions(msgEntity.id);
final ownReactions =
await _db.pinnedMessageReactionDao.getReactionsByUserId(
msgEntity.id,
_db.userId,
);
@@ -168,13 +170,21 @@ class PinnedMessageDao extends DatabaseAccessor<MoorChatDatabase>
/// Updates the message data of a particular channel with
/// the new [messageList] data
Future<void> updateMessages(String cid, List<Message> messageList) => batch(
(batch) {
batch.insertAll(
pinnedMessages,
messageList.map((it) => it.toPinnedEntity(cid: cid)).toList(),
mode: InsertMode.insertOrReplace,
);
},
);
Future<void> updateMessages(String cid, List<Message> messageList) =>
bulkUpdateMessages({cid: messageList});
/// Bulk updates the message data of multiple channels
Future<void> bulkUpdateMessages(
Map<String, List<Message>> channelWithMessages,
) {
final entities = channelWithMessages.entries
.map((entry) => entry.value.map(
(message) => message.toPinnedEntity(cid: entry.key),
))
.expand((it) => it)
.toList(growable: false);
return batch(
(batch) => batch.insertAllOnConflictUpdate(pinnedMessages, entities),
);
}
}
@@ -0,0 +1,60 @@
import 'package:moor/moor.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
import 'package:stream_chat_persistence/src/entity/pinned_message_reactions.dart';
import 'package:stream_chat_persistence/src/entity/users.dart';
import 'package:stream_chat_persistence/src/mapper/mapper.dart';
part 'pinned_message_reaction_dao.g.dart';
/// The Data Access Object for operations in [PinnedMessageReactions] table.
@UseDao(tables: [PinnedMessageReactions, Users])
class PinnedMessageReactionDao extends DatabaseAccessor<MoorChatDatabase>
with _$PinnedMessageReactionDaoMixin {
/// Creates a new reaction dao instance
PinnedMessageReactionDao(MoorChatDatabase db) : super(db);
/// Returns all the reactions of a particular message by matching
/// [Reactions.messageId] with [messageId]
Future<List<Reaction>> getReactions(String messageId) =>
(select(pinnedMessageReactions).join([
leftOuterJoin(users, pinnedMessageReactions.userId.equalsExp(users.id)),
])
..where(pinnedMessageReactions.messageId.equals(messageId))
..orderBy([OrderingTerm.asc(pinnedMessageReactions.createdAt)]))
.map((rows) {
final userEntity = rows.readTableOrNull(users);
final reactionEntity = rows.readTable(pinnedMessageReactions);
return reactionEntity.toReaction(user: userEntity?.toUser());
}).get();
/// Returns all the reactions of a particular message
/// added by a particular user by matching
/// [Reactions.messageId] with [messageId] and
/// [Reactions.userId] with [userId]
Future<List<Reaction>> getReactionsByUserId(
String messageId,
String userId,
) async {
final reactions = await getReactions(messageId);
return reactions.where((it) => it.userId == userId).toList();
}
/// Updates the reactions data with the new [reactionList] data
Future<void> updateReactions(List<Reaction> reactionList) => batch((it) {
it.insertAllOnConflictUpdate(
pinnedMessageReactions,
reactionList.map((r) => r.toPinnedEntity()).toList(),
);
});
/// Deletes all the reactions whose [Reactions.messageId] is
/// present in [messageIds]
Future<void> deleteReactionsByMessageIds(List<String> messageIds) =>
batch((it) {
it.deleteWhere<PinnedMessageReactions, PinnedMessageReactionEntity>(
pinnedMessageReactions,
(r) => r.messageId.isIn(messageIds),
);
});
}
@@ -0,0 +1,13 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'pinned_message_reaction_dao.dart';
// **************************************************************************
// DaoGenerator
// **************************************************************************
mixin _$PinnedMessageReactionDaoMixin on DatabaseAccessor<MoorChatDatabase> {
$PinnedMessageReactionsTable get pinnedMessageReactions =>
attachedDatabase.pinnedMessageReactions;
$UsersTable get users => attachedDatabase.users;
}
@@ -42,10 +42,9 @@ class ReactionDao extends DatabaseAccessor<MoorChatDatabase>
/// Updates the reactions data with the new [reactionList] data
Future<void> updateReactions(List<Reaction> reactionList) => batch((it) {
it.insertAll(
it.insertAllOnConflictUpdate(
reactions,
reactionList.map((r) => r.toEntity()).toList(),
mode: InsertMode.insertOrReplace,
);
});
@@ -29,11 +29,17 @@ class ReadDao extends DatabaseAccessor<MoorChatDatabase> with _$ReadDaoMixin {
/// Updates the read data of a particular channel with
/// the new [readList] data
Future<void> updateReads(String cid, List<Read> readList) => batch(
(it) => it.insertAll(
reads,
readList.map((r) => r.toEntity(cid: cid)).toList(),
mode: InsertMode.insertOrReplace,
),
);
Future<void> updateReads(String cid, List<Read> readList) =>
bulkUpdateReads({cid: readList});
/// Bulk updates the reads data of multiple channels
Future<void> bulkUpdateReads(Map<String, List<Read>> channelWithReads) {
final entities = channelWithReads.entries
.map((entry) => entry.value.map(
(read) => read.toEntity(cid: entry.key),
))
.expand((it) => it)
.toList(growable: false);
return batch((batch) => batch.insertAllOnConflictUpdate(reads, entities));
}
}
@@ -14,10 +14,9 @@ class UserDao extends DatabaseAccessor<MoorChatDatabase> with _$UserDaoMixin {
/// Updates the users data with the new [userList] data
Future<void> updateUsers(List<User> userList) => batch(
(it) => it.insertAll(
(it) => it.insertAllOnConflictUpdate(
users,
userList.map((u) => u.toEntity()).toList(),
mode: InsertMode.insertOrReplace,
),
);
@@ -14,6 +14,7 @@ part 'moor_chat_database.g.dart';
Channels,
Messages,
PinnedMessages,
PinnedMessageReactions,
Reactions,
Users,
Members,
@@ -25,6 +26,7 @@ part 'moor_chat_database.g.dart';
ChannelDao,
MessageDao,
PinnedMessageDao,
PinnedMessageReactionDao,
MemberDao,
ReactionDao,
ReadDao,
@@ -51,10 +53,13 @@ class MoorChatDatabase extends _$MoorChatDatabase {
// you should bump this number whenever you change or add a table definition.
@override
int get schemaVersion => 5;
int get schemaVersion => 6;
@override
MigrationStrategy get migration => MigrationStrategy(
beforeOpen: (details) async {
await customStatement('PRAGMA foreign_keys = ON');
},
onUpgrade: (openingDetails, before, after) async {
if (before != after) {
final m = createMigrator();
@@ -645,7 +645,7 @@ class MessageEntity extends DataClass implements Insertable<MessageEntity> {
final String? pinnedByUserId;
/// The channel cid of which this message is part of
final String? channelCid;
final String channelCid;
/// A Map of [messageText] translations.
final Map<String, String>? i18n;
@@ -675,7 +675,7 @@ class MessageEntity extends DataClass implements Insertable<MessageEntity> {
this.pinnedAt,
this.pinExpires,
this.pinnedByUserId,
this.channelCid,
required this.channelCid,
this.i18n,
this.extraData});
factory MessageEntity.fromData(
@@ -728,7 +728,7 @@ class MessageEntity extends DataClass implements Insertable<MessageEntity> {
pinnedByUserId: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}pinned_by_user_id']),
channelCid: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}channel_cid']),
.mapFromDatabaseResponse(data['${effectivePrefix}channel_cid'])!,
i18n: $MessagesTable.$converter5.mapToDart(const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}i18n'])),
extraData: $MessagesTable.$converter6.mapToDart(const StringType()
@@ -800,9 +800,7 @@ class MessageEntity extends DataClass implements Insertable<MessageEntity> {
if (!nullToAbsent || pinnedByUserId != null) {
map['pinned_by_user_id'] = Variable<String?>(pinnedByUserId);
}
if (!nullToAbsent || channelCid != null) {
map['channel_cid'] = Variable<String?>(channelCid);
}
map['channel_cid'] = Variable<String>(channelCid);
if (!nullToAbsent || i18n != null) {
final converter = $MessagesTable.$converter5;
map['i18n'] = Variable<String?>(converter.mapToSql(i18n));
@@ -842,7 +840,7 @@ class MessageEntity extends DataClass implements Insertable<MessageEntity> {
pinnedAt: serializer.fromJson<DateTime?>(json['pinnedAt']),
pinExpires: serializer.fromJson<DateTime?>(json['pinExpires']),
pinnedByUserId: serializer.fromJson<String?>(json['pinnedByUserId']),
channelCid: serializer.fromJson<String?>(json['channelCid']),
channelCid: serializer.fromJson<String>(json['channelCid']),
i18n: serializer.fromJson<Map<String, String>?>(json['i18n']),
extraData: serializer.fromJson<Map<String, Object?>?>(json['extraData']),
);
@@ -873,7 +871,7 @@ class MessageEntity extends DataClass implements Insertable<MessageEntity> {
'pinnedAt': serializer.toJson<DateTime?>(pinnedAt),
'pinExpires': serializer.toJson<DateTime?>(pinExpires),
'pinnedByUserId': serializer.toJson<String?>(pinnedByUserId),
'channelCid': serializer.toJson<String?>(channelCid),
'channelCid': serializer.toJson<String>(channelCid),
'i18n': serializer.toJson<Map<String, String>?>(i18n),
'extraData': serializer.toJson<Map<String, Object?>?>(extraData),
};
@@ -902,7 +900,7 @@ class MessageEntity extends DataClass implements Insertable<MessageEntity> {
Value<DateTime?> pinnedAt = const Value.absent(),
Value<DateTime?> pinExpires = const Value.absent(),
Value<String?> pinnedByUserId = const Value.absent(),
Value<String?> channelCid = const Value.absent(),
String? channelCid,
Value<Map<String, String>?> i18n = const Value.absent(),
Value<Map<String, Object?>?> extraData = const Value.absent()}) =>
MessageEntity(
@@ -934,7 +932,7 @@ class MessageEntity extends DataClass implements Insertable<MessageEntity> {
pinExpires: pinExpires.present ? pinExpires.value : this.pinExpires,
pinnedByUserId:
pinnedByUserId.present ? pinnedByUserId.value : this.pinnedByUserId,
channelCid: channelCid.present ? channelCid.value : this.channelCid,
channelCid: channelCid ?? this.channelCid,
i18n: i18n.present ? i18n.value : this.i18n,
extraData: extraData.present ? extraData.value : this.extraData,
);
@@ -1068,7 +1066,7 @@ class MessagesCompanion extends UpdateCompanion<MessageEntity> {
final Value<DateTime?> pinnedAt;
final Value<DateTime?> pinExpires;
final Value<String?> pinnedByUserId;
final Value<String?> channelCid;
final Value<String> channelCid;
final Value<Map<String, String>?> i18n;
final Value<Map<String, Object?>?> extraData;
const MessagesCompanion({
@@ -1121,12 +1119,13 @@ class MessagesCompanion extends UpdateCompanion<MessageEntity> {
this.pinnedAt = const Value.absent(),
this.pinExpires = const Value.absent(),
this.pinnedByUserId = const Value.absent(),
this.channelCid = const Value.absent(),
required String channelCid,
this.i18n = const Value.absent(),
this.extraData = const Value.absent(),
}) : id = Value(id),
attachments = Value(attachments),
mentionedUsers = Value(mentionedUsers);
mentionedUsers = Value(mentionedUsers),
channelCid = Value(channelCid);
static Insertable<MessageEntity> custom({
Expression<String>? id,
Expression<String?>? messageText,
@@ -1150,7 +1149,7 @@ class MessagesCompanion extends UpdateCompanion<MessageEntity> {
Expression<DateTime?>? pinnedAt,
Expression<DateTime?>? pinExpires,
Expression<String?>? pinnedByUserId,
Expression<String?>? channelCid,
Expression<String>? channelCid,
Expression<Map<String, String>?>? i18n,
Expression<Map<String, Object?>?>? extraData,
}) {
@@ -1206,7 +1205,7 @@ class MessagesCompanion extends UpdateCompanion<MessageEntity> {
Value<DateTime?>? pinnedAt,
Value<DateTime?>? pinExpires,
Value<String?>? pinnedByUserId,
Value<String?>? channelCid,
Value<String>? channelCid,
Value<Map<String, String>?>? i18n,
Value<Map<String, Object?>?>? extraData}) {
return MessagesCompanion(
@@ -1317,7 +1316,7 @@ class MessagesCompanion extends UpdateCompanion<MessageEntity> {
map['pinned_by_user_id'] = Variable<String?>(pinnedByUserId.value);
}
if (channelCid.present) {
map['channel_cid'] = Variable<String?>(channelCid.value);
map['channel_cid'] = Variable<String>(channelCid.value);
}
if (i18n.present) {
final converter = $MessagesTable.$converter5;
@@ -1491,11 +1490,10 @@ class $MessagesTable extends Messages
typeName: 'TEXT', requiredDuringInsert: false);
final VerificationMeta _channelCidMeta = const VerificationMeta('channelCid');
late final GeneratedColumn<String?> channelCid = GeneratedColumn<String?>(
'channel_cid', aliasedName, true,
'channel_cid', aliasedName, false,
typeName: 'TEXT',
requiredDuringInsert: false,
$customConstraints:
'NULLABLE REFERENCES channels(cid) ON DELETE CASCADE');
requiredDuringInsert: true,
$customConstraints: 'REFERENCES channels(cid) ON DELETE CASCADE');
final VerificationMeta _i18nMeta = const VerificationMeta('i18n');
late final GeneratedColumnWithTypeConverter<Map<String, String>, String?>
i18n = GeneratedColumn<String?>('i18n', aliasedName, true,
@@ -1634,6 +1632,8 @@ class $MessagesTable extends Messages
_channelCidMeta,
channelCid.isAcceptableOrUnknown(
data['channel_cid']!, _channelCidMeta));
} else if (isInserting) {
context.missing(_channelCidMeta);
}
context.handle(_i18nMeta, const VerificationResult.success());
context.handle(_extraDataMeta, const VerificationResult.success());
@@ -1739,7 +1739,7 @@ class PinnedMessageEntity extends DataClass
final String? pinnedByUserId;
/// The channel cid of which this message is part of
final String? channelCid;
final String channelCid;
/// A Map of [messageText] translations.
final Map<String, String>? i18n;
@@ -1769,7 +1769,7 @@ class PinnedMessageEntity extends DataClass
this.pinnedAt,
this.pinExpires,
this.pinnedByUserId,
this.channelCid,
required this.channelCid,
this.i18n,
this.extraData});
factory PinnedMessageEntity.fromData(
@@ -1825,7 +1825,7 @@ class PinnedMessageEntity extends DataClass
pinnedByUserId: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}pinned_by_user_id']),
channelCid: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}channel_cid']),
.mapFromDatabaseResponse(data['${effectivePrefix}channel_cid'])!,
i18n: $PinnedMessagesTable.$converter5.mapToDart(const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}i18n'])),
extraData: $PinnedMessagesTable.$converter6.mapToDart(const StringType()
@@ -1897,9 +1897,7 @@ class PinnedMessageEntity extends DataClass
if (!nullToAbsent || pinnedByUserId != null) {
map['pinned_by_user_id'] = Variable<String?>(pinnedByUserId);
}
if (!nullToAbsent || channelCid != null) {
map['channel_cid'] = Variable<String?>(channelCid);
}
map['channel_cid'] = Variable<String>(channelCid);
if (!nullToAbsent || i18n != null) {
final converter = $PinnedMessagesTable.$converter5;
map['i18n'] = Variable<String?>(converter.mapToSql(i18n));
@@ -1939,7 +1937,7 @@ class PinnedMessageEntity extends DataClass
pinnedAt: serializer.fromJson<DateTime?>(json['pinnedAt']),
pinExpires: serializer.fromJson<DateTime?>(json['pinExpires']),
pinnedByUserId: serializer.fromJson<String?>(json['pinnedByUserId']),
channelCid: serializer.fromJson<String?>(json['channelCid']),
channelCid: serializer.fromJson<String>(json['channelCid']),
i18n: serializer.fromJson<Map<String, String>?>(json['i18n']),
extraData: serializer.fromJson<Map<String, Object?>?>(json['extraData']),
);
@@ -1970,7 +1968,7 @@ class PinnedMessageEntity extends DataClass
'pinnedAt': serializer.toJson<DateTime?>(pinnedAt),
'pinExpires': serializer.toJson<DateTime?>(pinExpires),
'pinnedByUserId': serializer.toJson<String?>(pinnedByUserId),
'channelCid': serializer.toJson<String?>(channelCid),
'channelCid': serializer.toJson<String>(channelCid),
'i18n': serializer.toJson<Map<String, String>?>(i18n),
'extraData': serializer.toJson<Map<String, Object?>?>(extraData),
};
@@ -1999,7 +1997,7 @@ class PinnedMessageEntity extends DataClass
Value<DateTime?> pinnedAt = const Value.absent(),
Value<DateTime?> pinExpires = const Value.absent(),
Value<String?> pinnedByUserId = const Value.absent(),
Value<String?> channelCid = const Value.absent(),
String? channelCid,
Value<Map<String, String>?> i18n = const Value.absent(),
Value<Map<String, Object?>?> extraData = const Value.absent()}) =>
PinnedMessageEntity(
@@ -2031,7 +2029,7 @@ class PinnedMessageEntity extends DataClass
pinExpires: pinExpires.present ? pinExpires.value : this.pinExpires,
pinnedByUserId:
pinnedByUserId.present ? pinnedByUserId.value : this.pinnedByUserId,
channelCid: channelCid.present ? channelCid.value : this.channelCid,
channelCid: channelCid ?? this.channelCid,
i18n: i18n.present ? i18n.value : this.i18n,
extraData: extraData.present ? extraData.value : this.extraData,
);
@@ -2165,7 +2163,7 @@ class PinnedMessagesCompanion extends UpdateCompanion<PinnedMessageEntity> {
final Value<DateTime?> pinnedAt;
final Value<DateTime?> pinExpires;
final Value<String?> pinnedByUserId;
final Value<String?> channelCid;
final Value<String> channelCid;
final Value<Map<String, String>?> i18n;
final Value<Map<String, Object?>?> extraData;
const PinnedMessagesCompanion({
@@ -2218,12 +2216,13 @@ class PinnedMessagesCompanion extends UpdateCompanion<PinnedMessageEntity> {
this.pinnedAt = const Value.absent(),
this.pinExpires = const Value.absent(),
this.pinnedByUserId = const Value.absent(),
this.channelCid = const Value.absent(),
required String channelCid,
this.i18n = const Value.absent(),
this.extraData = const Value.absent(),
}) : id = Value(id),
attachments = Value(attachments),
mentionedUsers = Value(mentionedUsers);
mentionedUsers = Value(mentionedUsers),
channelCid = Value(channelCid);
static Insertable<PinnedMessageEntity> custom({
Expression<String>? id,
Expression<String?>? messageText,
@@ -2247,7 +2246,7 @@ class PinnedMessagesCompanion extends UpdateCompanion<PinnedMessageEntity> {
Expression<DateTime?>? pinnedAt,
Expression<DateTime?>? pinExpires,
Expression<String?>? pinnedByUserId,
Expression<String?>? channelCid,
Expression<String>? channelCid,
Expression<Map<String, String>?>? i18n,
Expression<Map<String, Object?>?>? extraData,
}) {
@@ -2303,7 +2302,7 @@ class PinnedMessagesCompanion extends UpdateCompanion<PinnedMessageEntity> {
Value<DateTime?>? pinnedAt,
Value<DateTime?>? pinExpires,
Value<String?>? pinnedByUserId,
Value<String?>? channelCid,
Value<String>? channelCid,
Value<Map<String, String>?>? i18n,
Value<Map<String, Object?>?>? extraData}) {
return PinnedMessagesCompanion(
@@ -2414,7 +2413,7 @@ class PinnedMessagesCompanion extends UpdateCompanion<PinnedMessageEntity> {
map['pinned_by_user_id'] = Variable<String?>(pinnedByUserId.value);
}
if (channelCid.present) {
map['channel_cid'] = Variable<String?>(channelCid.value);
map['channel_cid'] = Variable<String>(channelCid.value);
}
if (i18n.present) {
final converter = $PinnedMessagesTable.$converter5;
@@ -2589,11 +2588,10 @@ class $PinnedMessagesTable extends PinnedMessages
typeName: 'TEXT', requiredDuringInsert: false);
final VerificationMeta _channelCidMeta = const VerificationMeta('channelCid');
late final GeneratedColumn<String?> channelCid = GeneratedColumn<String?>(
'channel_cid', aliasedName, true,
'channel_cid', aliasedName, false,
typeName: 'TEXT',
requiredDuringInsert: false,
$customConstraints:
'NULLABLE REFERENCES channels(cid) ON DELETE CASCADE');
requiredDuringInsert: true,
$customConstraints: 'REFERENCES channels(cid) ON DELETE CASCADE');
final VerificationMeta _i18nMeta = const VerificationMeta('i18n');
late final GeneratedColumnWithTypeConverter<Map<String, String>, String?>
i18n = GeneratedColumn<String?>('i18n', aliasedName, true,
@@ -2734,6 +2732,8 @@ class $PinnedMessagesTable extends PinnedMessages
_channelCidMeta,
channelCid.isAcceptableOrUnknown(
data['channel_cid']!, _channelCidMeta));
} else if (isInserting) {
context.missing(_channelCidMeta);
}
context.handle(_i18nMeta, const VerificationResult.success());
context.handle(_extraDataMeta, const VerificationResult.success());
@@ -2769,6 +2769,340 @@ class $PinnedMessagesTable extends PinnedMessages
MapConverter<Object?>();
}
class PinnedMessageReactionEntity extends DataClass
implements Insertable<PinnedMessageReactionEntity> {
/// The id of the user that sent the reaction
final String userId;
/// The messageId to which the reaction belongs
final String messageId;
/// The type of the reaction
final String type;
/// The DateTime on which the reaction is created
final DateTime createdAt;
/// The score of the reaction (ie. number of reactions sent)
final int score;
/// Reaction custom extraData
final Map<String, Object?>? extraData;
PinnedMessageReactionEntity(
{required this.userId,
required this.messageId,
required this.type,
required this.createdAt,
required this.score,
this.extraData});
factory PinnedMessageReactionEntity.fromData(
Map<String, dynamic> data, GeneratedDatabase db,
{String? prefix}) {
final effectivePrefix = prefix ?? '';
return PinnedMessageReactionEntity(
userId: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}user_id'])!,
messageId: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}message_id'])!,
type: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}type'])!,
createdAt: const DateTimeType()
.mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!,
score: const IntType()
.mapFromDatabaseResponse(data['${effectivePrefix}score'])!,
extraData: $PinnedMessageReactionsTable.$converter0.mapToDart(
const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}extra_data'])),
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['user_id'] = Variable<String>(userId);
map['message_id'] = Variable<String>(messageId);
map['type'] = Variable<String>(type);
map['created_at'] = Variable<DateTime>(createdAt);
map['score'] = Variable<int>(score);
if (!nullToAbsent || extraData != null) {
final converter = $PinnedMessageReactionsTable.$converter0;
map['extra_data'] = Variable<String?>(converter.mapToSql(extraData));
}
return map;
}
factory PinnedMessageReactionEntity.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= moorRuntimeOptions.defaultSerializer;
return PinnedMessageReactionEntity(
userId: serializer.fromJson<String>(json['userId']),
messageId: serializer.fromJson<String>(json['messageId']),
type: serializer.fromJson<String>(json['type']),
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
score: serializer.fromJson<int>(json['score']),
extraData: serializer.fromJson<Map<String, Object?>?>(json['extraData']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= moorRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'userId': serializer.toJson<String>(userId),
'messageId': serializer.toJson<String>(messageId),
'type': serializer.toJson<String>(type),
'createdAt': serializer.toJson<DateTime>(createdAt),
'score': serializer.toJson<int>(score),
'extraData': serializer.toJson<Map<String, Object?>?>(extraData),
};
}
PinnedMessageReactionEntity copyWith(
{String? userId,
String? messageId,
String? type,
DateTime? createdAt,
int? score,
Value<Map<String, Object?>?> extraData = const Value.absent()}) =>
PinnedMessageReactionEntity(
userId: userId ?? this.userId,
messageId: messageId ?? this.messageId,
type: type ?? this.type,
createdAt: createdAt ?? this.createdAt,
score: score ?? this.score,
extraData: extraData.present ? extraData.value : this.extraData,
);
@override
String toString() {
return (StringBuffer('PinnedMessageReactionEntity(')
..write('userId: $userId, ')
..write('messageId: $messageId, ')
..write('type: $type, ')
..write('createdAt: $createdAt, ')
..write('score: $score, ')
..write('extraData: $extraData')
..write(')'))
.toString();
}
@override
int get hashCode => $mrjf($mrjc(
userId.hashCode,
$mrjc(
messageId.hashCode,
$mrjc(
type.hashCode,
$mrjc(createdAt.hashCode,
$mrjc(score.hashCode, extraData.hashCode))))));
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is PinnedMessageReactionEntity &&
other.userId == this.userId &&
other.messageId == this.messageId &&
other.type == this.type &&
other.createdAt == this.createdAt &&
other.score == this.score &&
other.extraData == this.extraData);
}
class PinnedMessageReactionsCompanion
extends UpdateCompanion<PinnedMessageReactionEntity> {
final Value<String> userId;
final Value<String> messageId;
final Value<String> type;
final Value<DateTime> createdAt;
final Value<int> score;
final Value<Map<String, Object?>?> extraData;
const PinnedMessageReactionsCompanion({
this.userId = const Value.absent(),
this.messageId = const Value.absent(),
this.type = const Value.absent(),
this.createdAt = const Value.absent(),
this.score = const Value.absent(),
this.extraData = const Value.absent(),
});
PinnedMessageReactionsCompanion.insert({
required String userId,
required String messageId,
required String type,
this.createdAt = const Value.absent(),
this.score = const Value.absent(),
this.extraData = const Value.absent(),
}) : userId = Value(userId),
messageId = Value(messageId),
type = Value(type);
static Insertable<PinnedMessageReactionEntity> custom({
Expression<String>? userId,
Expression<String>? messageId,
Expression<String>? type,
Expression<DateTime>? createdAt,
Expression<int>? score,
Expression<Map<String, Object?>?>? extraData,
}) {
return RawValuesInsertable({
if (userId != null) 'user_id': userId,
if (messageId != null) 'message_id': messageId,
if (type != null) 'type': type,
if (createdAt != null) 'created_at': createdAt,
if (score != null) 'score': score,
if (extraData != null) 'extra_data': extraData,
});
}
PinnedMessageReactionsCompanion copyWith(
{Value<String>? userId,
Value<String>? messageId,
Value<String>? type,
Value<DateTime>? createdAt,
Value<int>? score,
Value<Map<String, Object?>?>? extraData}) {
return PinnedMessageReactionsCompanion(
userId: userId ?? this.userId,
messageId: messageId ?? this.messageId,
type: type ?? this.type,
createdAt: createdAt ?? this.createdAt,
score: score ?? this.score,
extraData: extraData ?? this.extraData,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (userId.present) {
map['user_id'] = Variable<String>(userId.value);
}
if (messageId.present) {
map['message_id'] = Variable<String>(messageId.value);
}
if (type.present) {
map['type'] = Variable<String>(type.value);
}
if (createdAt.present) {
map['created_at'] = Variable<DateTime>(createdAt.value);
}
if (score.present) {
map['score'] = Variable<int>(score.value);
}
if (extraData.present) {
final converter = $PinnedMessageReactionsTable.$converter0;
map['extra_data'] =
Variable<String?>(converter.mapToSql(extraData.value));
}
return map;
}
@override
String toString() {
return (StringBuffer('PinnedMessageReactionsCompanion(')
..write('userId: $userId, ')
..write('messageId: $messageId, ')
..write('type: $type, ')
..write('createdAt: $createdAt, ')
..write('score: $score, ')
..write('extraData: $extraData')
..write(')'))
.toString();
}
}
class $PinnedMessageReactionsTable extends PinnedMessageReactions
with TableInfo<$PinnedMessageReactionsTable, PinnedMessageReactionEntity> {
final GeneratedDatabase _db;
final String? _alias;
$PinnedMessageReactionsTable(this._db, [this._alias]);
final VerificationMeta _userIdMeta = const VerificationMeta('userId');
late final GeneratedColumn<String?> userId = GeneratedColumn<String?>(
'user_id', aliasedName, false,
typeName: 'TEXT', requiredDuringInsert: true);
final VerificationMeta _messageIdMeta = const VerificationMeta('messageId');
late final GeneratedColumn<String?> messageId = GeneratedColumn<String?>(
'message_id', aliasedName, false,
typeName: 'TEXT',
requiredDuringInsert: true,
$customConstraints: 'REFERENCES pinned_messages(id) ON DELETE CASCADE');
final VerificationMeta _typeMeta = const VerificationMeta('type');
late final GeneratedColumn<String?> type = GeneratedColumn<String?>(
'type', aliasedName, false,
typeName: 'TEXT', requiredDuringInsert: true);
final VerificationMeta _createdAtMeta = const VerificationMeta('createdAt');
late final GeneratedColumn<DateTime?> createdAt = GeneratedColumn<DateTime?>(
'created_at', aliasedName, false,
typeName: 'INTEGER',
requiredDuringInsert: false,
defaultValue: currentDateAndTime);
final VerificationMeta _scoreMeta = const VerificationMeta('score');
late final GeneratedColumn<int?> score = GeneratedColumn<int?>(
'score', aliasedName, false,
typeName: 'INTEGER',
requiredDuringInsert: false,
defaultValue: const Constant(0));
final VerificationMeta _extraDataMeta = const VerificationMeta('extraData');
late final GeneratedColumnWithTypeConverter<Map<String, Object?>, String?>
extraData = GeneratedColumn<String?>('extra_data', aliasedName, true,
typeName: 'TEXT', requiredDuringInsert: false)
.withConverter<Map<String, Object?>>(
$PinnedMessageReactionsTable.$converter0);
@override
List<GeneratedColumn> get $columns =>
[userId, messageId, type, createdAt, score, extraData];
@override
String get aliasedName => _alias ?? 'pinned_message_reactions';
@override
String get actualTableName => 'pinned_message_reactions';
@override
VerificationContext validateIntegrity(
Insertable<PinnedMessageReactionEntity> instance,
{bool isInserting = false}) {
final context = VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('user_id')) {
context.handle(_userIdMeta,
userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta));
} else if (isInserting) {
context.missing(_userIdMeta);
}
if (data.containsKey('message_id')) {
context.handle(_messageIdMeta,
messageId.isAcceptableOrUnknown(data['message_id']!, _messageIdMeta));
} else if (isInserting) {
context.missing(_messageIdMeta);
}
if (data.containsKey('type')) {
context.handle(
_typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta));
} else if (isInserting) {
context.missing(_typeMeta);
}
if (data.containsKey('created_at')) {
context.handle(_createdAtMeta,
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
}
if (data.containsKey('score')) {
context.handle(
_scoreMeta, score.isAcceptableOrUnknown(data['score']!, _scoreMeta));
}
context.handle(_extraDataMeta, const VerificationResult.success());
return context;
}
@override
Set<GeneratedColumn> get $primaryKey => {messageId, type, userId};
@override
PinnedMessageReactionEntity map(Map<String, dynamic> data,
{String? tablePrefix}) {
return PinnedMessageReactionEntity.fromData(data, _db,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
}
@override
$PinnedMessageReactionsTable createAlias(String alias) {
return $PinnedMessageReactionsTable(_db, alias);
}
static TypeConverter<Map<String, Object?>, String> $converter0 =
MapConverter<Object?>();
}
class ReactionEntity extends DataClass implements Insertable<ReactionEntity> {
/// The id of the user that sent the reaction
final String userId;
@@ -4897,6 +5231,8 @@ abstract class _$MoorChatDatabase extends GeneratedDatabase {
late final $ChannelsTable channels = $ChannelsTable(this);
late final $MessagesTable messages = $MessagesTable(this);
late final $PinnedMessagesTable pinnedMessages = $PinnedMessagesTable(this);
late final $PinnedMessageReactionsTable pinnedMessageReactions =
$PinnedMessageReactionsTable(this);
late final $ReactionsTable reactions = $ReactionsTable(this);
late final $UsersTable users = $UsersTable(this);
late final $MembersTable members = $MembersTable(this);
@@ -4909,6 +5245,8 @@ abstract class _$MoorChatDatabase extends GeneratedDatabase {
late final MessageDao messageDao = MessageDao(this as MoorChatDatabase);
late final PinnedMessageDao pinnedMessageDao =
PinnedMessageDao(this as MoorChatDatabase);
late final PinnedMessageReactionDao pinnedMessageReactionDao =
PinnedMessageReactionDao(this as MoorChatDatabase);
late final MemberDao memberDao = MemberDao(this as MoorChatDatabase);
late final ReactionDao reactionDao = ReactionDao(this as MoorChatDatabase);
late final ReadDao readDao = ReadDao(this as MoorChatDatabase);
@@ -4923,6 +5261,7 @@ abstract class _$MoorChatDatabase extends GeneratedDatabase {
channels,
messages,
pinnedMessages,
pinnedMessageReactions,
reactions,
users,
members,
@@ -3,6 +3,7 @@ export 'channels.dart';
export 'connection_events.dart';
export 'members.dart';
export 'messages.dart';
export 'pinned_message_reactions.dart';
export 'pinned_messages.dart';
export 'reactions.dart';
export 'reads.dart';
@@ -39,8 +39,5 @@ class Members extends Table {
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
@override
Set<Column> get primaryKey => {
userId,
channelCid,
};
Set<Column> get primaryKey => {userId, channelCid};
}
@@ -77,8 +77,8 @@ class Messages extends Table {
TextColumn get pinnedByUserId => text().nullable()();
/// The channel cid of which this message is part of
TextColumn get channelCid => text().nullable().customConstraint(
'NULLABLE REFERENCES channels(cid) ON DELETE CASCADE')();
TextColumn get channelCid =>
text().customConstraint('REFERENCES channels(cid) ON DELETE CASCADE')();
/// A Map of [messageText] translations.
TextColumn get i18n => text().nullable().map(MapConverter<String>())();
@@ -0,0 +1,13 @@
// coverage:ignore-file
import 'package:moor/moor.dart';
import 'package:stream_chat_persistence/src/entity/reactions.dart';
/// Represents a [PinnedMessageReactions] table in [MoorChatDatabase].
@DataClassName('PinnedMessageReactionEntity')
class PinnedMessageReactions extends Reactions {
/// The messageId to which the reaction belongs
@override
TextColumn get messageId => text()
.customConstraint('REFERENCES pinned_messages(id) ON DELETE CASCADE')();
}
@@ -3,6 +3,7 @@ export 'event_mapper.dart';
export 'member_mapper.dart';
export 'message_mapper.dart';
export 'pinned_message_mapper.dart';
export 'pinned_message_reaction_mapper.dart';
export 'reaction_mapper.dart';
export 'read_mapper.dart';
export 'user_mapper.dart';
@@ -51,7 +51,7 @@ extension MessageEntityX on MessageEntity {
/// Useful mapping functions for [Message]
extension MessageX on Message {
/// Maps a [Message] into [MessageEntity]
MessageEntity toEntity({String? cid}) => MessageEntity(
MessageEntity toEntity({required String cid}) => MessageEntity(
id: id,
attachments: attachments.map((it) => jsonEncode(it.toData())).toList(),
channelCid: cid,
@@ -51,7 +51,8 @@ extension PinnedMessageEntityX on PinnedMessageEntity {
/// Useful mapping functions for [Message]
extension PMessageX on Message {
/// Maps a [Message] into [PinnedMessageEntity]
PinnedMessageEntity toPinnedEntity({String? cid}) => PinnedMessageEntity(
PinnedMessageEntity toPinnedEntity({required String cid}) =>
PinnedMessageEntity(
id: id,
attachments: attachments.map((it) => jsonEncode(it.toData())).toList(),
channelCid: cid,
@@ -0,0 +1,29 @@
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
/// Useful mapping functions for [PinnedMessageReactionEntity]
extension PinnedMessageReactionEntityX on PinnedMessageReactionEntity {
/// Maps a [PinnedMessageReactionEntity] into [Reaction]
Reaction toReaction({User? user}) => Reaction(
extraData: extraData ?? {},
type: type,
createdAt: createdAt,
userId: userId,
user: user,
messageId: messageId,
score: score,
);
}
/// Useful mapping functions for [Reaction]
extension PReactionX on Reaction {
/// Maps a [Reaction] into [ReactionEntity]
PinnedMessageReactionEntity toPinnedEntity() => PinnedMessageReactionEntity(
extraData: extraData,
type: type,
createdAt: createdAt,
userId: userId!,
messageId: messageId!,
score: score,
);
}
@@ -296,25 +296,34 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
}
@override
Future<void> updateMembers(String cid, List<Member> members) {
Future<void> bulkUpdateMembers(Map<String, List<Member>> members) {
assert(_debugIsConnected, '');
_logger.info('updateMembers');
return _readProtected(() => db!.memberDao.updateMembers(cid, members));
_logger.info('bulkUpdateMembers');
return _readProtected(() => db!.memberDao.bulkUpdateMembers(members));
}
@override
Future<void> updateMessages(String cid, List<Message> messages) {
Future<void> bulkUpdateMessages(Map<String, List<Message>> messages) {
assert(_debugIsConnected, '');
_logger.info('updateMessages');
return _readProtected(() => db!.messageDao.updateMessages(cid, messages));
_logger.info('bulkUpdateMessages');
return _readProtected(() => db!.messageDao.bulkUpdateMessages(messages));
}
@override
Future<void> updatePinnedMessages(String cid, List<Message> messages) {
Future<void> bulkUpdatePinnedMessages(Map<String, List<Message>> messages) {
assert(_debugIsConnected, '');
_logger.info('updatePinnedMessages');
_logger.info('bulkUpdatePinnedMessages');
return _readProtected(
() => db!.pinnedMessageDao.updateMessages(cid, messages),
() => db!.pinnedMessageDao.bulkUpdateMessages(messages),
);
}
@override
Future<void> updatePinnedMessageReactions(List<Reaction> reactions) {
assert(_debugIsConnected, '');
_logger.info('updatePinnedMessageReactions');
return _readProtected(
() => db!.pinnedMessageReactionDao.updateReactions(reactions),
);
}
@@ -326,10 +335,10 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
}
@override
Future<void> updateReads(String cid, List<Read> reads) {
Future<void> bulkUpdateReads(Map<String, List<Read>> reads) {
assert(_debugIsConnected, '');
_logger.info('updateReads');
return _readProtected(() => db!.readDao.updateReads(cid, reads));
_logger.info('bulkUpdateReads');
return _readProtected(() => db!.readDao.bulkUpdateReads(reads));
}
@override
@@ -339,6 +348,18 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
return _readProtected(() => db!.userDao.updateUsers(users));
}
@override
Future<void> deletePinnedMessageReactionsByMessageId(
List<String> messageIds,
) {
assert(_debugIsConnected, '');
_logger.info('deletePinnedMessageReactionsByMessageId');
return _readProtected(
() =>
db!.pinnedMessageReactionDao.deleteReactionsByMessageIds(messageIds),
);
}
@override
Future<void> deleteReactionsByMessageId(List<String> messageIds) {
assert(_debugIsConnected, '');
@@ -34,6 +34,12 @@ class MockChatDatabase extends Mock implements MoorChatDatabase {
@override
ReactionDao get reactionDao => _reactionDao ??= MockReactionDao();
PinnedMessageReactionDao? _pinnedMessageReactionDao;
@override
PinnedMessageReactionDao get pinnedMessageReactionDao =>
_pinnedMessageReactionDao ??= MockPinnedMessageReactionDao();
ReadDao? _readDao;
@override
@@ -70,6 +76,9 @@ class MockMemberDao extends Mock implements MemberDao {}
class MockReactionDao extends Mock implements ReactionDao {}
class MockPinnedMessageReactionDao extends Mock
implements PinnedMessageReactionDao {}
class MockReadDao extends Mock implements ReadDao {}
class MockChannelQueryDao extends Mock implements ChannelQueryDao {}
@@ -61,12 +61,73 @@ void main() {
expect(updatedChannel.cid, cid);
expect(updatedChannel.type, type);
//Saving a dummy user
const userId = 'userId';
final dummyUser = User(id: userId);
await database.userDao.updateUsers([dummyUser]);
// Saving a dummy member
final dummyMember = Member(userId: userId, user: dummyUser);
await database.memberDao.updateMembers(cid, [dummyMember]);
// Should match the dummy member
final updatedMembers = await database.memberDao.getMembersByCid(cid);
expect(updatedMembers.length, 1);
expect(updatedMembers.first.userId, userId);
// Saving a dummy message
const messageId = 'messageId';
final dummyMessage = Message(id: messageId, user: dummyUser);
await database.messageDao.updateMessages(cid, [dummyMessage]);
// Should match the dummy message
final updatedMessages = await database.messageDao.getMessagesByCid(cid);
expect(updatedMessages.length, 1);
expect(updatedMessages.first.id, messageId);
// Saving a dummy read
final dummyRead = Read(lastRead: DateTime.now(), user: dummyUser);
await database.readDao.updateReads(cid, [dummyRead]);
// Should match the dummy read
final updatedReads = await database.readDao.getReadsByCid(cid);
expect(updatedReads.length, 1);
expect(updatedReads.first.user, dummyUser);
// Saving a dummy reaction
final dummyReaction =
Reaction(type: 'type', messageId: messageId, userId: userId);
await database.reactionDao.updateReactions([dummyReaction]);
// Should match the dummy reaction
final updatedReactions =
await database.reactionDao.getReactionsByUserId(messageId, userId);
expect(updatedReactions.length, 1);
expect(updatedReactions.first.messageId, messageId);
// Deleting the dummyChannel using cid
await channelDao.deleteChannelByCids([cid]);
// Fetched channel Should be null
final channel = await channelDao.getChannelByCid(cid);
expect(channel, isNull);
// Fetched members for passed cid should be empty
final members = await database.memberDao.getMembersByCid(cid);
expect(members, isEmpty);
// Fetched messages for passed cid should be empty
final messages = await database.messageDao.getMessagesByCid(cid);
expect(messages, isEmpty);
// Fetched reads for passed cid should be empty
final reads = await database.readDao.getReadsByCid(cid);
expect(reads, isEmpty);
// Fetched readtions for passed message id and user id should be empty
final reactions =
await database.reactionDao.getReactionsByUserId(messageId, userId);
expect(reactions, isEmpty);
});
test('cids', () async {
@@ -18,6 +18,7 @@ void main() {
});
Future<List<Member>> _prepareTestData(String cid) async {
final channels = [ChannelModel(cid: cid)];
final users = List.generate(3, (index) => User(id: 'testUserId$index'));
final memberList = List.generate(
3,
@@ -34,12 +35,13 @@ void main() {
),
);
await database.userDao.updateUsers(users);
await database.channelDao.updateChannels(channels);
await memberDao.updateMembers(cid, memberList);
return memberList;
}
test('getMembersByCid', () async {
const cid = 'testCid';
const cid = 'test:Cid';
// Should be empty initially
final members = await memberDao.getMembersByCid(cid);
@@ -70,7 +72,7 @@ void main() {
});
test('updateMembers', () async {
const cid = 'testCid';
const cid = 'test:Cid';
// Preparing test data
final memberList = await _prepareTestData(cid);
@@ -132,7 +134,7 @@ void main() {
});
test('deleteMemberByCids', () async {
const cid = 'testCid';
const cid = 'test:Cid';
// Preparing test data
final members = await _prepareTestData(cid);
@@ -23,6 +23,7 @@ void main() {
bool mapAllThreadToFirstMessage = false,
int count = 3,
}) async {
final channels = [ChannelModel(cid: cid)];
final users = List.generate(count, (index) => User(id: 'testUserId$index'));
final messages = List.generate(
count,
@@ -98,13 +99,20 @@ void main() {
if (quoted) ...quotedMessages,
if (threads) ...threadMessages
];
final reaction = Reaction(
type: 'type',
messageId: allMessages.first.id,
user: users.first,
);
await database.userDao.updateUsers(users);
await database.channelDao.updateChannels(channels);
await messageDao.updateMessages(cid, allMessages);
await database.reactionDao.updateReactions([reaction]);
return allMessages;
}
test('deleteMessageByIds', () async {
const cid = 'testCid';
const cid = 'test:Cid';
// Preparing test data
final insertedMessages = await _prepareTestData(cid);
@@ -113,66 +121,112 @@ void main() {
final messages = await messageDao.getMessagesByCid(cid);
expect(messages.length, insertedMessages.length);
final firstMessageId = messages.first.id;
// Fetched reactions list should have one reaction for given message id
final reactions = await database.reactionDao.getReactions(firstMessageId);
expect(reactions.length, 1);
// Deleting 2 messages from DB
await messageDao.deleteMessageByIds(
['testMessageId${cid}0', 'testMessageId${cid}1'],
[firstMessageId, 'testMessageId${cid}1'],
);
// New fetched messages length should 2 less than the
// previous fetched messages
final newMessages = await messageDao.getMessagesByCid(cid);
expect(newMessages.length, messages.length - 2);
// Reaction for the first message should be deleted too
final newReactions =
await database.reactionDao.getReactions(firstMessageId);
expect(newReactions, isEmpty);
});
group('deleteMessageByCids', () {
const cid1 = 'testCid1';
const cid2 = 'testCid2';
const cid1 = 'test:Cid1';
const cid2 = 'test:Cid2';
test('should delete all the messages of first channel', () async {
// Preparing test data
final cid1InsertedMessages = await _prepareTestData(cid1);
final cid2InsertedMessages = await _prepareTestData(cid2);
test(
'should delete all the messages and reactions of first channel',
() async {
// Preparing test data
final cid1InsertedMessages = await _prepareTestData(cid1);
final cid2InsertedMessages = await _prepareTestData(cid2);
// Fetched message list should match the test message list length
final cid1Messages = await messageDao.getMessagesByCid(cid1);
final cid2Messages = await messageDao.getMessagesByCid(cid2);
expect(cid1Messages.length, cid1InsertedMessages.length);
expect(cid2Messages.length, cid2InsertedMessages.length);
// Fetched message list should match the test message list length
final cid1Messages = await messageDao.getMessagesByCid(cid1);
final cid2Messages = await messageDao.getMessagesByCid(cid2);
expect(cid1Messages.length, cid1InsertedMessages.length);
expect(cid2Messages.length, cid2InsertedMessages.length);
// Deleting all the messages of cid1
await messageDao.deleteMessageByCids([cid1]);
// Fetched reactions list should have one reaction for given message id
final cid1firstMessageId = cid1Messages.first.id;
final cid1Reactions =
await database.reactionDao.getReactions(cid1firstMessageId);
expect(cid1Reactions.length, 1);
// Fetched messages length of only cid1 should be empty
final cid1FetchedMessages = await messageDao.getMessagesByCid(cid1);
final cid2FetchedMessages = await messageDao.getMessagesByCid(cid2);
expect(cid1FetchedMessages, isEmpty);
expect(cid2FetchedMessages, isNotEmpty);
});
// Deleting all the messages of cid1
await messageDao.deleteMessageByCids([cid1]);
test('should delete all the messages of both channel', () async {
// Preparing test data
final cid1InsertedMessages = await _prepareTestData(cid1);
final cid2InsertedMessages = await _prepareTestData(cid2);
// Fetched messages length of only cid1 should be empty
final cid1FetchedMessages = await messageDao.getMessagesByCid(cid1);
final cid2FetchedMessages = await messageDao.getMessagesByCid(cid2);
expect(cid1FetchedMessages, isEmpty);
expect(cid2FetchedMessages, isNotEmpty);
// Fetched message list should match the test message list length
final cid1Messages = await messageDao.getMessagesByCid(cid1);
final cid2Messages = await messageDao.getMessagesByCid(cid2);
expect(cid1Messages.length, cid1InsertedMessages.length);
expect(cid2Messages.length, cid2InsertedMessages.length);
// Reaction for the first message should be deleted too
final cid1FetchedReactions =
await database.reactionDao.getReactions(cid1firstMessageId);
expect(cid1FetchedReactions, isEmpty);
},
);
// Deleting all the messages of cid1
await messageDao.deleteMessageByCids([cid1, cid2]);
test(
'should delete all the messages and reactions of both channel',
() async {
// Preparing test data
final cid1InsertedMessages = await _prepareTestData(cid1);
final cid2InsertedMessages = await _prepareTestData(cid2);
// Fetched messages length of both cid1 and cid2 should be empty
final cid1FetchedMessages = await messageDao.getMessagesByCid(cid1);
final cid2FetchedMessages = await messageDao.getMessagesByCid(cid2);
expect(cid1FetchedMessages, isEmpty);
expect(cid2FetchedMessages, isEmpty);
});
// Fetched message list should match the test message list length
final cid1Messages = await messageDao.getMessagesByCid(cid1);
final cid2Messages = await messageDao.getMessagesByCid(cid2);
expect(cid1Messages.length, cid1InsertedMessages.length);
expect(cid2Messages.length, cid2InsertedMessages.length);
// Fetched reactions list should have one reaction for given message id
final cid1FirstMessageId = cid1Messages.first.id;
final cid1Reactions =
await database.reactionDao.getReactions(cid1FirstMessageId);
expect(cid1Reactions.length, 1);
final cid2FirstMessageId = cid2Messages.first.id;
final cid2Reactions =
await database.reactionDao.getReactions(cid2FirstMessageId);
expect(cid2Reactions.length, 1);
// Deleting all the messages of cid1
await messageDao.deleteMessageByCids([cid1, cid2]);
// Fetched messages length of both cid1 and cid2 should be empty
final cid1FetchedMessages = await messageDao.getMessagesByCid(cid1);
final cid2FetchedMessages = await messageDao.getMessagesByCid(cid2);
expect(cid1FetchedMessages, isEmpty);
expect(cid2FetchedMessages, isEmpty);
// Reaction for the first message should be deleted too
final cid1FetchedReactions =
await database.reactionDao.getReactions(cid1FirstMessageId);
expect(cid1FetchedReactions, isEmpty);
final cid2FetchedReactions =
await database.reactionDao.getReactions(cid2FirstMessageId);
expect(cid2FetchedReactions, isEmpty);
},
);
});
test('getMessageById', () async {
const cid = 'testCid';
const cid = 'test:Cid';
const id = 'testMessageId${cid}0';
// Should be null initially
@@ -190,7 +244,7 @@ void main() {
});
test('getThreadMessages', () async {
const cid = 'testCid';
const cid = 'test:Cid';
// Messages should be empty initially
final messages = await messageDao.getThreadMessages(cid);
@@ -209,7 +263,7 @@ void main() {
});
test('getThreadMessagesByParentId', () async {
const cid = 'testCid';
const cid = 'test:Cid';
const parentId = 'testMessageId${cid}0';
// Messages should be empty initially
@@ -228,7 +282,7 @@ void main() {
});
test('getThreadMessagesByParentId along with pagination', () async {
const cid = 'testCid';
const cid = 'test:Cid';
const parentId = 'testMessageId${cid}0';
const options = PaginationParams(
limit: 15,
@@ -262,7 +316,7 @@ void main() {
});
test('getMessagesByCid', () async {
const cid = 'testCid';
const cid = 'test:Cid';
// Should be empty initially
final messages = await messageDao.getMessagesByCid(cid);
@@ -283,7 +337,7 @@ void main() {
});
test('getMessagesByCid along with quotedMessage', () async {
const cid = 'testCid';
const cid = 'test:Cid';
// Should be empty initially
final messages = await messageDao.getMessagesByCid(cid);
@@ -301,7 +355,7 @@ void main() {
});
test('getMessagesByCid along with pagination', () async {
const cid = 'testCid';
const cid = 'test:Cid';
const limit = 15;
const lessThan = 'testMessageId${cid}25';
const greaterThanOrEqual = 'testMessageId${cid}5';
@@ -333,7 +387,7 @@ void main() {
});
test('updateMessages', () async {
const cid = 'testCid';
const cid = 'test:Cid';
// Preparing test data
final insertedMessages = await _prepareTestData(cid);
@@ -23,6 +23,7 @@ void main() {
bool mapAllThreadToFirstMessage = false,
int count = 3,
}) async {
final channels = [ChannelModel(cid: cid)];
final users = List.generate(count, (index) => User(id: 'testUserId$index'));
final messages = List.generate(
count,
@@ -83,13 +84,20 @@ void main() {
if (quoted) ...quotedMessages,
if (threads) ...threadMessages
];
final reaction = Reaction(
type: 'type',
messageId: allMessages.first.id,
user: users.first,
);
await database.userDao.updateUsers(users);
await database.channelDao.updateChannels(channels);
await pinnedMessageDao.updateMessages(cid, allMessages);
await database.pinnedMessageReactionDao.updateReactions([reaction]);
return allMessages;
}
test('deleteMessageByIds', () async {
const cid = 'testCid';
const cid = 'test:Cid';
// Preparing test data
final insertedMessages = await _prepareTestData(cid);
@@ -98,66 +106,117 @@ void main() {
final messages = await pinnedMessageDao.getMessagesByCid(cid);
expect(messages.length, insertedMessages.length);
final firstMessageId = messages.first.id;
// Fetched reactions list should have one reaction for given message id
final reactions =
await database.pinnedMessageReactionDao.getReactions(firstMessageId);
expect(reactions.length, 1);
// Deleting 2 messages from DB
await pinnedMessageDao.deleteMessageByIds(
['testMessageId${cid}0', 'testMessageId${cid}1'],
[firstMessageId, 'testMessageId${cid}1'],
);
// New fetched messages length should 2 less than the
// previous fetched messages
final newMessages = await pinnedMessageDao.getMessagesByCid(cid);
expect(newMessages.length, messages.length - 2);
// Reaction for the first message should be deleted too
final newReactions =
await database.pinnedMessageReactionDao.getReactions(firstMessageId);
expect(newReactions, isEmpty);
});
group('deleteMessageByCids', () {
const cid1 = 'testCid1';
const cid2 = 'testCid2';
const cid1 = 'test:Cid1';
const cid2 = 'test:Cid2';
test('should delete all the messages of first channel', () async {
// Preparing test data
final cid1InsertedMessages = await _prepareTestData(cid1);
final cid2InsertedMessages = await _prepareTestData(cid2);
test(
'should delete all the messages and reactions of first channel',
() async {
// Preparing test data
final cid1InsertedMessages = await _prepareTestData(cid1);
final cid2InsertedMessages = await _prepareTestData(cid2);
// Fetched message list should match the test message list length
final cid1Messages = await pinnedMessageDao.getMessagesByCid(cid1);
final cid2Messages = await pinnedMessageDao.getMessagesByCid(cid2);
expect(cid1Messages.length, cid1InsertedMessages.length);
expect(cid2Messages.length, cid2InsertedMessages.length);
// Fetched message list should match the test message list length
final cid1Messages = await pinnedMessageDao.getMessagesByCid(cid1);
final cid2Messages = await pinnedMessageDao.getMessagesByCid(cid2);
expect(cid1Messages.length, cid1InsertedMessages.length);
expect(cid2Messages.length, cid2InsertedMessages.length);
// Deleting all the messages of cid1
await pinnedMessageDao.deleteMessageByCids([cid1]);
// Fetched reactions list should have one reaction for given message id
final cid1firstMessageId = cid1Messages.first.id;
final cid1Reactions = await database.pinnedMessageReactionDao
.getReactions(cid1firstMessageId);
expect(cid1Reactions.length, 1);
// Fetched messages length of only cid1 should be empty
final cid1FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid1);
final cid2FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid2);
expect(cid1FetchedMessages, isEmpty);
expect(cid2FetchedMessages, isNotEmpty);
});
// Deleting all the messages of cid1
await pinnedMessageDao.deleteMessageByCids([cid1]);
test('should delete all the messages of both channel', () async {
// Preparing test data
final cid1InsertedMessages = await _prepareTestData(cid1);
final cid2InsertedMessages = await _prepareTestData(cid2);
// Fetched messages length of only cid1 should be empty
final cid1FetchedMessages =
await pinnedMessageDao.getMessagesByCid(cid1);
final cid2FetchedMessages =
await pinnedMessageDao.getMessagesByCid(cid2);
expect(cid1FetchedMessages, isEmpty);
expect(cid2FetchedMessages, isNotEmpty);
// Fetched message list should match the test message list length
final cid1Messages = await pinnedMessageDao.getMessagesByCid(cid1);
final cid2Messages = await pinnedMessageDao.getMessagesByCid(cid2);
expect(cid1Messages.length, cid1InsertedMessages.length);
expect(cid2Messages.length, cid2InsertedMessages.length);
// Reaction for the first message should be deleted too
final cid1FetchedReactions = await database.pinnedMessageReactionDao
.getReactions(cid1firstMessageId);
expect(cid1FetchedReactions, isEmpty);
},
);
// Deleting all the messages of cid1
await pinnedMessageDao.deleteMessageByCids([cid1, cid2]);
test(
'should delete all the messages and reactions of both channel',
() async {
// Preparing test data
final cid1InsertedMessages = await _prepareTestData(cid1);
final cid2InsertedMessages = await _prepareTestData(cid2);
// Fetched messages length of both cid1 and cid2 should be empty
final cid1FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid1);
final cid2FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid2);
expect(cid1FetchedMessages, isEmpty);
expect(cid2FetchedMessages, isEmpty);
});
// Fetched message list should match the test message list length
final cid1Messages = await pinnedMessageDao.getMessagesByCid(cid1);
final cid2Messages = await pinnedMessageDao.getMessagesByCid(cid2);
expect(cid1Messages.length, cid1InsertedMessages.length);
expect(cid2Messages.length, cid2InsertedMessages.length);
// Fetched reactions list should have one reaction for given message id
final cid1FirstMessageId = cid1Messages.first.id;
final cid1Reactions = await database.pinnedMessageReactionDao
.getReactions(cid1FirstMessageId);
expect(cid1Reactions.length, 1);
final cid2FirstMessageId = cid2Messages.first.id;
final cid2Reactions = await database.pinnedMessageReactionDao
.getReactions(cid2FirstMessageId);
expect(cid2Reactions.length, 1);
// Deleting all the messages of cid1
await pinnedMessageDao.deleteMessageByCids([cid1, cid2]);
// Fetched messages length of both cid1 and cid2 should be empty
final cid1FetchedMessages =
await pinnedMessageDao.getMessagesByCid(cid1);
final cid2FetchedMessages =
await pinnedMessageDao.getMessagesByCid(cid2);
expect(cid1FetchedMessages, isEmpty);
expect(cid2FetchedMessages, isEmpty);
// Reaction for the first message should be deleted too
final cid1FetchedReactions = await database.pinnedMessageReactionDao
.getReactions(cid1FirstMessageId);
expect(cid1FetchedReactions, isEmpty);
final cid2FetchedReactions = await database.pinnedMessageReactionDao
.getReactions(cid2FirstMessageId);
expect(cid2FetchedReactions, isEmpty);
},
);
});
test('getMessageById', () async {
const cid = 'testCid';
const cid = 'test:Cid';
const id = 'testMessageId${cid}0';
// Should be null initially
@@ -175,7 +234,7 @@ void main() {
});
test('getThreadMessages', () async {
const cid = 'testCid';
const cid = 'test:Cid';
// Messages should be empty initially
final messages = await pinnedMessageDao.getThreadMessages(cid);
@@ -194,7 +253,7 @@ void main() {
});
test('getThreadMessagesByParentId', () async {
const cid = 'testCid';
const cid = 'test:Cid';
const parentId = 'testMessageId${cid}0';
// Messages should be empty initially
@@ -214,7 +273,7 @@ void main() {
});
test('getThreadMessagesByParentId along with pagination', () async {
const cid = 'testCid';
const cid = 'test:Cid';
const parentId = 'testMessageId${cid}0';
const options = PaginationParams(
limit: 15,
@@ -248,7 +307,7 @@ void main() {
});
test('getMessagesByCid', () async {
const cid = 'testCid';
const cid = 'test:Cid';
// Should be empty initially
final messages = await pinnedMessageDao.getMessagesByCid(cid);
@@ -269,7 +328,7 @@ void main() {
});
test('getMessagesByCid along with quotedMessage', () async {
const cid = 'testCid';
const cid = 'test:Cid';
// Should be empty initially
final messages = await pinnedMessageDao.getMessagesByCid(cid);
@@ -287,7 +346,7 @@ void main() {
});
test('getMessagesByCid along with pagination', () async {
const cid = 'testCid';
const cid = 'test:Cid';
const limit = 15;
const lessThan = 'testMessageId${cid}25';
const greaterThanOrEqual = 'testMessageId${cid}5';
@@ -319,7 +378,7 @@ void main() {
});
test('updateMessages', () async {
const cid = 'testCid';
const cid = 'test:Cid';
// Preparing test data
final insertedMessages = await _prepareTestData(cid);
@@ -0,0 +1,205 @@
import 'dart:math' as math;
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/dao/pinned_message_reaction_dao.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
import 'package:test/test.dart';
import '../../stream_chat_persistence_client_test.dart';
void main() {
late PinnedMessageReactionDao pinnedMessageReactionDao;
late MoorChatDatabase database;
setUp(() {
database = testDatabaseProvider('testUserId');
pinnedMessageReactionDao = database.pinnedMessageReactionDao;
});
Future<List<Reaction>> _prepareReactionData(
String messageId, {
String? userId,
int count = 3,
}) async {
const cid = 'test:Cid';
final channels = [ChannelModel(cid: cid)];
final users = List.generate(count, (index) => User(id: 'testUserId$index'));
final message = Message(
id: messageId,
type: 'testType',
user: users.first,
createdAt: DateTime.now(),
shadowed: math.Random().nextBool(),
showInChannel: math.Random().nextBool(),
replyCount: 3,
updatedAt: DateTime.now(),
extraData: const {'extra_test_field': 'extraTestData'},
text: 'Dummy text',
pinned: math.Random().nextBool(),
pinnedAt: DateTime.now(),
pinnedBy: users.first,
);
final reactions = List.generate(
count,
(index) => Reaction(
type: 'testType$index',
createdAt: DateTime.now(),
userId: userId ?? users[index].id,
messageId: message.id,
score: count + 3,
extraData: {'extra_test_field': 'extraTestData'},
),
);
await database.userDao.updateUsers(users);
await database.channelDao.updateChannels(channels);
await database.pinnedMessageDao.updateMessages(cid, [message]);
await pinnedMessageReactionDao.updateReactions(reactions);
return reactions;
}
test('getReactions', () async {
const messageId = 'testMessageId';
// Should be empty initially
final reactions = await pinnedMessageReactionDao.getReactions(messageId);
expect(reactions, isEmpty);
// Adding sample reactions
final insertedReactions = await _prepareReactionData(messageId);
expect(insertedReactions, isNotEmpty);
// Fetched reaction length should match inserted reactions length.
// Every reaction messageId should match the provided messageId.
final fetchedReactions =
await pinnedMessageReactionDao.getReactions(messageId);
expect(fetchedReactions.length, insertedReactions.length);
expect(fetchedReactions.every((it) => it.messageId == messageId), true);
});
test('getReactionsByUserId', () async {
const messageId = 'testMessageId';
const userId = 'testUserId';
// Should be empty initially
final reactions =
await pinnedMessageReactionDao.getReactionsByUserId(messageId, userId);
expect(reactions, isEmpty);
// Adding sample reactions
final insertedReactions =
await _prepareReactionData(messageId, userId: userId);
expect(insertedReactions, isNotEmpty);
// Fetched reaction length should match inserted reactions length.
// Every reaction messageId should match the provided messageId.
// Every reaction userId should match the provided userId.
final fetchedReactions =
await pinnedMessageReactionDao.getReactionsByUserId(messageId, userId);
expect(fetchedReactions.length, insertedReactions.length);
expect(fetchedReactions.every((it) => it.messageId == messageId), true);
expect(fetchedReactions.every((it) => it.userId == userId), true);
});
test('updateReactions', () async {
const messageId = 'testMessageId';
// Preparing test data
final reactions = await _prepareReactionData(messageId);
// Modifying one of the reaction and also adding one new
final copyReaction = reactions.first.copyWith(score: 33);
final newReaction = Reaction(
type: 'testType3',
createdAt: DateTime.now(),
userId: 'testUserId3',
messageId: messageId,
score: 30,
extraData: {'extra_test_field': 'extraTestData'},
);
await pinnedMessageReactionDao.updateReactions([copyReaction, newReaction]);
// Fetched reaction length should be one more than inserted reactions.
// copyReaction `score` modified field should be 33.
// Fetched reactions should contain the newReaction.
final fetchedReactions =
await pinnedMessageReactionDao.getReactions(messageId);
expect(fetchedReactions.length, reactions.length + 1);
expect(
fetchedReactions
.firstWhere((it) =>
it.userId == copyReaction.userId && it.type == copyReaction.type)
.score,
33,
);
expect(
fetchedReactions
.where((it) =>
it.userId == newReaction.userId && it.type == newReaction.type)
.isNotEmpty,
true,
);
});
group('deleteReactionsByMessageIds', () {
const messageId1 = 'testMessageId1';
const messageId2 = 'testMessageId2';
test('should delete all the reactions of first message', () async {
// Preparing test data
final insertedReactions1 = await _prepareReactionData(messageId1);
final insertedReactions2 = await _prepareReactionData(messageId2);
// Fetched reaction list length should match
// the inserted reactions list length
final reactions1 =
await pinnedMessageReactionDao.getReactions(messageId1);
final reactions2 =
await pinnedMessageReactionDao.getReactions(messageId2);
expect(reactions1.length, insertedReactions1.length);
expect(reactions2.length, insertedReactions2.length);
// Deleting all the reactions of messageId1
await pinnedMessageReactionDao.deleteReactionsByMessageIds([messageId1]);
// Fetched reactions length of only messageId1 should be empty
final fetchedReactions1 =
await pinnedMessageReactionDao.getReactions(messageId1);
final fetchedReactions2 =
await pinnedMessageReactionDao.getReactions(messageId2);
expect(fetchedReactions1, isEmpty);
expect(fetchedReactions2, isNotEmpty);
});
test('should delete all the messages of both message', () async {
// Preparing test data
final insertedReactions1 = await _prepareReactionData(messageId1);
final insertedReactions2 = await _prepareReactionData(messageId2);
// Fetched reaction list length should match
// the inserted reactions list length
final reactions1 =
await pinnedMessageReactionDao.getReactions(messageId1);
final reactions2 =
await pinnedMessageReactionDao.getReactions(messageId2);
expect(reactions1.length, insertedReactions1.length);
expect(reactions2.length, insertedReactions2.length);
// Deleting all the reactions of messageId1 and messageId2
await pinnedMessageReactionDao
.deleteReactionsByMessageIds([messageId1, messageId2]);
// Fetched reactions length of both messages should be empty
final fetchedReactions1 =
await pinnedMessageReactionDao.getReactions(messageId1);
final fetchedReactions2 =
await pinnedMessageReactionDao.getReactions(messageId2);
expect(fetchedReactions1, isEmpty);
expect(fetchedReactions2, isEmpty);
});
});
tearDown(() async {
await database.disconnect();
});
}
@@ -21,6 +21,8 @@ void main() {
String? userId,
int count = 3,
}) async {
const cid = 'test:Cid';
final channels = [ChannelModel(cid: cid)];
final users = List.generate(count, (index) => User(id: 'testUserId$index'));
final message = Message(
id: messageId,
@@ -50,7 +52,8 @@ void main() {
);
await database.userDao.updateUsers(users);
await database.messageDao.updateMessages('testCid', [message]);
await database.channelDao.updateChannels(channels);
await database.messageDao.updateMessages(cid, [message]);
await reactionDao.updateReactions(reactions);
return reactions;
@@ -16,6 +16,7 @@ void main() {
});
Future<List<Read>> _prepareReadData(String cid, {int count = 3}) async {
final channels = [ChannelModel(cid: cid)];
final users = List.generate(count, (index) => User(id: 'testUserId$index'));
final reads = List.generate(
count,
@@ -27,12 +28,13 @@ void main() {
);
await database.userDao.updateUsers(users);
await database.channelDao.updateChannels(channels);
await readDao.updateReads(cid, reads);
return reads;
}
test('getReadsByCid', () async {
const cid = 'testCid';
const cid = 'test:Cid';
// Should be empty initially
final reads = await readDao.getReadsByCid(cid);
@@ -55,7 +57,7 @@ void main() {
});
test('updateReads', () async {
const cid = 'testCid';
const cid = 'test:Cid';
// Preparing test data
final insertedReads = await _prepareReadData(cid);
@@ -0,0 +1,52 @@
import 'package:test/test.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
import 'package:stream_chat_persistence/src/mapper/pinned_message_reaction_mapper.dart';
import '../utils/date_matcher.dart';
void main() {
test('toReaction should map the entity into Reaction', () {
final user = User(id: 'testUserId');
final message = Message(id: 'testMessageId');
final entity = PinnedMessageReactionEntity(
userId: user.id,
messageId: message.id,
type: 'haha',
score: 33,
createdAt: DateTime.now(),
extraData: {'extra_test_data': 'extraData'},
);
final reaction = entity.toReaction(user: user);
expect(reaction, isA<Reaction>());
expect(reaction.userId, entity.userId);
expect(reaction.messageId, entity.messageId);
expect(reaction.type, entity.type);
expect(reaction.score, entity.score);
expect(reaction.createdAt, isSameDateAs(entity.createdAt));
expect(reaction.extraData, entity.extraData);
});
test('toEntity should map reaction into PinnedMessageReactionEntity', () {
final user = User(id: 'testUserId');
final message = Message(id: 'testMessageId');
final reaction = Reaction(
userId: user.id,
messageId: message.id,
type: 'haha',
score: 33,
createdAt: DateTime.now(),
extraData: {'extra_test_data': 'extraData'},
);
final entity = reaction.toPinnedEntity();
expect(entity, isA<PinnedMessageReactionEntity>());
expect(entity.userId, reaction.userId);
expect(entity.messageId, reaction.messageId);
expect(entity.type, reaction.type);
expect(entity.score, reaction.score);
expect(entity.createdAt, isSameDateAs(reaction.createdAt));
expect(entity.extraData, reaction.extraData);
});
}
@@ -397,23 +397,26 @@ void main() {
test('updateMessages', () async {
const cid = 'testCid';
final messages = List.generate(3, (index) => Message());
when(() => mockDatabase.messageDao.updateMessages(cid, messages))
when(() => mockDatabase.messageDao.bulkUpdateMessages({cid: messages}))
.thenAnswer((_) => Future.value());
await client.updateMessages(cid, messages);
verify(() => mockDatabase.messageDao.updateMessages(cid, messages))
verify(() => mockDatabase.messageDao.bulkUpdateMessages({cid: messages}))
.called(1);
});
test('updatePinnedMessages', () async {
const cid = 'testCid';
final messages = List.generate(3, (index) => Message());
when(() => mockDatabase.pinnedMessageDao.updateMessages(cid, messages))
.thenAnswer((_) => Future.value());
when(
() => mockDatabase.pinnedMessageDao.bulkUpdateMessages({cid: messages}),
).thenAnswer((_) => Future.value());
await client.updatePinnedMessages(cid, messages);
verify(() => mockDatabase.pinnedMessageDao.updateMessages(cid, messages))
.called(1);
verify(
() => mockDatabase.pinnedMessageDao.bulkUpdateMessages({cid: messages}),
).called(1);
});
test('getChannelThreads', () async {
@@ -456,11 +459,11 @@ void main() {
test('updateMembers', () async {
const cid = 'testCid';
final members = List.generate(3, (index) => Member());
when(() => mockDatabase.memberDao.updateMembers(cid, members))
when(() => mockDatabase.memberDao.bulkUpdateMembers({cid: members}))
.thenAnswer((_) => Future.value());
await client.updateMembers(cid, members);
verify(() => mockDatabase.memberDao.updateMembers(cid, members))
verify(() => mockDatabase.memberDao.bulkUpdateMembers({cid: members}))
.called(1);
});
@@ -473,11 +476,12 @@ void main() {
lastRead: DateTime.now(),
),
);
when(() => mockDatabase.readDao.updateReads(cid, reads))
when(() => mockDatabase.readDao.bulkUpdateReads({cid: reads}))
.thenAnswer((_) => Future.value());
await client.updateReads(cid, reads);
verify(() => mockDatabase.readDao.updateReads(cid, reads)).called(1);
verify(() => mockDatabase.readDao.bulkUpdateReads({cid: reads}))
.called(1);
});
test('updateUsers', () async {
@@ -502,6 +506,21 @@ void main() {
.called(1);
});
test('updatePinnedMessageReactions', () async {
final reactions = List.generate(
3,
(index) => Reaction(type: 'testType$index'),
);
when(() =>
mockDatabase.pinnedMessageReactionDao.updateReactions(reactions))
.thenAnswer((_) => Future.value());
await client.updatePinnedMessageReactions(reactions);
verify(() =>
mockDatabase.pinnedMessageReactionDao.updateReactions(reactions))
.called(1);
});
test('deleteReactionsByMessageId', () async {
final messageIds = <String>[];
when(() =>
@@ -514,6 +533,17 @@ void main() {
.called(1);
});
test('deletePinnedMessageReactionsByMessageId', () async {
final messageIds = <String>[];
when(() => mockDatabase.pinnedMessageReactionDao
.deleteReactionsByMessageIds(messageIds))
.thenAnswer((_) => Future.value());
await client.deletePinnedMessageReactionsByMessageId(messageIds);
verify(() => mockDatabase.pinnedMessageReactionDao
.deleteReactionsByMessageIds(messageIds)).called(1);
});
test('deleteMembersByCids', () async {
final cids = <String>[];
when(() => mockDatabase.memberDao.deleteMemberByCids(cids))